diff --git a/engines/ags/engine/ac/asset_helper.h b/engines/ags/engine/ac/asset_helper.h new file mode 100644 index 00000000000..9c6c442166c --- /dev/null +++ b/engines/ags/engine/ac/asset_helper.h @@ -0,0 +1,67 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Functions related to finding and opening game assets. +// +//============================================================================= +#ifndef __AGS_EE_AC__ASSETHELPER_H +#define __AGS_EE_AC__ASSETHELPER_H +#include +#include +#include "util/string.h" + +namespace AGS { namespace Common {class Stream;}} +using AGS::Common::Stream; +using AGS::Common::String; + +// Looks for valid asset library everywhere and returns path, or empty string if failed +String find_assetlib(const String &filename); +// Looks up for known valid asset library and returns path, or empty string if failed +String get_known_assetlib(const String &filename); +// Looks for asset everywhere and returns opened stream, or NULL if failed +Stream *find_open_asset(const String &filename); + +extern "C" { + struct PACKFILE; // Allegro 4's own stream type + struct DUMBFILE; // DUMB stream type +} + +// AssetPath combines asset library and item names +// TODO: implement support for registering multiple libraries at once for +// the AssetManager, then we could remove assetlib parameter. +typedef std::pair AssetPath; + +// Returns the path to the audio asset, considering the given bundling type +AssetPath get_audio_clip_assetpath(int bundling_type, const String &filename); +// Returns the path to the voice-over asset +AssetPath get_voice_over_assetpath(const String &filename); + +// Custom AGS PACKFILE user object +// TODO: it is preferrable to let our Stream define custom readable window instead, +// keeping this as simple as possible for now (we may require a stream classes overhaul). +struct AGS_PACKFILE_OBJ +{ + std::unique_ptr stream; + size_t asset_size = 0u; + size_t remains = 0u; +}; +// Creates PACKFILE stream from AGS asset. +// This function is supposed to be used only when you have to create Allegro +// object, passing PACKFILE stream to constructor. +PACKFILE *PackfileFromAsset(const AssetPath &path, size_t &asset_size); +// Creates DUMBFILE stream from AGS asset. Used for creating DUMB objects +DUMBFILE *DUMBfileFromAsset(const AssetPath &path, size_t &asset_size); +bool DoesAssetExistInLib(const AssetPath &assetname); + +#endif // __AGS_EE_AC__ASSETHELPER_H diff --git a/engines/ags/engine/ac/audiochannel.cpp b/engines/ags/engine/ac/audiochannel.cpp new file mode 100644 index 00000000000..4ac75c4e7a7 --- /dev/null +++ b/engines/ags/engine/ac/audiochannel.cpp @@ -0,0 +1,353 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/audiochannel.h" +#include "ac/gamestate.h" +#include "ac/global_audio.h" +#include "ac/dynobj/cc_audioclip.h" +#include "debug/debug_log.h" +#include "game/roomstruct.h" +#include "script/runtimescriptvalue.h" +#include "media/audio/audio_system.h" + +using namespace AGS::Common; + +extern GameState play; +extern RoomStruct thisroom; +extern CCAudioClip ccDynamicAudioClip; + +int AudioChannel_GetID(ScriptAudioChannel *channel) +{ + return channel->id; +} + +int AudioChannel_GetIsPlaying(ScriptAudioChannel *channel) +{ + if (play.fast_forward) + { + return 0; + } + + return channel_is_playing(channel->id) ? 1 : 0; +} + +int AudioChannel_GetPanning(ScriptAudioChannel *channel) +{ + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + return ch->panningAsPercentage; + } + return 0; +} + +void AudioChannel_SetPanning(ScriptAudioChannel *channel, int newPanning) +{ + if ((newPanning < -100) || (newPanning > 100)) + quitprintf("!AudioChannel.Panning: panning value must be between -100 and 100 (passed=%d)", newPanning); + + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + ch->set_panning(((newPanning + 100) * 255) / 200); + ch->panningAsPercentage = newPanning; + } +} + +ScriptAudioClip* AudioChannel_GetPlayingClip(ScriptAudioChannel *channel) +{ + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + return (ScriptAudioClip*)ch->sourceClip; + } + return nullptr; +} + +int AudioChannel_GetPosition(ScriptAudioChannel *channel) +{ + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + if (play.fast_forward) + return 999999999; + + return ch->get_pos(); + } + return 0; +} + +int AudioChannel_GetPositionMs(ScriptAudioChannel *channel) +{ + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + if (play.fast_forward) + return 999999999; + + return ch->get_pos_ms(); + } + return 0; +} + +int AudioChannel_GetLengthMs(ScriptAudioChannel *channel) +{ + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + return ch->get_length_ms(); + } + return 0; +} + +int AudioChannel_GetVolume(ScriptAudioChannel *channel) +{ + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + return ch->get_volume(); + } + return 0; +} + +int AudioChannel_SetVolume(ScriptAudioChannel *channel, int newVolume) +{ + if ((newVolume < 0) || (newVolume > 100)) + quitprintf("!AudioChannel.Volume: new value out of range (supplied: %d, range: 0..100)", newVolume); + + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + ch->set_volume_percent(newVolume); + } + return 0; +} + +int AudioChannel_GetSpeed(ScriptAudioChannel *channel) +{ + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + return ch->get_speed(); + } + return 0; +} + +void AudioChannel_SetSpeed(ScriptAudioChannel *channel, int new_speed) +{ + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + ch->set_speed(new_speed); + } +} + +void AudioChannel_Stop(ScriptAudioChannel *channel) +{ + if (channel->id == SCHAN_SPEECH && play.IsNonBlockingVoiceSpeech()) + stop_voice_nonblocking(); + else + stop_or_fade_out_channel(channel->id, -1, nullptr); +} + +void AudioChannel_Seek(ScriptAudioChannel *channel, int newPosition) +{ + if (newPosition < 0) + quitprintf("!AudioChannel.Seek: invalid seek position %d", newPosition); + + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + ch->seek(newPosition); + } +} + +void AudioChannel_SetRoomLocation(ScriptAudioChannel *channel, int xPos, int yPos) +{ + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(channel->id); + + if (ch) + { + int maxDist = ((xPos > thisroom.Width / 2) ? xPos : (thisroom.Width - xPos)) - AMBIENCE_FULL_DIST; + ch->xSource = (xPos > 0) ? xPos : -1; + ch->ySource = yPos; + ch->maximumPossibleDistanceAway = maxDist; + if (xPos > 0) + { + update_directional_sound_vol(); + } + else + { + ch->apply_directional_modifier(0); + } + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// int | ScriptAudioChannel *channel +RuntimeScriptValue Sc_AudioChannel_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioChannel, AudioChannel_GetID); +} + +// int | ScriptAudioChannel *channel +RuntimeScriptValue Sc_AudioChannel_GetIsPlaying(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioChannel, AudioChannel_GetIsPlaying); +} + +// int | ScriptAudioChannel *channel +RuntimeScriptValue Sc_AudioChannel_GetPanning(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioChannel, AudioChannel_GetPanning); +} + +// void | ScriptAudioChannel *channel, int newPanning +RuntimeScriptValue Sc_AudioChannel_SetPanning(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptAudioChannel, AudioChannel_SetPanning); +} + +// ScriptAudioClip* | ScriptAudioChannel *channel +RuntimeScriptValue Sc_AudioChannel_GetPlayingClip(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptAudioChannel, ScriptAudioClip, ccDynamicAudioClip, AudioChannel_GetPlayingClip); +} + +// int | ScriptAudioChannel *channel +RuntimeScriptValue Sc_AudioChannel_GetPosition(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioChannel, AudioChannel_GetPosition); +} + +// int | ScriptAudioChannel *channel +RuntimeScriptValue Sc_AudioChannel_GetPositionMs(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioChannel, AudioChannel_GetPositionMs); +} + +// int | ScriptAudioChannel *channel +RuntimeScriptValue Sc_AudioChannel_GetLengthMs(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioChannel, AudioChannel_GetLengthMs); +} + +// int | ScriptAudioChannel *channel +RuntimeScriptValue Sc_AudioChannel_GetVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioChannel, AudioChannel_GetVolume); +} + +// int | ScriptAudioChannel *channel, int newVolume +RuntimeScriptValue Sc_AudioChannel_SetVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT(ScriptAudioChannel, AudioChannel_SetVolume); +} + +// void | ScriptAudioChannel *channel +RuntimeScriptValue Sc_AudioChannel_Stop(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptAudioChannel, AudioChannel_Stop); +} + +// void | ScriptAudioChannel *channel, int newPosition +RuntimeScriptValue Sc_AudioChannel_Seek(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptAudioChannel, AudioChannel_Seek); +} + +// void | ScriptAudioChannel *channel, int xPos, int yPos +RuntimeScriptValue Sc_AudioChannel_SetRoomLocation(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(ScriptAudioChannel, AudioChannel_SetRoomLocation); +} + +RuntimeScriptValue Sc_AudioChannel_GetSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioChannel, AudioChannel_GetSpeed); +} + +RuntimeScriptValue Sc_AudioChannel_SetSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptAudioChannel, AudioChannel_SetSpeed); +} + +void RegisterAudioChannelAPI() +{ + ccAddExternalObjectFunction("AudioChannel::Seek^1", Sc_AudioChannel_Seek); + ccAddExternalObjectFunction("AudioChannel::SetRoomLocation^2", Sc_AudioChannel_SetRoomLocation); + ccAddExternalObjectFunction("AudioChannel::Stop^0", Sc_AudioChannel_Stop); + ccAddExternalObjectFunction("AudioChannel::get_ID", Sc_AudioChannel_GetID); + ccAddExternalObjectFunction("AudioChannel::get_IsPlaying", Sc_AudioChannel_GetIsPlaying); + ccAddExternalObjectFunction("AudioChannel::get_LengthMs", Sc_AudioChannel_GetLengthMs); + ccAddExternalObjectFunction("AudioChannel::get_Panning", Sc_AudioChannel_GetPanning); + ccAddExternalObjectFunction("AudioChannel::set_Panning", Sc_AudioChannel_SetPanning); + ccAddExternalObjectFunction("AudioChannel::get_PlayingClip", Sc_AudioChannel_GetPlayingClip); + ccAddExternalObjectFunction("AudioChannel::get_Position", Sc_AudioChannel_GetPosition); + ccAddExternalObjectFunction("AudioChannel::get_PositionMs", Sc_AudioChannel_GetPositionMs); + ccAddExternalObjectFunction("AudioChannel::get_Volume", Sc_AudioChannel_GetVolume); + ccAddExternalObjectFunction("AudioChannel::set_Volume", Sc_AudioChannel_SetVolume); + ccAddExternalObjectFunction("AudioChannel::get_Speed", Sc_AudioChannel_GetSpeed); + ccAddExternalObjectFunction("AudioChannel::set_Speed", Sc_AudioChannel_SetSpeed); + // For compatibility with Ahmet Kamil's (aka Gord10) custom engine + ccAddExternalObjectFunction("AudioChannel::SetSpeed^1", Sc_AudioChannel_SetSpeed); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("AudioChannel::Seek^1", (void*)AudioChannel_Seek); + ccAddExternalFunctionForPlugin("AudioChannel::SetRoomLocation^2", (void*)AudioChannel_SetRoomLocation); + ccAddExternalFunctionForPlugin("AudioChannel::Stop^0", (void*)AudioChannel_Stop); + ccAddExternalFunctionForPlugin("AudioChannel::get_ID", (void*)AudioChannel_GetID); + ccAddExternalFunctionForPlugin("AudioChannel::get_IsPlaying", (void*)AudioChannel_GetIsPlaying); + ccAddExternalFunctionForPlugin("AudioChannel::get_LengthMs", (void*)AudioChannel_GetLengthMs); + ccAddExternalFunctionForPlugin("AudioChannel::get_Panning", (void*)AudioChannel_GetPanning); + ccAddExternalFunctionForPlugin("AudioChannel::set_Panning", (void*)AudioChannel_SetPanning); + ccAddExternalFunctionForPlugin("AudioChannel::get_PlayingClip", (void*)AudioChannel_GetPlayingClip); + ccAddExternalFunctionForPlugin("AudioChannel::get_Position", (void*)AudioChannel_GetPosition); + ccAddExternalFunctionForPlugin("AudioChannel::get_PositionMs", (void*)AudioChannel_GetPositionMs); + ccAddExternalFunctionForPlugin("AudioChannel::get_Volume", (void*)AudioChannel_GetVolume); + ccAddExternalFunctionForPlugin("AudioChannel::set_Volume", (void*)AudioChannel_SetVolume); +} diff --git a/engines/ags/engine/ac/audiochannel.h b/engines/ags/engine/ac/audiochannel.h new file mode 100644 index 00000000000..de655725ad4 --- /dev/null +++ b/engines/ags/engine/ac/audiochannel.h @@ -0,0 +1,38 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__AUDIOCHANNEL_H +#define __AGS_EE_AC__AUDIOCHANNEL_H + +#include "ac/dynobj/scriptaudioclip.h" +#include "ac/dynobj/scriptaudiochannel.h" + +int AudioChannel_GetID(ScriptAudioChannel *channel); +int AudioChannel_GetIsPlaying(ScriptAudioChannel *channel); +int AudioChannel_GetPanning(ScriptAudioChannel *channel); +void AudioChannel_SetPanning(ScriptAudioChannel *channel, int newPanning); +ScriptAudioClip* AudioChannel_GetPlayingClip(ScriptAudioChannel *channel); +int AudioChannel_GetPosition(ScriptAudioChannel *channel); +int AudioChannel_GetPositionMs(ScriptAudioChannel *channel); +int AudioChannel_GetLengthMs(ScriptAudioChannel *channel); +int AudioChannel_GetVolume(ScriptAudioChannel *channel); +int AudioChannel_SetVolume(ScriptAudioChannel *channel, int newVolume); +void AudioChannel_Stop(ScriptAudioChannel *channel); +void AudioChannel_Seek(ScriptAudioChannel *channel, int newPosition); +void AudioChannel_SetRoomLocation(ScriptAudioChannel *channel, int xPos, int yPos); + +#endif // __AGS_EE_AC__AUDIOCHANNEL_H diff --git a/engines/ags/engine/ac/audioclip.cpp b/engines/ags/engine/ac/audioclip.cpp new file mode 100644 index 00000000000..5489ee5a3b3 --- /dev/null +++ b/engines/ags/engine/ac/audioclip.cpp @@ -0,0 +1,154 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/asset_helper.h" +#include "ac/audioclip.h" +#include "ac/audiochannel.h" +#include "ac/gamesetupstruct.h" +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_audiochannel.h" +#include "media/audio/audio_system.h" + +extern GameSetupStruct game; +extern ScriptAudioChannel scrAudioChannel[MAX_SOUND_CHANNELS + 1]; +extern CCAudioChannel ccDynamicAudio; + +int AudioClip_GetID(ScriptAudioClip *clip) +{ + return clip->id; +} + +int AudioClip_GetFileType(ScriptAudioClip *clip) +{ + return clip->fileType; +} + +int AudioClip_GetType(ScriptAudioClip *clip) +{ + return clip->type; +} +int AudioClip_GetIsAvailable(ScriptAudioClip *clip) +{ + return DoesAssetExistInLib(get_audio_clip_assetpath(clip->bundlingType, clip->fileName)) ? 1 : 0; +} + +void AudioClip_Stop(ScriptAudioClip *clip) +{ + AudioChannelsLock lock; + for (int i = 0; i < MAX_SOUND_CHANNELS; i++) + { + auto* ch = lock.GetChannelIfPlaying(i); + if ((ch != nullptr) && (ch->sourceClip == clip)) + { + AudioChannel_Stop(&scrAudioChannel[i]); + } + } +} + +ScriptAudioChannel* AudioClip_Play(ScriptAudioClip *clip, int priority, int repeat) +{ + ScriptAudioChannel *sc_ch = play_audio_clip(clip, priority, repeat, 0, false); + return sc_ch; +} + +ScriptAudioChannel* AudioClip_PlayFrom(ScriptAudioClip *clip, int position, int priority, int repeat) +{ + ScriptAudioChannel *sc_ch = play_audio_clip(clip, priority, repeat, position, false); + return sc_ch; +} + +ScriptAudioChannel* AudioClip_PlayQueued(ScriptAudioClip *clip, int priority, int repeat) +{ + ScriptAudioChannel *sc_ch = play_audio_clip(clip, priority, repeat, 0, true); + return sc_ch; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +RuntimeScriptValue Sc_AudioClip_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioClip, AudioClip_GetID); +} + +// int | ScriptAudioClip *clip +RuntimeScriptValue Sc_AudioClip_GetFileType(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioClip, AudioClip_GetFileType); +} + +// int | ScriptAudioClip *clip +RuntimeScriptValue Sc_AudioClip_GetType(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioClip, AudioClip_GetType); +} + +// int | ScriptAudioClip *clip +RuntimeScriptValue Sc_AudioClip_GetIsAvailable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptAudioClip, AudioClip_GetIsAvailable); +} + +// void | ScriptAudioClip *clip +RuntimeScriptValue Sc_AudioClip_Stop(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptAudioClip, AudioClip_Stop); +} + +// ScriptAudioChannel* | ScriptAudioClip *clip, int priority, int repeat +RuntimeScriptValue Sc_AudioClip_Play(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT2(ScriptAudioClip, ScriptAudioChannel, ccDynamicAudio, AudioClip_Play); +} + +// ScriptAudioChannel* | ScriptAudioClip *clip, int position, int priority, int repeat +RuntimeScriptValue Sc_AudioClip_PlayFrom(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT3(ScriptAudioClip, ScriptAudioChannel, ccDynamicAudio, AudioClip_PlayFrom); +} + +// ScriptAudioChannel* | ScriptAudioClip *clip, int priority, int repeat +RuntimeScriptValue Sc_AudioClip_PlayQueued(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT2(ScriptAudioClip, ScriptAudioChannel, ccDynamicAudio, AudioClip_PlayQueued); +} + +void RegisterAudioClipAPI() +{ + ccAddExternalObjectFunction("AudioClip::Play^2", Sc_AudioClip_Play); + ccAddExternalObjectFunction("AudioClip::PlayFrom^3", Sc_AudioClip_PlayFrom); + ccAddExternalObjectFunction("AudioClip::PlayQueued^2", Sc_AudioClip_PlayQueued); + ccAddExternalObjectFunction("AudioClip::Stop^0", Sc_AudioClip_Stop); + ccAddExternalObjectFunction("AudioClip::get_ID", Sc_AudioClip_GetID); + ccAddExternalObjectFunction("AudioClip::get_FileType", Sc_AudioClip_GetFileType); + ccAddExternalObjectFunction("AudioClip::get_IsAvailable", Sc_AudioClip_GetIsAvailable); + ccAddExternalObjectFunction("AudioClip::get_Type", Sc_AudioClip_GetType); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("AudioClip::Play^2", (void*)AudioClip_Play); + ccAddExternalFunctionForPlugin("AudioClip::PlayFrom^3", (void*)AudioClip_PlayFrom); + ccAddExternalFunctionForPlugin("AudioClip::PlayQueued^2", (void*)AudioClip_PlayQueued); + ccAddExternalFunctionForPlugin("AudioClip::Stop^0", (void*)AudioClip_Stop); + ccAddExternalFunctionForPlugin("AudioClip::get_FileType", (void*)AudioClip_GetFileType); + ccAddExternalFunctionForPlugin("AudioClip::get_IsAvailable", (void*)AudioClip_GetIsAvailable); + ccAddExternalFunctionForPlugin("AudioClip::get_Type", (void*)AudioClip_GetType); +} diff --git a/engines/ags/engine/ac/audioclip.h b/engines/ags/engine/ac/audioclip.h new file mode 100644 index 00000000000..ffb4a100b5e --- /dev/null +++ b/engines/ags/engine/ac/audioclip.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__AUDIOCLIP_H +#define __AGS_EE_AC__AUDIOCLIP_H + +#include "ac/dynobj/scriptaudioclip.h" +#include "ac/dynobj/scriptaudiochannel.h" + +int AudioClip_GetFileType(ScriptAudioClip *clip); +int AudioClip_GetType(ScriptAudioClip *clip); +int AudioClip_GetIsAvailable(ScriptAudioClip *clip); +void AudioClip_Stop(ScriptAudioClip *clip); +ScriptAudioChannel* AudioClip_Play(ScriptAudioClip *clip, int priority, int repeat); +ScriptAudioChannel* AudioClip_PlayFrom(ScriptAudioClip *clip, int position, int priority, int repeat); +ScriptAudioChannel* AudioClip_PlayQueued(ScriptAudioClip *clip, int priority, int repeat); + +#endif // __AGS_EE_AC__AUDIOCLIP_H diff --git a/engines/ags/engine/ac/button.cpp b/engines/ags/engine/ac/button.cpp new file mode 100644 index 00000000000..ecbcb1b1bc1 --- /dev/null +++ b/engines/ags/engine/ac/button.cpp @@ -0,0 +1,501 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/button.h" +#include "ac/common.h" +#include "ac/gui.h" +#include "ac/view.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_translation.h" +#include "ac/string.h" +#include "ac/viewframe.h" +#include "debug/debug_log.h" +#include "gui/animatingguibutton.h" +#include "gui/guimain.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; +extern ViewStruct*views; + +// *** BUTTON FUNCTIONS + +AnimatingGUIButton animbuts[MAX_ANIMATING_BUTTONS]; +int numAnimButs; + +void Button_Animate(GUIButton *butt, int view, int loop, int speed, int repeat) { + int guin = butt->ParentId; + int objn = butt->Id; + + if ((view < 1) || (view > game.numviews)) + quit("!AnimateButton: invalid view specified"); + view--; + + if ((loop < 0) || (loop >= views[view].numLoops)) + quit("!AnimateButton: invalid loop specified for view"); + + // if it's already animating, stop it + FindAndRemoveButtonAnimation(guin, objn); + + if (numAnimButs >= MAX_ANIMATING_BUTTONS) + quit("!AnimateButton: too many animating GUI buttons at once"); + + int buttonId = guis[guin].GetControlID(objn); + + guibuts[buttonId].PushedImage = 0; + guibuts[buttonId].MouseOverImage = 0; + + animbuts[numAnimButs].ongui = guin; + animbuts[numAnimButs].onguibut = objn; + animbuts[numAnimButs].buttonid = buttonId; + animbuts[numAnimButs].view = view; + animbuts[numAnimButs].loop = loop; + animbuts[numAnimButs].speed = speed; + animbuts[numAnimButs].repeat = repeat; + animbuts[numAnimButs].frame = -1; + animbuts[numAnimButs].wait = 0; + numAnimButs++; + // launch into the first frame + if (UpdateAnimatingButton(numAnimButs - 1)) + { + debug_script_warn("AnimateButton: no frames to animate"); + StopButtonAnimation(numAnimButs - 1); + } +} + +const char* Button_GetText_New(GUIButton *butt) { + return CreateNewScriptString(butt->GetText()); +} + +void Button_GetText(GUIButton *butt, char *buffer) { + strcpy(buffer, butt->GetText()); +} + +void Button_SetText(GUIButton *butt, const char *newtx) { + newtx = get_translation(newtx); + + if (strcmp(butt->GetText(), newtx)) { + guis_need_update = 1; + butt->SetText(newtx); + } +} + +void Button_SetFont(GUIButton *butt, int newFont) { + if ((newFont < 0) || (newFont >= game.numfonts)) + quit("!Button.Font: invalid font number."); + + if (butt->Font != newFont) { + butt->Font = newFont; + guis_need_update = 1; + } +} + +int Button_GetFont(GUIButton *butt) { + return butt->Font; +} + +int Button_GetClipImage(GUIButton *butt) { + return butt->IsClippingImage() ? 1 : 0; +} + +void Button_SetClipImage(GUIButton *butt, int newval) { + if (butt->IsClippingImage() != (newval != 0)) + { + butt->SetClipImage(newval != 0); + guis_need_update = 1; + } +} + +int Button_GetGraphic(GUIButton *butt) { + // return currently displayed pic + if (butt->CurrentImage < 0) + return butt->Image; + return butt->CurrentImage; +} + +int Button_GetMouseOverGraphic(GUIButton *butt) { + return butt->MouseOverImage; +} + +void Button_SetMouseOverGraphic(GUIButton *guil, int slotn) { + debug_script_log("GUI %d Button %d mouseover set to slot %d", guil->ParentId, guil->Id, slotn); + + if ((guil->IsMouseOver != 0) && (guil->IsPushed == 0)) + guil->CurrentImage = slotn; + guil->MouseOverImage = slotn; + + guis_need_update = 1; + FindAndRemoveButtonAnimation(guil->ParentId, guil->Id); +} + +int Button_GetNormalGraphic(GUIButton *butt) { + return butt->Image; +} + +void Button_SetNormalGraphic(GUIButton *guil, int slotn) { + debug_script_log("GUI %d Button %d normal set to slot %d", guil->ParentId, guil->Id, slotn); + // normal pic - update if mouse is not over, or if there's no MouseOverImage + if (((guil->IsMouseOver == 0) || (guil->MouseOverImage < 1)) && (guil->IsPushed == 0)) + guil->CurrentImage = slotn; + guil->Image = slotn; + // update the clickable area to the same size as the graphic + guil->Width = game.SpriteInfos[slotn].Width; + guil->Height = game.SpriteInfos[slotn].Height; + + guis_need_update = 1; + FindAndRemoveButtonAnimation(guil->ParentId, guil->Id); +} + +int Button_GetPushedGraphic(GUIButton *butt) { + return butt->PushedImage; +} + +void Button_SetPushedGraphic(GUIButton *guil, int slotn) { + debug_script_log("GUI %d Button %d pushed set to slot %d", guil->ParentId, guil->Id, slotn); + + if (guil->IsPushed) + guil->CurrentImage = slotn; + guil->PushedImage = slotn; + + guis_need_update = 1; + FindAndRemoveButtonAnimation(guil->ParentId, guil->Id); +} + +int Button_GetTextColor(GUIButton *butt) { + return butt->TextColor; +} + +void Button_SetTextColor(GUIButton *butt, int newcol) { + if (butt->TextColor != newcol) { + butt->TextColor = newcol; + guis_need_update = 1; + } +} + +extern AnimatingGUIButton animbuts[MAX_ANIMATING_BUTTONS]; +extern int numAnimButs; + +// ** start animating buttons code + +// returns 1 if animation finished +int UpdateAnimatingButton(int bu) { + if (animbuts[bu].wait > 0) { + animbuts[bu].wait--; + return 0; + } + ViewStruct *tview = &views[animbuts[bu].view]; + + animbuts[bu].frame++; + + if (animbuts[bu].frame >= tview->loops[animbuts[bu].loop].numFrames) + { + if (tview->loops[animbuts[bu].loop].RunNextLoop()) { + // go to next loop + animbuts[bu].loop++; + animbuts[bu].frame = 0; + } + else if (animbuts[bu].repeat) { + animbuts[bu].frame = 0; + // multi-loop anim, go back + while ((animbuts[bu].loop > 0) && + (tview->loops[animbuts[bu].loop - 1].RunNextLoop())) + animbuts[bu].loop--; + } + else + return 1; + } + + CheckViewFrame(animbuts[bu].view, animbuts[bu].loop, animbuts[bu].frame); + + // update the button's image + guibuts[animbuts[bu].buttonid].Image = tview->loops[animbuts[bu].loop].frames[animbuts[bu].frame].pic; + guibuts[animbuts[bu].buttonid].CurrentImage = guibuts[animbuts[bu].buttonid].Image; + guibuts[animbuts[bu].buttonid].PushedImage = 0; + guibuts[animbuts[bu].buttonid].MouseOverImage = 0; + guis_need_update = 1; + + animbuts[bu].wait = animbuts[bu].speed + tview->loops[animbuts[bu].loop].frames[animbuts[bu].frame].speed; + return 0; +} + +void StopButtonAnimation(int idxn) { + numAnimButs--; + for (int aa = idxn; aa < numAnimButs; aa++) { + animbuts[aa] = animbuts[aa + 1]; + } +} + +// Returns the index of the AnimatingGUIButton object corresponding to the +// given button ID; returns -1 if no such animation exists +int FindAnimatedButton(int guin, int objn) +{ + for (int i = 0; i < numAnimButs; ++i) + { + if (animbuts[i].ongui == guin && animbuts[i].onguibut == objn) + return i; + } + return -1; +} + +void FindAndRemoveButtonAnimation(int guin, int objn) +{ + int idx = FindAnimatedButton(guin, objn); + if (idx >= 0) + StopButtonAnimation(idx); +} +// ** end animating buttons code + +void Button_Click(GUIButton *butt, int mbut) +{ + process_interface_click(butt->ParentId, butt->Id, mbut); +} + +bool Button_IsAnimating(GUIButton *butt) +{ + return FindAnimatedButton(butt->ParentId, butt->Id) >= 0; +} + +// NOTE: in correspondance to similar functions for Character & Object, +// GetView returns (view index + 1), while GetLoop and GetFrame return +// zero-based index and 0 in case of no animation. +int Button_GetAnimView(GUIButton *butt) +{ + int idx = FindAnimatedButton(butt->ParentId, butt->Id); + return idx >= 0 ? animbuts[idx].view + 1 : 0; +} + +int Button_GetAnimLoop(GUIButton *butt) +{ + int idx = FindAnimatedButton(butt->ParentId, butt->Id); + return idx >= 0 ? animbuts[idx].loop : 0; +} + +int Button_GetAnimFrame(GUIButton *butt) +{ + int idx = FindAnimatedButton(butt->ParentId, butt->Id); + return idx >= 0 ? animbuts[idx].frame : 0; +} + +int Button_GetTextAlignment(GUIButton *butt) +{ + return butt->TextAlignment; +} + +void Button_SetTextAlignment(GUIButton *butt, int align) +{ + if (butt->TextAlignment != align) { + butt->TextAlignment = (FrameAlignment)align; + guis_need_update = 1; + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// void | GUIButton *butt, int view, int loop, int speed, int repeat +RuntimeScriptValue Sc_Button_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(GUIButton, Button_Animate); +} + +// const char* | GUIButton *butt +RuntimeScriptValue Sc_Button_GetText_New(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUIButton, const char, myScriptStringImpl, Button_GetText_New); +} + +// void | GUIButton *butt, char *buffer +RuntimeScriptValue Sc_Button_GetText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(GUIButton, Button_GetText, char); +} + +// void | GUIButton *butt, const char *newtx +RuntimeScriptValue Sc_Button_SetText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(GUIButton, Button_SetText, const char); +} + +// void | GUIButton *butt, int newFont +RuntimeScriptValue Sc_Button_SetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIButton, Button_SetFont); +} + +// int | GUIButton *butt +RuntimeScriptValue Sc_Button_GetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetFont); +} + +// int | GUIButton *butt +RuntimeScriptValue Sc_Button_GetClipImage(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetClipImage); +} + +// void | GUIButton *butt, int newval +RuntimeScriptValue Sc_Button_SetClipImage(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIButton, Button_SetClipImage); +} + +// int | GUIButton *butt +RuntimeScriptValue Sc_Button_GetGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetGraphic); +} + +// int | GUIButton *butt +RuntimeScriptValue Sc_Button_GetMouseOverGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetMouseOverGraphic); +} + +// void | GUIButton *guil, int slotn +RuntimeScriptValue Sc_Button_SetMouseOverGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIButton, Button_SetMouseOverGraphic); +} + +// int | GUIButton *butt +RuntimeScriptValue Sc_Button_GetNormalGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetNormalGraphic); +} + +// void | GUIButton *guil, int slotn +RuntimeScriptValue Sc_Button_SetNormalGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIButton, Button_SetNormalGraphic); +} + +// int | GUIButton *butt +RuntimeScriptValue Sc_Button_GetPushedGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetPushedGraphic); +} + +// void | GUIButton *guil, int slotn +RuntimeScriptValue Sc_Button_SetPushedGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIButton, Button_SetPushedGraphic); +} + +// int | GUIButton *butt +RuntimeScriptValue Sc_Button_GetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetTextColor); +} + +// void | GUIButton *butt, int newcol +RuntimeScriptValue Sc_Button_SetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIButton, Button_SetTextColor); +} + +RuntimeScriptValue Sc_Button_Click(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIButton, Button_Click); +} + +RuntimeScriptValue Sc_Button_GetAnimating(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(GUIButton, Button_IsAnimating); +} + +RuntimeScriptValue Sc_Button_GetTextAlignment(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetTextAlignment); +} + +RuntimeScriptValue Sc_Button_SetTextAlignment(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIButton, Button_SetTextAlignment); +} + +RuntimeScriptValue Sc_Button_GetFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetAnimFrame); +} + +RuntimeScriptValue Sc_Button_GetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetAnimLoop); +} + +RuntimeScriptValue Sc_Button_GetView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIButton, Button_GetAnimView); +} + +void RegisterButtonAPI() +{ + ccAddExternalObjectFunction("Button::Animate^4", Sc_Button_Animate); + ccAddExternalObjectFunction("Button::Click^1", Sc_Button_Click); + ccAddExternalObjectFunction("Button::GetText^1", Sc_Button_GetText); + ccAddExternalObjectFunction("Button::SetText^1", Sc_Button_SetText); + ccAddExternalObjectFunction("Button::get_TextAlignment", Sc_Button_GetTextAlignment); + ccAddExternalObjectFunction("Button::set_TextAlignment", Sc_Button_SetTextAlignment); + ccAddExternalObjectFunction("Button::get_Animating", Sc_Button_GetAnimating); + ccAddExternalObjectFunction("Button::get_ClipImage", Sc_Button_GetClipImage); + ccAddExternalObjectFunction("Button::set_ClipImage", Sc_Button_SetClipImage); + ccAddExternalObjectFunction("Button::get_Font", Sc_Button_GetFont); + ccAddExternalObjectFunction("Button::set_Font", Sc_Button_SetFont); + ccAddExternalObjectFunction("Button::get_Frame", Sc_Button_GetFrame); + ccAddExternalObjectFunction("Button::get_Graphic", Sc_Button_GetGraphic); + ccAddExternalObjectFunction("Button::get_Loop", Sc_Button_GetLoop); + ccAddExternalObjectFunction("Button::get_MouseOverGraphic", Sc_Button_GetMouseOverGraphic); + ccAddExternalObjectFunction("Button::set_MouseOverGraphic", Sc_Button_SetMouseOverGraphic); + ccAddExternalObjectFunction("Button::get_NormalGraphic", Sc_Button_GetNormalGraphic); + ccAddExternalObjectFunction("Button::set_NormalGraphic", Sc_Button_SetNormalGraphic); + ccAddExternalObjectFunction("Button::get_PushedGraphic", Sc_Button_GetPushedGraphic); + ccAddExternalObjectFunction("Button::set_PushedGraphic", Sc_Button_SetPushedGraphic); + ccAddExternalObjectFunction("Button::get_Text", Sc_Button_GetText_New); + ccAddExternalObjectFunction("Button::set_Text", Sc_Button_SetText); + ccAddExternalObjectFunction("Button::get_TextColor", Sc_Button_GetTextColor); + ccAddExternalObjectFunction("Button::set_TextColor", Sc_Button_SetTextColor); + ccAddExternalObjectFunction("Button::get_View", Sc_Button_GetView); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Button::Animate^4", (void*)Button_Animate); + ccAddExternalFunctionForPlugin("Button::GetText^1", (void*)Button_GetText); + ccAddExternalFunctionForPlugin("Button::SetText^1", (void*)Button_SetText); + ccAddExternalFunctionForPlugin("Button::get_ClipImage", (void*)Button_GetClipImage); + ccAddExternalFunctionForPlugin("Button::set_ClipImage", (void*)Button_SetClipImage); + ccAddExternalFunctionForPlugin("Button::get_Font", (void*)Button_GetFont); + ccAddExternalFunctionForPlugin("Button::set_Font", (void*)Button_SetFont); + ccAddExternalFunctionForPlugin("Button::get_Graphic", (void*)Button_GetGraphic); + ccAddExternalFunctionForPlugin("Button::get_MouseOverGraphic", (void*)Button_GetMouseOverGraphic); + ccAddExternalFunctionForPlugin("Button::set_MouseOverGraphic", (void*)Button_SetMouseOverGraphic); + ccAddExternalFunctionForPlugin("Button::get_NormalGraphic", (void*)Button_GetNormalGraphic); + ccAddExternalFunctionForPlugin("Button::set_NormalGraphic", (void*)Button_SetNormalGraphic); + ccAddExternalFunctionForPlugin("Button::get_PushedGraphic", (void*)Button_GetPushedGraphic); + ccAddExternalFunctionForPlugin("Button::set_PushedGraphic", (void*)Button_SetPushedGraphic); + ccAddExternalFunctionForPlugin("Button::get_Text", (void*)Button_GetText_New); + ccAddExternalFunctionForPlugin("Button::set_Text", (void*)Button_SetText); + ccAddExternalFunctionForPlugin("Button::get_TextColor", (void*)Button_GetTextColor); + ccAddExternalFunctionForPlugin("Button::set_TextColor", (void*)Button_SetTextColor); +} diff --git a/engines/ags/engine/ac/button.h b/engines/ags/engine/ac/button.h new file mode 100644 index 00000000000..5f94aaea18d --- /dev/null +++ b/engines/ags/engine/ac/button.h @@ -0,0 +1,47 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__BUTTON_H +#define __AGS_EE_AC__BUTTON_H + +#include "gui/guibutton.h" + +using AGS::Common::GUIButton; + +void Button_Animate(GUIButton *butt, int view, int loop, int speed, int repeat); +const char* Button_GetText_New(GUIButton *butt); +void Button_GetText(GUIButton *butt, char *buffer); +void Button_SetText(GUIButton *butt, const char *newtx); +void Button_SetFont(GUIButton *butt, int newFont); +int Button_GetFont(GUIButton *butt); +int Button_GetClipImage(GUIButton *butt); +void Button_SetClipImage(GUIButton *butt, int newval); +int Button_GetGraphic(GUIButton *butt); +int Button_GetMouseOverGraphic(GUIButton *butt); +void Button_SetMouseOverGraphic(GUIButton *guil, int slotn); +int Button_GetNormalGraphic(GUIButton *butt); +void Button_SetNormalGraphic(GUIButton *guil, int slotn); +int Button_GetPushedGraphic(GUIButton *butt); +void Button_SetPushedGraphic(GUIButton *guil, int slotn); +int Button_GetTextColor(GUIButton *butt); +void Button_SetTextColor(GUIButton *butt, int newcol); + +int UpdateAnimatingButton(int bu); +void StopButtonAnimation(int idxn); +void FindAndRemoveButtonAnimation(int guin, int objn); + +#endif // __AGS_EE_AC__BUTTON_H diff --git a/engines/ags/engine/ac/cdaudio.cpp b/engines/ags/engine/ac/cdaudio.cpp new file mode 100644 index 00000000000..866d797f50c --- /dev/null +++ b/engines/ags/engine/ac/cdaudio.cpp @@ -0,0 +1,39 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/cdaudio.h" +#include "platform/base/agsplatformdriver.h" + +int use_cdplayer=0; +bool triedToUseCdAudioCommand = false; +int need_to_stop_cd=0; + +int init_cd_player() +{ + use_cdplayer=0; + return platform->InitializeCDPlayer(); +} + +int cd_manager(int cmdd,int datt) +{ + if (!triedToUseCdAudioCommand) + { + triedToUseCdAudioCommand = true; + init_cd_player(); + } + if (cmdd==0) return use_cdplayer; + if (use_cdplayer==0) return 0; // ignore other commands + + return platform->CDPlayerCommand(cmdd, datt); +} diff --git a/engines/ags/engine/ac/cdaudio.h b/engines/ags/engine/ac/cdaudio.h new file mode 100644 index 00000000000..4009b520fbe --- /dev/null +++ b/engines/ags/engine/ac/cdaudio.h @@ -0,0 +1,31 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__CDAUDIO_H +#define __AGS_EE_AC__CDAUDIO_H + +// CD Player functions +// flags returned with cd_getstatus +#define CDS_DRIVEOPEN 0x0001 // tray is open +#define CDS_DRIVELOCKED 0x0002 // tray locked shut by software +#define CDS_AUDIOSUPPORT 0x0010 // supports audio CDs +#define CDS_DRIVEEMPTY 0x0800 // no CD in drive + +int init_cd_player() ; +int cd_manager(int cmdd,int datt) ; + +#endif // __AGS_EE_AC__CDAUDIO_H diff --git a/engines/ags/engine/ac/character.cpp b/engines/ags/engine/ac/character.cpp new file mode 100644 index 00000000000..526c9705acf --- /dev/null +++ b/engines/ags/engine/ac/character.cpp @@ -0,0 +1,4122 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AGS Character functions +// +//============================================================================= + +#include "ac/character.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/view.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/event.h" +#include "ac/game.h" +#include "ac/global_audio.h" +#include "ac/global_character.h" +#include "ac/global_game.h" +#include "ac/global_object.h" +#include "ac/global_region.h" +#include "ac/global_room.h" +#include "ac/global_translation.h" +#include "ac/gui.h" +#include "ac/lipsync.h" +#include "ac/mouse.h" +#include "ac/object.h" +#include "ac/overlay.h" +#include "ac/properties.h" +#include "ac/room.h" +#include "ac/screenoverlay.h" +#include "ac/string.h" +#include "ac/system.h" +#include "ac/viewframe.h" +#include "ac/walkablearea.h" +#include "gui/guimain.h" +#include "ac/route_finder.h" +#include "ac/gamestate.h" +#include "debug/debug_log.h" +#include "main/game_run.h" +#include "main/update.h" +#include "ac/spritecache.h" +#include "util/string_compat.h" +#include +#include "gfx/graphicsdriver.h" +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_character.h" +#include "ac/dynobj/cc_inventory.h" +#include "script/script_runtime.h" +#include "gfx/gfx_def.h" +#include "media/audio/audio_system.h" +#include "ac/movelist.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; +extern int displayed_room,starting_room; +extern RoomStruct thisroom; +extern MoveList *mls; +extern ViewStruct*views; +extern RoomObject*objs; +extern ScriptInvItem scrInv[MAX_INV]; +extern SpriteCache spriteset; +extern Bitmap *walkable_areas_temp; +extern IGraphicsDriver *gfxDriver; +extern Bitmap **actsps; +extern int is_text_overlay; +extern int said_speech_line; +extern int said_text; +extern int our_eip; +extern CCCharacter ccDynamicCharacter; +extern CCInventory ccDynamicInv; + +//-------------------------------- + + +CharacterExtras *charextra; +CharacterInfo*playerchar; +int32_t _sc_PlayerCharPtr = 0; +int char_lowest_yp; + +// Sierra-style speech settings +int face_talking=-1,facetalkview=0,facetalkwait=0,facetalkframe=0; +int facetalkloop=0, facetalkrepeat = 0, facetalkAllowBlink = 1; +int facetalkBlinkLoop = 0; +CharacterInfo *facetalkchar = nullptr; +// Do override default portrait position during QFG4-style speech overlay update +bool facetalk_qfg4_override_placement_x = false; +bool facetalk_qfg4_override_placement_y = false; + +// lip-sync speech settings +int loops_per_character, text_lips_offset, char_speaking = -1; +int char_thinking = -1; +const char *text_lips_text = nullptr; +SpeechLipSyncLine *splipsync = nullptr; +int numLipLines = 0, curLipLine = -1, curLipLinePhoneme = 0; + +// **** CHARACTER: FUNCTIONS **** + +void Character_AddInventory(CharacterInfo *chaa, ScriptInvItem *invi, int addIndex) { + int ee; + + if (invi == nullptr) + quit("!AddInventoryToCharacter: invalid invnetory number"); + + int inum = invi->id; + + if (chaa->inv[inum] >= 32000) + quit("!AddInventory: cannot carry more than 32000 of one inventory item"); + + chaa->inv[inum]++; + + int charid = chaa->index_id; + + if (game.options[OPT_DUPLICATEINV] == 0) { + // Ensure it is only in the list once + for (ee = 0; ee < charextra[charid].invorder_count; ee++) { + if (charextra[charid].invorder[ee] == inum) { + // They already have the item, so don't add it to the list + if (chaa == playerchar) + run_on_event (GE_ADD_INV, RuntimeScriptValue().SetInt32(inum)); + return; + } + } + } + if (charextra[charid].invorder_count >= MAX_INVORDER) + quit("!Too many inventory items added, max 500 display at one time"); + + if ((addIndex == SCR_NO_VALUE) || + (addIndex >= charextra[charid].invorder_count) || + (addIndex < 0)) { + // add new item at end of list + charextra[charid].invorder[charextra[charid].invorder_count] = inum; + } + else { + // insert new item at index + for (ee = charextra[charid].invorder_count - 1; ee >= addIndex; ee--) + charextra[charid].invorder[ee + 1] = charextra[charid].invorder[ee]; + + charextra[charid].invorder[addIndex] = inum; + } + charextra[charid].invorder_count++; + guis_need_update = 1; + if (chaa == playerchar) + run_on_event (GE_ADD_INV, RuntimeScriptValue().SetInt32(inum)); + +} + +void Character_AddWaypoint(CharacterInfo *chaa, int x, int y) { + + if (chaa->room != displayed_room) + quit("!MoveCharacterPath: specified character not in current room"); + + // not already walking, so just do a normal move + if (chaa->walking <= 0) { + Character_Walk(chaa, x, y, IN_BACKGROUND, ANYWHERE); + return; + } + + MoveList *cmls = &mls[chaa->walking % TURNING_AROUND]; + if (cmls->numstage >= MAXNEEDSTAGES) + { + debug_script_warn("Character_AddWaypoint: move is too complex, cannot add any further paths"); + return; + } + + cmls->pos[cmls->numstage] = (x << 16) + y; + // They're already walking there anyway + if (cmls->pos[cmls->numstage] == cmls->pos[cmls->numstage - 1]) + return; + + calculate_move_stage (cmls, cmls->numstage-1); + cmls->numstage ++; + +} + +void Character_AnimateFrom(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction, int sframe) { + + if (direction == FORWARDS) + direction = 0; + else if (direction == BACKWARDS) + direction = 1; + else + quit("!Character.Animate: Invalid DIRECTION parameter"); + + animate_character(chaa, loop, delay, repeat, 0, direction, sframe); + + if ((blocking == BLOCKING) || (blocking == 1)) + GameLoopUntilValueIsZero(&chaa->animating); + else if ((blocking != IN_BACKGROUND) && (blocking != 0)) + quit("!Character.Animate: Invalid BLOCKING parameter"); +} + +void Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction) { + Character_AnimateFrom(chaa, loop, delay, repeat, blocking, direction, 0); +} + +void Character_ChangeRoomAutoPosition(CharacterInfo *chaa, int room, int newPos) +{ + if (chaa->index_id != game.playercharacter) + { + quit("!Character.ChangeRoomAutoPosition can only be used with the player character."); + } + + new_room_pos = newPos; + + if (new_room_pos == 0) { + // auto place on other side of screen + if (chaa->x <= thisroom.Edges.Left + 10) + new_room_pos = 2000; + else if (chaa->x >= thisroom.Edges.Right - 10) + new_room_pos = 1000; + else if (chaa->y <= thisroom.Edges.Top + 10) + new_room_pos = 3000; + else if (chaa->y >= thisroom.Edges.Bottom - 10) + new_room_pos = 4000; + + if (new_room_pos < 3000) + new_room_pos += chaa->y; + else + new_room_pos += chaa->x; + } + NewRoom(room); +} + +void Character_ChangeRoom(CharacterInfo *chaa, int room, int x, int y) { + Character_ChangeRoomSetLoop(chaa, room, x, y, SCR_NO_VALUE); +} + +void Character_ChangeRoomSetLoop(CharacterInfo *chaa, int room, int x, int y, int direction) { + + if (chaa->index_id != game.playercharacter) { + // NewRoomNPC + if ((x != SCR_NO_VALUE) && (y != SCR_NO_VALUE)) { + chaa->x = x; + chaa->y = y; + if (direction != SCR_NO_VALUE && direction>=0) chaa->loop = direction; + } + chaa->prevroom = chaa->room; + chaa->room = room; + + debug_script_log("%s moved to room %d, location %d,%d, loop %d", + chaa->scrname, room, chaa->x, chaa->y, chaa->loop); + + return; + } + + if ((x != SCR_NO_VALUE) && (y != SCR_NO_VALUE)) { + new_room_pos = 0; + + if (loaded_game_file_version <= kGameVersion_272) + { + // Set position immediately on 2.x. + chaa->x = x; + chaa->y = y; + } + else + { + // don't check X or Y bounds, so that they can do a + // walk-in animation if they want + new_room_x = x; + new_room_y = y; + if (direction != SCR_NO_VALUE) new_room_loop = direction; + } + } + + NewRoom(room); +} + + +void Character_ChangeView(CharacterInfo *chap, int vii) { + vii--; + + if ((vii < 0) || (vii >= game.numviews)) + quit("!ChangeCharacterView: invalid view number specified"); + + // if animating, but not idle view, give warning message + if ((chap->flags & CHF_FIXVIEW) && (chap->idleleft >= 0)) + debug_script_warn("Warning: ChangeCharacterView was used while the view was fixed - call ReleaseCharView first"); + + // if the idle animation is playing we should release the view + if ( chap->idleleft < 0) { + Character_UnlockView(chap); + chap->idleleft = chap->idletime; + } + + debug_script_log("%s: Change view to %d", chap->scrname, vii+1); + chap->defview = vii; + chap->view = vii; + chap->animating = 0; + chap->frame = 0; + chap->wait = 0; + chap->walkwait = 0; + charextra[chap->index_id].animwait = 0; + FindReasonableLoopForCharacter(chap); +} + +enum DirectionalLoop +{ + kDirLoop_Down = 0, + kDirLoop_Left = 1, + kDirLoop_Right = 2, + kDirLoop_Up = 3, + kDirLoop_DownRight = 4, + kDirLoop_UpRight = 5, + kDirLoop_DownLeft = 6, + kDirLoop_UpLeft = 7, + + kDirLoop_Default = kDirLoop_Down, + kDirLoop_LastOrthogonal = kDirLoop_Up, + kDirLoop_Last = kDirLoop_UpLeft, +}; + +// Internal direction-facing functions + +DirectionalLoop GetDirectionalLoop(CharacterInfo *chinfo, int x_diff, int y_diff) +{ + DirectionalLoop next_loop = kDirLoop_Left; // NOTE: default loop was Left for some reason + + const ViewStruct &chview = views[chinfo->view]; + const bool new_version = loaded_game_file_version > kGameVersion_272; + const bool has_down_loop = ((chview.numLoops > kDirLoop_Down) && (chview.loops[kDirLoop_Down].numFrames > 0)); + const bool has_up_loop = ((chview.numLoops > kDirLoop_Up) && (chview.loops[kDirLoop_Up].numFrames > 0)); + // NOTE: 3.+ games required left & right loops to be present at all times + const bool has_left_loop = new_version || + ((chview.numLoops > kDirLoop_Left) && (chview.loops[kDirLoop_Left].numFrames > 0)); + const bool has_right_loop = new_version || + ((chview.numLoops > kDirLoop_Right) && (chview.loops[kDirLoop_Right].numFrames > 0)); + const bool has_diagonal_loops = useDiagonal(chinfo) == 0; // NOTE: useDiagonal returns 0 for "true" + + const bool want_horizontal = (abs(y_diff) < abs(x_diff)) || + (new_version && (!has_down_loop || !has_up_loop) )|| + // NOTE: <= 2.72 games switch to horizontal loops only if both vertical ones are missing + (!new_version && (!has_down_loop && !has_up_loop)); + if (want_horizontal) + { + const bool want_diagonal = has_diagonal_loops && (abs(y_diff) > abs(x_diff) / 2); + if (!has_left_loop && !has_right_loop) + { + next_loop = kDirLoop_Down; + } + else if (has_right_loop && (x_diff > 0)) + { + next_loop = want_diagonal ? (y_diff < 0 ? kDirLoop_UpRight : kDirLoop_DownRight) : + kDirLoop_Right; + } + else if (has_left_loop && (x_diff <= 0)) + { + next_loop = want_diagonal ? (y_diff < 0 ? kDirLoop_UpLeft : kDirLoop_DownLeft) : + kDirLoop_Left; + } + } + else + { + const bool want_diagonal = has_diagonal_loops && (abs(x_diff) > abs(y_diff) / 2); + if (y_diff > 0 || !has_up_loop) + { + next_loop = want_diagonal ? (x_diff < 0 ? kDirLoop_DownLeft : kDirLoop_DownRight) : + kDirLoop_Down; + } + else + { + next_loop = want_diagonal ? (x_diff < 0 ? kDirLoop_UpLeft : kDirLoop_UpRight) : + kDirLoop_Up; + } + } + return next_loop; +} + +void FaceDirectionalLoop(CharacterInfo *char1, int direction, int blockingStyle) +{ + // Change facing only if the desired direction is different + if (direction != char1->loop) + { + if ((game.options[OPT_TURNTOFACELOC] != 0) && + (in_enters_screen == 0)) + { + const int no_diagonal = useDiagonal (char1); + const int highestLoopForTurning = no_diagonal != 1 ? kDirLoop_Last : kDirLoop_LastOrthogonal; + if ((char1->loop <= highestLoopForTurning)) + { + // Turn to face new direction + Character_StopMoving(char1); + if (char1->on == 1) + { + // only do the turning if the character is not hidden + // (otherwise GameLoopUntilNotMoving will never return) + start_character_turning (char1, direction, no_diagonal); + + if ((blockingStyle == BLOCKING) || (blockingStyle == 1)) + GameLoopUntilNotMoving(&char1->walking); + } + else + char1->loop = direction; + } + else + char1->loop = direction; + } + else + char1->loop = direction; + } + + char1->frame = 0; +} + +void FaceLocationXY(CharacterInfo *char1, int xx, int yy, int blockingStyle) +{ + debug_script_log("%s: Face location %d,%d", char1->scrname, xx, yy); + + const int diffrx = xx - char1->x; + const int diffry = yy - char1->y; + + if ((diffrx == 0) && (diffry == 0)) { + // FaceLocation called on their current position - do nothing + return; + } + + FaceDirectionalLoop(char1, GetDirectionalLoop(char1, diffrx, diffry), blockingStyle); +} + +// External direction-facing functions with validation + +void Character_FaceDirection(CharacterInfo *char1, int direction, int blockingStyle) +{ + if (char1 == nullptr) + quit("!FaceDirection: invalid character specified"); + + if (direction != SCR_NO_VALUE) + { + if (direction < 0 || direction > kDirLoop_Last) + quit("!FaceDirection: invalid direction specified"); + + FaceDirectionalLoop(char1, direction, blockingStyle); + } +} + +void Character_FaceLocation(CharacterInfo *char1, int xx, int yy, int blockingStyle) +{ + if (char1 == nullptr) + quit("!FaceLocation: invalid character specified"); + + FaceLocationXY(char1, xx, yy, blockingStyle); +} + +void Character_FaceObject(CharacterInfo *char1, ScriptObject *obj, int blockingStyle) { + if (obj == nullptr) + quit("!FaceObject: invalid object specified"); + + FaceLocationXY(char1, objs[obj->id].x, objs[obj->id].y, blockingStyle); +} + +void Character_FaceCharacter(CharacterInfo *char1, CharacterInfo *char2, int blockingStyle) { + if (char2 == nullptr) + quit("!FaceCharacter: invalid character specified"); + + if (char1->room != char2->room) + quit("!FaceCharacter: characters are in different rooms"); + + FaceLocationXY(char1, char2->x, char2->y, blockingStyle); +} + +void Character_FollowCharacter(CharacterInfo *chaa, CharacterInfo *tofollow, int distaway, int eagerness) { + + if ((eagerness < 0) || (eagerness > 250)) + quit("!FollowCharacterEx: invalid eagerness: must be 0-250"); + + if ((chaa->index_id == game.playercharacter) && (tofollow != nullptr) && + (tofollow->room != chaa->room)) + quit("!FollowCharacterEx: you cannot tell the player character to follow a character in another room"); + + if (tofollow != nullptr) { + debug_script_log("%s: Start following %s (dist %d, eager %d)", chaa->scrname, tofollow->scrname, distaway, eagerness); + } + else { + debug_script_log("%s: Stop following other character", chaa->scrname); + } + + if ((chaa->following >= 0) && + (chaa->followinfo == FOLLOW_ALWAYSONTOP)) { + // if this character was following always-on-top, its baseline will + // have been changed, so release it. + chaa->baseline = -1; + } + + if (tofollow == nullptr) + chaa->following = -1; + else + chaa->following = tofollow->index_id; + + chaa->followinfo=(distaway << 8) | eagerness; + + chaa->flags &= ~CHF_BEHINDSHEPHERD; + + // special case for Always On Other Character + if (distaway == FOLLOW_ALWAYSONTOP) { + chaa->followinfo = FOLLOW_ALWAYSONTOP; + if (eagerness == 1) + chaa->flags |= CHF_BEHINDSHEPHERD; + } + + if (chaa->animating & CHANIM_REPEAT) + debug_script_warn("Warning: FollowCharacter called but the sheep is currently animating looped. It may never start to follow."); + +} + +int Character_IsCollidingWithChar(CharacterInfo *char1, CharacterInfo *char2) { + if (char2 == nullptr) + quit("!AreCharactersColliding: invalid char2"); + + if (char1->room != char2->room) return 0; // not colliding + + if ((char1->y > char2->y - 5) && (char1->y < char2->y + 5)) ; + else return 0; + + int w1 = game_to_data_coord(GetCharacterWidth(char1->index_id)); + int w2 = game_to_data_coord(GetCharacterWidth(char2->index_id)); + + int xps1=char1->x - w1/2; + int xps2=char2->x - w2/2; + + if ((xps1 >= xps2 - w1) & (xps1 <= xps2 + w2)) return 1; + return 0; +} + +int Character_IsCollidingWithObject(CharacterInfo *chin, ScriptObject *objid) { + if (objid == nullptr) + quit("!AreCharObjColliding: invalid object number"); + + if (chin->room != displayed_room) + return 0; + if (objs[objid->id].on != 1) + return 0; + + Bitmap *checkblk = GetObjectImage(objid->id, nullptr); + int objWidth = checkblk->GetWidth(); + int objHeight = checkblk->GetHeight(); + int o1x = objs[objid->id].x; + int o1y = objs[objid->id].y - game_to_data_coord(objHeight); + + Bitmap *charpic = GetCharacterImage(chin->index_id, nullptr); + + int charWidth = charpic->GetWidth(); + int charHeight = charpic->GetHeight(); + int o2x = chin->x - game_to_data_coord(charWidth) / 2; + int o2y = chin->get_effective_y() - 5; // only check feet + + if ((o2x >= o1x - game_to_data_coord(charWidth)) && + (o2x <= o1x + game_to_data_coord(objWidth)) && + (o2y >= o1y - 8) && + (o2y <= o1y + game_to_data_coord(objHeight))) { + // the character's feet are on the object + if (game.options[OPT_PIXPERFECT] == 0) + return 1; + // check if they're on a transparent bit of the object + int stxp = data_to_game_coord(o2x - o1x); + int styp = data_to_game_coord(o2y - o1y); + int maskcol = checkblk->GetMaskColor (); + int maskcolc = charpic->GetMaskColor (); + int thispix, thispixc; + // check each pixel of the object along the char's feet + for (int i = 0; i < charWidth; i += get_fixed_pixel_size(1)) { + for (int j = 0; j < get_fixed_pixel_size(6); j += get_fixed_pixel_size(1)) { + thispix = my_getpixel(checkblk, i + stxp, j + styp); + thispixc = my_getpixel(charpic, i, j + (charHeight - get_fixed_pixel_size(5))); + + if ((thispix != -1) && (thispix != maskcol) && + (thispixc != -1) && (thispixc != maskcolc)) + return 1; + } + } + + } + return 0; +} + +bool Character_IsInteractionAvailable(CharacterInfo *cchar, int mood) { + + play.check_interaction_only = 1; + RunCharacterInteraction(cchar->index_id, mood); + int ciwas = play.check_interaction_only; + play.check_interaction_only = 0; + return (ciwas == 2); +} + +void Character_LockView(CharacterInfo *chap, int vii) { + Character_LockViewEx(chap, vii, STOP_MOVING); +} + +void Character_LockViewEx(CharacterInfo *chap, int vii, int stopMoving) { + + if ((vii < 1) || (vii > game.numviews)) { + quitprintf("!SetCharacterView: invalid view number (You said %d, max is %d)", vii, game.numviews); + } + vii--; + + debug_script_log("%s: View locked to %d", chap->scrname, vii+1); + if (chap->idleleft < 0) { + Character_UnlockView(chap); + chap->idleleft = chap->idletime; + } + if (stopMoving != KEEP_MOVING) + { + Character_StopMoving(chap); + } + chap->view=vii; + chap->animating=0; + FindReasonableLoopForCharacter(chap); + chap->frame=0; + chap->wait=0; + chap->flags|=CHF_FIXVIEW; + chap->pic_xoffs = 0; + chap->pic_yoffs = 0; +} + +void Character_LockViewAligned_Old(CharacterInfo *chap, int vii, int loop, int align) { + Character_LockViewAlignedEx(chap, vii, loop, ConvertLegacyScriptAlignment((LegacyScriptAlignment)align), STOP_MOVING); +} + +void Character_LockViewAlignedEx_Old(CharacterInfo *chap, int vii, int loop, int align, int stopMoving) { + Character_LockViewAlignedEx(chap, vii, loop, ConvertLegacyScriptAlignment((LegacyScriptAlignment)align), stopMoving); +} + +void Character_LockViewAligned(CharacterInfo *chap, int vii, int loop, int align) { + Character_LockViewAlignedEx(chap, vii, loop, align, STOP_MOVING); +} + +void Character_LockViewAlignedEx(CharacterInfo *chap, int vii, int loop, int align, int stopMoving) { + if (chap->view < 0) + quit("!SetCharacterLoop: character has invalid old view number"); + + int sppic = views[chap->view].loops[chap->loop].frames[chap->frame].pic; + int leftSide = data_to_game_coord(chap->x) - game.SpriteInfos[sppic].Width / 2; + + Character_LockViewEx(chap, vii, stopMoving); + + if ((loop < 0) || (loop >= views[chap->view].numLoops)) + quit("!SetCharacterViewEx: invalid loop specified"); + + chap->loop = loop; + chap->frame = 0; + int newpic = views[chap->view].loops[chap->loop].frames[chap->frame].pic; + int newLeft = data_to_game_coord(chap->x) - game.SpriteInfos[newpic].Width / 2; + int xdiff = 0; + + if (align & kMAlignLeft) + xdiff = leftSide - newLeft; + else if (align & kMAlignHCenter) + xdiff = 0; + else if (align & kMAlignRight) + xdiff = (leftSide + game.SpriteInfos[sppic].Width) - (newLeft + game.SpriteInfos[newpic].Width); + else + quit("!SetCharacterViewEx: invalid alignment type specified"); + + chap->pic_xoffs = xdiff; + chap->pic_yoffs = 0; +} + +void Character_LockViewFrame(CharacterInfo *chaa, int view, int loop, int frame) { + Character_LockViewFrameEx(chaa, view, loop, frame, STOP_MOVING); +} + +void Character_LockViewFrameEx(CharacterInfo *chaa, int view, int loop, int frame, int stopMoving) { + + Character_LockViewEx(chaa, view, stopMoving); + + view--; + if ((loop < 0) || (loop >= views[view].numLoops)) + quit("!SetCharacterFrame: invalid loop specified"); + if ((frame < 0) || (frame >= views[view].loops[loop].numFrames)) + quit("!SetCharacterFrame: invalid frame specified"); + + chaa->loop = loop; + chaa->frame = frame; +} + +void Character_LockViewOffset(CharacterInfo *chap, int vii, int xoffs, int yoffs) { + Character_LockViewOffsetEx(chap, vii, xoffs, yoffs, STOP_MOVING); +} + +void Character_LockViewOffsetEx(CharacterInfo *chap, int vii, int xoffs, int yoffs, int stopMoving) { + Character_LockViewEx(chap, vii, stopMoving); + + // This function takes offsets in real game coordinates as opposed to script coordinates + defgame_to_finalgame_coords(xoffs, yoffs); + chap->pic_xoffs = xoffs; + chap->pic_yoffs = yoffs; +} + +void Character_LoseInventory(CharacterInfo *chap, ScriptInvItem *invi) { + + if (invi == nullptr) + quit("!LoseInventoryFromCharacter: invalid invnetory number"); + + int inum = invi->id; + + if (chap->inv[inum] > 0) + chap->inv[inum]--; + + if ((chap->activeinv == inum) & (chap->inv[inum] < 1)) { + chap->activeinv = -1; + if ((chap == playerchar) && (GetCursorMode() == MODE_USE)) + set_cursor_mode(0); + } + + int charid = chap->index_id; + + if ((chap->inv[inum] == 0) || (game.options[OPT_DUPLICATEINV] > 0)) { + int xx,tt; + for (xx = 0; xx < charextra[charid].invorder_count; xx++) { + if (charextra[charid].invorder[xx] == inum) { + charextra[charid].invorder_count--; + for (tt = xx; tt < charextra[charid].invorder_count; tt++) + charextra[charid].invorder[tt] = charextra[charid].invorder[tt+1]; + break; + } + } + } + guis_need_update = 1; + + if (chap == playerchar) + run_on_event (GE_LOSE_INV, RuntimeScriptValue().SetInt32(inum)); +} + +void Character_PlaceOnWalkableArea(CharacterInfo *chap) +{ + if (displayed_room < 0) + quit("!Character.PlaceOnWalkableArea: no room is currently loaded"); + + find_nearest_walkable_area(&chap->x, &chap->y); +} + +void Character_RemoveTint(CharacterInfo *chaa) { + + if (chaa->flags & (CHF_HASTINT | CHF_HASLIGHT)) { + debug_script_log("Un-tint %s", chaa->scrname); + chaa->flags &= ~(CHF_HASTINT | CHF_HASLIGHT); + } + else { + debug_script_warn("Character.RemoveTint called but character was not tinted"); + } +} + +int Character_GetHasExplicitTint_Old(CharacterInfo *ch) +{ + return ch->has_explicit_tint() || ch->has_explicit_light(); +} + +int Character_GetHasExplicitTint(CharacterInfo *ch) +{ + return ch->has_explicit_tint(); +} + +void Character_Say(CharacterInfo *chaa, const char *text) { + _DisplaySpeechCore(chaa->index_id, text); +} + +void Character_SayAt(CharacterInfo *chaa, int x, int y, int width, const char *texx) { + + DisplaySpeechAt(x, y, width, chaa->index_id, (char*)texx); +} + +ScriptOverlay* Character_SayBackground(CharacterInfo *chaa, const char *texx) { + + int ovltype = DisplaySpeechBackground(chaa->index_id, (char*)texx); + int ovri = find_overlay_of_type(ovltype); + if (ovri<0) + quit("!SayBackground internal error: no overlay"); + + // Convert the overlay ID to an Overlay object + ScriptOverlay *scOver = new ScriptOverlay(); + scOver->overlayId = ovltype; + scOver->borderHeight = 0; + scOver->borderWidth = 0; + scOver->isBackgroundSpeech = 1; + int handl = ccRegisterManagedObject(scOver, scOver); + screenover[ovri].associatedOverlayHandle = handl; + + return scOver; +} + +void Character_SetAsPlayer(CharacterInfo *chaa) { + + // Set to same character, so ignore. + // But only on versions > 2.61. The relevant entry in the 2.62 changelog is: + // - Fixed SetPlayerCharacter to do nothing at all if you pass the current + // player character to it (previously it was resetting the inventory layout) + if ((loaded_game_file_version > kGameVersion_261) && (game.playercharacter == chaa->index_id)) + return; + + setup_player_character(chaa->index_id); + + //update_invorder(); + + debug_script_log("%s is new player character", playerchar->scrname); + + // Within game_start, return now + if (displayed_room < 0) + return; + + // Ignore invalid room numbers for the character and just place him in + // the current room for 2.x. Following script calls to NewRoom() will + // make sure this still works as intended. + if ((loaded_game_file_version <= kGameVersion_272) && (playerchar->room < 0)) + playerchar->room = displayed_room; + + if (displayed_room != playerchar->room) + NewRoom(playerchar->room); + else // make sure it doesn't run the region interactions + play.player_on_region = GetRegionIDAtRoom(playerchar->x, playerchar->y); + + if ((playerchar->activeinv >= 0) && (playerchar->inv[playerchar->activeinv] < 1)) + playerchar->activeinv = -1; + + // They had inv selected, so change the cursor + if (cur_mode == MODE_USE) { + if (playerchar->activeinv < 0) + SetNextCursor (); + else + SetActiveInventory (playerchar->activeinv); + } + +} + + +void Character_SetIdleView(CharacterInfo *chaa, int iview, int itime) { + + if (iview == 1) + quit("!SetCharacterIdle: view 1 cannot be used as an idle view, sorry."); + + // if an idle anim is currently playing, release it + if (chaa->idleleft < 0) + Character_UnlockView(chaa); + + chaa->idleview = iview - 1; + // make sure they don't appear idle while idle anim is disabled + if (iview < 1) + itime = 10; + chaa->idletime = itime; + chaa->idleleft = itime; + + // if not currently animating, reset the wait counter + if ((chaa->animating == 0) && (chaa->walking == 0)) + chaa->wait = 0; + + if (iview >= 1) { + debug_script_log("Set %s idle view to %d (time %d)", chaa->scrname, iview, itime); + } + else { + debug_script_log("%s idle view disabled", chaa->scrname); + } + if (chaa->flags & CHF_FIXVIEW) { + debug_script_warn("SetCharacterIdle called while character view locked with SetCharacterView; idle ignored"); + debug_script_log("View locked, idle will not kick in until Released"); + } + // if they switch to a swimming animation, kick it off immediately + if (itime == 0) + charextra[chaa->index_id].process_idle_this_time = 1; + +} + +bool Character_GetHasExplicitLight(CharacterInfo *ch) +{ + return ch->has_explicit_light(); +} + +int Character_GetLightLevel(CharacterInfo *ch) +{ + return ch->has_explicit_light() ? charextra[ch->index_id].tint_light : 0; +} + +void Character_SetLightLevel(CharacterInfo *chaa, int light_level) +{ + light_level = Math::Clamp(light_level, -100, 100); + + charextra[chaa->index_id].tint_light = light_level; + chaa->flags &= ~CHF_HASTINT; + chaa->flags |= CHF_HASLIGHT; +} + +int Character_GetTintRed(CharacterInfo *ch) +{ + return ch->has_explicit_tint() ? charextra[ch->index_id].tint_r : 0; +} + +int Character_GetTintGreen(CharacterInfo *ch) +{ + return ch->has_explicit_tint() ? charextra[ch->index_id].tint_g : 0; +} + +int Character_GetTintBlue(CharacterInfo *ch) +{ + return ch->has_explicit_tint() ? charextra[ch->index_id].tint_b : 0; +} + +int Character_GetTintSaturation(CharacterInfo *ch) +{ + return ch->has_explicit_tint() ? charextra[ch->index_id].tint_level : 0; +} + +int Character_GetTintLuminance(CharacterInfo *ch) +{ + return ch->has_explicit_tint() ? ((charextra[ch->index_id].tint_light * 10) / 25) : 0; +} + +void Character_SetOption(CharacterInfo *chaa, int flag, int yesorno) { + + if ((yesorno < 0) || (yesorno > 1)) + quit("!SetCharacterProperty: last parameter must be 0 or 1"); + + if (flag & CHF_MANUALSCALING) { + // backwards compatibility fix + Character_SetIgnoreScaling(chaa, yesorno); + } + else { + chaa->flags &= ~flag; + if (yesorno) + chaa->flags |= flag; + } + +} + +void Character_SetSpeed(CharacterInfo *chaa, int xspeed, int yspeed) { + + if ((xspeed == 0) || (xspeed > 50) || (yspeed == 0) || (yspeed > 50)) + quit("!SetCharacterSpeedEx: invalid speed value"); + if (chaa->walking) + { + debug_script_warn("Character_SetSpeed: cannot change speed while walking"); + return; + } + + chaa->walkspeed = xspeed; + + if (yspeed == xspeed) + chaa->walkspeed_y = UNIFORM_WALK_SPEED; + else + chaa->walkspeed_y = yspeed; +} + + +void Character_StopMoving(CharacterInfo *charp) { + + int chaa = charp->index_id; + if (chaa == play.skip_until_char_stops) + EndSkippingUntilCharStops(); + + if (charextra[chaa].xwas != INVALID_X) { + charp->x = charextra[chaa].xwas; + charp->y = charextra[chaa].ywas; + charextra[chaa].xwas = INVALID_X; + } + if ((charp->walking > 0) && (charp->walking < TURNING_AROUND)) { + // if it's not a MoveCharDirect, make sure they end up on a walkable area + if ((mls[charp->walking].direct == 0) && (charp->room == displayed_room)) + Character_PlaceOnWalkableArea(charp); + + debug_script_log("%s: stop moving", charp->scrname); + + charp->idleleft = charp->idletime; + // restart the idle animation straight away + charextra[chaa].process_idle_this_time = 1; + } + if (charp->walking) { + // If the character is currently moving, stop them and reset their frame + charp->walking = 0; + if ((charp->flags & CHF_MOVENOTWALK) == 0) + charp->frame = 0; + } +} + +void Character_Tint(CharacterInfo *chaa, int red, int green, int blue, int opacity, int luminance) { + if ((red < 0) || (green < 0) || (blue < 0) || + (red > 255) || (green > 255) || (blue > 255) || + (opacity < 0) || (opacity > 100) || + (luminance < 0) || (luminance > 100)) + quit("!Character.Tint: invalid parameter. R,G,B must be 0-255, opacity & luminance 0-100"); + + debug_script_log("Set %s tint RGB(%d,%d,%d) %d%%", chaa->scrname, red, green, blue, opacity); + + charextra[chaa->index_id].tint_r = red; + charextra[chaa->index_id].tint_g = green; + charextra[chaa->index_id].tint_b = blue; + charextra[chaa->index_id].tint_level = opacity; + charextra[chaa->index_id].tint_light = (luminance * 25) / 10; + chaa->flags &= ~CHF_HASLIGHT; + chaa->flags |= CHF_HASTINT; +} + +void Character_Think(CharacterInfo *chaa, const char *text) { + _DisplayThoughtCore(chaa->index_id, text); +} + +void Character_UnlockView(CharacterInfo *chaa) { + Character_UnlockViewEx(chaa, STOP_MOVING); +} + +void Character_UnlockViewEx(CharacterInfo *chaa, int stopMoving) { + if (chaa->flags & CHF_FIXVIEW) { + debug_script_log("%s: Released view back to default", chaa->scrname); + } + chaa->flags &= ~CHF_FIXVIEW; + chaa->view = chaa->defview; + chaa->frame = 0; + if (stopMoving != KEEP_MOVING) + { + Character_StopMoving(chaa); + } + if (chaa->view >= 0) { + int maxloop = views[chaa->view].numLoops; + if (((chaa->flags & CHF_NODIAGONAL)!=0) && (maxloop > 4)) + maxloop = 4; + FindReasonableLoopForCharacter(chaa); + } + chaa->animating = 0; + chaa->idleleft = chaa->idletime; + chaa->pic_xoffs = 0; + chaa->pic_yoffs = 0; + // restart the idle animation straight away + charextra[chaa->index_id].process_idle_this_time = 1; + +} + + +void Character_Walk(CharacterInfo *chaa, int x, int y, int blocking, int direct) +{ + walk_or_move_character(chaa, x, y, blocking, direct, true); +} + +void Character_Move(CharacterInfo *chaa, int x, int y, int blocking, int direct) +{ + walk_or_move_character(chaa, x, y, blocking, direct, false); +} + +void Character_WalkStraight(CharacterInfo *chaa, int xx, int yy, int blocking) { + + if (chaa->room != displayed_room) + quit("!MoveCharacterStraight: specified character not in current room"); + + Character_StopMoving(chaa); + int movetox = xx, movetoy = yy; + + set_wallscreen(prepare_walkable_areas(chaa->index_id)); + + int fromXLowres = room_to_mask_coord(chaa->x); + int fromYLowres = room_to_mask_coord(chaa->y); + int toXLowres = room_to_mask_coord(xx); + int toYLowres = room_to_mask_coord(yy); + + if (!can_see_from(fromXLowres, fromYLowres, toXLowres, toYLowres)) { + int lastcx, lastcy; + get_lastcpos(lastcx, lastcy); + movetox = mask_to_room_coord(lastcx); + movetoy = mask_to_room_coord(lastcy); + } + + walk_character(chaa->index_id, movetox, movetoy, 1, true); + + if ((blocking == BLOCKING) || (blocking == 1)) + GameLoopUntilNotMoving(&chaa->walking); + else if ((blocking != IN_BACKGROUND) && (blocking != 0)) + quit("!Character.Walk: Blocking must be BLOCKING or IN_BACKGRUOND"); + +} + +void Character_RunInteraction(CharacterInfo *chaa, int mood) { + + RunCharacterInteraction(chaa->index_id, mood); +} + + + +// **** CHARACTER: PROPERTIES **** + +int Character_GetProperty(CharacterInfo *chaa, const char *property) { + + return get_int_property(game.charProps[chaa->index_id], play.charProps[chaa->index_id], property); + +} +void Character_GetPropertyText(CharacterInfo *chaa, const char *property, char *bufer) { + get_text_property(game.charProps[chaa->index_id], play.charProps[chaa->index_id], property, bufer); +} +const char* Character_GetTextProperty(CharacterInfo *chaa, const char *property) { + return get_text_property_dynamic_string(game.charProps[chaa->index_id], play.charProps[chaa->index_id], property); +} + +bool Character_SetProperty(CharacterInfo *chaa, const char *property, int value) +{ + return set_int_property(play.charProps[chaa->index_id], property, value); +} + +bool Character_SetTextProperty(CharacterInfo *chaa, const char *property, const char *value) +{ + return set_text_property(play.charProps[chaa->index_id], property, value); +} + +ScriptInvItem* Character_GetActiveInventory(CharacterInfo *chaa) { + + if (chaa->activeinv <= 0) + return nullptr; + + return &scrInv[chaa->activeinv]; +} + +void Character_SetActiveInventory(CharacterInfo *chaa, ScriptInvItem* iit) { + guis_need_update = 1; + + if (iit == nullptr) { + chaa->activeinv = -1; + + if (chaa->index_id == game.playercharacter) { + + if (GetCursorMode()==MODE_USE) + set_cursor_mode(0); + } + return; + } + + if (chaa->inv[iit->id] < 1) + { + debug_script_warn("SetActiveInventory: character doesn't have any of that inventory"); + return; + } + + chaa->activeinv = iit->id; + + if (chaa->index_id == game.playercharacter) { + // if it's the player character, update mouse cursor + update_inv_cursor(iit->id); + set_cursor_mode(MODE_USE); + } +} + +int Character_GetAnimating(CharacterInfo *chaa) { + if (chaa->animating) + return 1; + return 0; +} + +int Character_GetAnimationSpeed(CharacterInfo *chaa) { + return chaa->animspeed; +} + +void Character_SetAnimationSpeed(CharacterInfo *chaa, int newval) { + + chaa->animspeed = newval; +} + +int Character_GetBaseline(CharacterInfo *chaa) { + + if (chaa->baseline < 1) + return 0; + + return chaa->baseline; +} + +void Character_SetBaseline(CharacterInfo *chaa, int basel) { + + chaa->baseline = basel; +} + +int Character_GetBlinkInterval(CharacterInfo *chaa) { + + return chaa->blinkinterval; +} + +void Character_SetBlinkInterval(CharacterInfo *chaa, int interval) { + + if (interval < 0) + quit("!SetCharacterBlinkView: invalid blink interval"); + + chaa->blinkinterval = interval; + + if (chaa->blinktimer > 0) + chaa->blinktimer = chaa->blinkinterval; +} + +int Character_GetBlinkView(CharacterInfo *chaa) { + + return chaa->blinkview + 1; +} + +void Character_SetBlinkView(CharacterInfo *chaa, int vii) { + + if (((vii < 2) || (vii > game.numviews)) && (vii != -1)) + quit("!SetCharacterBlinkView: invalid view number"); + + chaa->blinkview = vii - 1; +} + +int Character_GetBlinkWhileThinking(CharacterInfo *chaa) { + if (chaa->flags & CHF_NOBLINKANDTHINK) + return 0; + return 1; +} + +void Character_SetBlinkWhileThinking(CharacterInfo *chaa, int yesOrNo) { + chaa->flags &= ~CHF_NOBLINKANDTHINK; + if (yesOrNo == 0) + chaa->flags |= CHF_NOBLINKANDTHINK; +} + +int Character_GetBlockingHeight(CharacterInfo *chaa) { + + return chaa->blocking_height; +} + +void Character_SetBlockingHeight(CharacterInfo *chaa, int hit) { + + chaa->blocking_height = hit; +} + +int Character_GetBlockingWidth(CharacterInfo *chaa) { + + return chaa->blocking_width; +} + +void Character_SetBlockingWidth(CharacterInfo *chaa, int wid) { + + chaa->blocking_width = wid; +} + +int Character_GetDiagonalWalking(CharacterInfo *chaa) { + + if (chaa->flags & CHF_NODIAGONAL) + return 0; + return 1; +} + +void Character_SetDiagonalWalking(CharacterInfo *chaa, int yesorno) { + + chaa->flags &= ~CHF_NODIAGONAL; + if (!yesorno) + chaa->flags |= CHF_NODIAGONAL; +} + +int Character_GetClickable(CharacterInfo *chaa) { + + if (chaa->flags & CHF_NOINTERACT) + return 0; + return 1; +} + +void Character_SetClickable(CharacterInfo *chaa, int clik) { + + chaa->flags &= ~CHF_NOINTERACT; + // if they don't want it clickable, set the relevant bit + if (clik == 0) + chaa->flags |= CHF_NOINTERACT; +} + +int Character_GetID(CharacterInfo *chaa) { + + return chaa->index_id; + +} + +int Character_GetFrame(CharacterInfo *chaa) { + return chaa->frame; +} + +void Character_SetFrame(CharacterInfo *chaa, int newval) { + chaa->frame = newval; +} + +int Character_GetIdleView(CharacterInfo *chaa) { + + if (chaa->idleview < 1) + return -1; + + return chaa->idleview + 1; +} + +int Character_GetIInventoryQuantity(CharacterInfo *chaa, int index) { + if ((index < 1) || (index >= game.numinvitems)) + quitprintf("!Character.InventoryQuantity: invalid inventory index %d", index); + + return chaa->inv[index]; +} + +int Character_HasInventory(CharacterInfo *chaa, ScriptInvItem *invi) +{ + if (invi == nullptr) + quit("!Character.HasInventory: NULL inventory item supplied"); + + return (chaa->inv[invi->id] > 0) ? 1 : 0; +} + +void Character_SetIInventoryQuantity(CharacterInfo *chaa, int index, int quant) { + if ((index < 1) || (index >= game.numinvitems)) + quitprintf("!Character.InventoryQuantity: invalid inventory index %d", index); + + if ((quant < 0) || (quant > 32000)) + quitprintf("!Character.InventoryQuantity: invalid quantity %d", quant); + + chaa->inv[index] = quant; +} + +int Character_GetIgnoreLighting(CharacterInfo *chaa) { + + if (chaa->flags & CHF_NOLIGHTING) + return 1; + return 0; +} + +void Character_SetIgnoreLighting(CharacterInfo *chaa, int yesorno) { + + chaa->flags &= ~CHF_NOLIGHTING; + if (yesorno) + chaa->flags |= CHF_NOLIGHTING; +} + +int Character_GetIgnoreScaling(CharacterInfo *chaa) { + + if (chaa->flags & CHF_MANUALSCALING) + return 1; + return 0; +} + +void Character_SetIgnoreScaling(CharacterInfo *chaa, int yesorno) { + + if (yesorno) { + // when setting IgnoreScaling to 1, should reset zoom level + // like it used to in pre-2.71 + charextra[chaa->index_id].zoom = 100; + } + Character_SetManualScaling(chaa, yesorno); +} + +void Character_SetManualScaling(CharacterInfo *chaa, int yesorno) { + + chaa->flags &= ~CHF_MANUALSCALING; + if (yesorno) + chaa->flags |= CHF_MANUALSCALING; +} + +int Character_GetIgnoreWalkbehinds(CharacterInfo *chaa) { + + if (chaa->flags & CHF_NOWALKBEHINDS) + return 1; + return 0; +} + +void Character_SetIgnoreWalkbehinds(CharacterInfo *chaa, int yesorno) { + if (game.options[OPT_BASESCRIPTAPI] >= kScriptAPI_v350) + debug_script_warn("IgnoreWalkbehinds is not recommended for use, consider other solutions"); + chaa->flags &= ~CHF_NOWALKBEHINDS; + if (yesorno) + chaa->flags |= CHF_NOWALKBEHINDS; +} + +int Character_GetMovementLinkedToAnimation(CharacterInfo *chaa) { + + if (chaa->flags & CHF_ANTIGLIDE) + return 1; + return 0; +} + +void Character_SetMovementLinkedToAnimation(CharacterInfo *chaa, int yesorno) { + + chaa->flags &= ~CHF_ANTIGLIDE; + if (yesorno) + chaa->flags |= CHF_ANTIGLIDE; +} + +int Character_GetLoop(CharacterInfo *chaa) { + return chaa->loop; +} + +void Character_SetLoop(CharacterInfo *chaa, int newval) { + if ((newval < 0) || (newval >= views[chaa->view].numLoops)) + quit("!Character.Loop: invalid loop number for this view"); + + chaa->loop = newval; + + if (chaa->frame >= views[chaa->view].loops[chaa->loop].numFrames) + chaa->frame = 0; +} + +int Character_GetMoving(CharacterInfo *chaa) { + if (chaa->walking) + return 1; + return 0; +} + +int Character_GetDestinationX(CharacterInfo *chaa) { + if (chaa->walking) { + MoveList *cmls = &mls[chaa->walking % TURNING_AROUND]; + return cmls->pos[cmls->numstage - 1] >> 16; + } + else + return chaa->x; +} + +int Character_GetDestinationY(CharacterInfo *chaa) { + if (chaa->walking) { + MoveList *cmls = &mls[chaa->walking % TURNING_AROUND]; + return cmls->pos[cmls->numstage - 1] & 0xFFFF; + } + else + return chaa->y; +} + +const char* Character_GetName(CharacterInfo *chaa) { + return CreateNewScriptString(chaa->name); +} + +void Character_SetName(CharacterInfo *chaa, const char *newName) { + strncpy(chaa->name, newName, 40); + chaa->name[39] = 0; +} + +int Character_GetNormalView(CharacterInfo *chaa) { + return chaa->defview + 1; +} + +int Character_GetPreviousRoom(CharacterInfo *chaa) { + return chaa->prevroom; +} + +int Character_GetRoom(CharacterInfo *chaa) { + return chaa->room; +} + + +int Character_GetScaleMoveSpeed(CharacterInfo *chaa) { + + if (chaa->flags & CHF_SCALEMOVESPEED) + return 1; + return 0; +} + +void Character_SetScaleMoveSpeed(CharacterInfo *chaa, int yesorno) { + + if ((yesorno < 0) || (yesorno > 1)) + quit("Character.ScaleMoveSpeed: value must be true or false (1 or 0)"); + + chaa->flags &= ~CHF_SCALEMOVESPEED; + if (yesorno) + chaa->flags |= CHF_SCALEMOVESPEED; +} + +int Character_GetScaleVolume(CharacterInfo *chaa) { + + if (chaa->flags & CHF_SCALEVOLUME) + return 1; + return 0; +} + +void Character_SetScaleVolume(CharacterInfo *chaa, int yesorno) { + + if ((yesorno < 0) || (yesorno > 1)) + quit("Character.ScaleVolume: value must be true or false (1 or 0)"); + + chaa->flags &= ~CHF_SCALEVOLUME; + if (yesorno) + chaa->flags |= CHF_SCALEVOLUME; +} + +int Character_GetScaling(CharacterInfo *chaa) { + return charextra[chaa->index_id].zoom; +} + +void Character_SetScaling(CharacterInfo *chaa, int zoomlevel) { + + if ((chaa->flags & CHF_MANUALSCALING) == 0) + { + debug_script_warn("Character.Scaling: cannot set property unless ManualScaling is enabled"); + return; + } + if ((zoomlevel < 5) || (zoomlevel > 200)) + quit("!Character.Scaling: scaling level must be between 5 and 200%"); + + charextra[chaa->index_id].zoom = zoomlevel; +} + +int Character_GetSolid(CharacterInfo *chaa) { + + if (chaa->flags & CHF_NOBLOCKING) + return 0; + return 1; +} + +void Character_SetSolid(CharacterInfo *chaa, int yesorno) { + + chaa->flags &= ~CHF_NOBLOCKING; + if (!yesorno) + chaa->flags |= CHF_NOBLOCKING; +} + +int Character_GetSpeaking(CharacterInfo *chaa) { + if (get_character_currently_talking() == chaa->index_id) + return 1; + + return 0; +} + +int Character_GetSpeechColor(CharacterInfo *chaa) { + + return chaa->talkcolor; +} + +void Character_SetSpeechColor(CharacterInfo *chaa, int ncol) { + + chaa->talkcolor = ncol; +} + +void Character_SetSpeechAnimationDelay(CharacterInfo *chaa, int newDelay) +{ + if (game.options[OPT_GLOBALTALKANIMSPD] != 0) + { + debug_script_warn("Character.SpeechAnimationDelay cannot be set when global speech animation speed is enabled"); + return; + } + + chaa->speech_anim_speed = newDelay; +} + +int Character_GetSpeechView(CharacterInfo *chaa) { + + return chaa->talkview + 1; +} + +void Character_SetSpeechView(CharacterInfo *chaa, int vii) { + if (vii == -1) { + chaa->talkview = -1; + return; + } + + if ((vii < 1) || (vii > game.numviews)) + quit("!SetCharacterSpeechView: invalid view number"); + + chaa->talkview = vii - 1; +} + +bool Character_GetThinking(CharacterInfo *chaa) +{ + return char_thinking == chaa->index_id; +} + +int Character_GetThinkingFrame(CharacterInfo *chaa) +{ + if (char_thinking == chaa->index_id) + return chaa->thinkview > 0 ? chaa->frame : -1; + + debug_script_warn("Character.ThinkingFrame: character is not currently thinking"); + return -1; +} + +int Character_GetThinkView(CharacterInfo *chaa) { + + return chaa->thinkview + 1; +} + +void Character_SetThinkView(CharacterInfo *chaa, int vii) { + if (((vii < 2) || (vii > game.numviews)) && (vii != -1)) + quit("!SetCharacterThinkView: invalid view number"); + + chaa->thinkview = vii - 1; +} + +int Character_GetTransparency(CharacterInfo *chaa) { + + return GfxDef::LegacyTrans255ToTrans100(chaa->transparency); +} + +void Character_SetTransparency(CharacterInfo *chaa, int trans) { + + if ((trans < 0) || (trans > 100)) + quit("!SetCharTransparent: transparency value must be between 0 and 100"); + + chaa->transparency = GfxDef::Trans100ToLegacyTrans255(trans); +} + +int Character_GetTurnBeforeWalking(CharacterInfo *chaa) { + + if (chaa->flags & CHF_NOTURNING) + return 0; + return 1; +} + +void Character_SetTurnBeforeWalking(CharacterInfo *chaa, int yesorno) { + + chaa->flags &= ~CHF_NOTURNING; + if (!yesorno) + chaa->flags |= CHF_NOTURNING; +} + +int Character_GetView(CharacterInfo *chaa) { + return chaa->view + 1; +} + +int Character_GetWalkSpeedX(CharacterInfo *chaa) { + return chaa->walkspeed; +} + +int Character_GetWalkSpeedY(CharacterInfo *chaa) { + if (chaa->walkspeed_y != UNIFORM_WALK_SPEED) + return chaa->walkspeed_y; + + return chaa->walkspeed; +} + +int Character_GetX(CharacterInfo *chaa) { + return chaa->x; +} + +void Character_SetX(CharacterInfo *chaa, int newval) { + chaa->x = newval; +} + +int Character_GetY(CharacterInfo *chaa) { + return chaa->y; +} + +void Character_SetY(CharacterInfo *chaa, int newval) { + chaa->y = newval; +} + +int Character_GetZ(CharacterInfo *chaa) { + return chaa->z; +} + +void Character_SetZ(CharacterInfo *chaa, int newval) { + chaa->z = newval; +} + +extern int char_speaking; + +int Character_GetSpeakingFrame(CharacterInfo *chaa) { + + if ((face_talking >= 0) && (facetalkrepeat)) + { + if (facetalkchar->index_id == chaa->index_id) + { + return facetalkframe; + } + } + else if (char_speaking >= 0) + { + if (char_speaking == chaa->index_id) + { + return chaa->frame; + } + } + + debug_script_warn("Character.SpeakingFrame: character is not currently speaking"); + return -1; +} + +//============================================================================= + +// order of loops to turn character in circle from down to down +int turnlooporder[8] = {0, 6, 1, 7, 3, 5, 2, 4}; + +void walk_character(int chac,int tox,int toy,int ignwal, bool autoWalkAnims) { + CharacterInfo*chin=&game.chars[chac]; + if (chin->room!=displayed_room) + quit("!MoveCharacter: character not in current room"); + + chin->flags &= ~CHF_MOVENOTWALK; + + int toxPassedIn = tox, toyPassedIn = toy; + int charX = room_to_mask_coord(chin->x); + int charY = room_to_mask_coord(chin->y); + tox = room_to_mask_coord(tox); + toy = room_to_mask_coord(toy); + + if ((tox == charX) && (toy == charY)) { + StopMoving(chac); + debug_script_log("%s already at destination, not moving", chin->scrname); + return; + } + + if ((chin->animating) && (autoWalkAnims)) + chin->animating = 0; + + if (chin->idleleft < 0) { + ReleaseCharacterView(chac); + chin->idleleft=chin->idletime; + } + // stop them to make sure they're on a walkable area + // but save their frame first so that if they're already + // moving it looks smoother + int oldframe = chin->frame; + int waitWas = 0, animWaitWas = 0; + // if they are currently walking, save the current Wait + if (chin->walking) + { + waitWas = chin->walkwait; + animWaitWas = charextra[chac].animwait; + } + + StopMoving (chac); + chin->frame = oldframe; + // use toxPassedIn cached variable so the hi-res co-ordinates + // are still displayed as such + debug_script_log("%s: Start move to %d,%d", chin->scrname, toxPassedIn, toyPassedIn); + + int move_speed_x = chin->walkspeed; + int move_speed_y = chin->walkspeed; + + if (chin->walkspeed_y != UNIFORM_WALK_SPEED) + move_speed_y = chin->walkspeed_y; + + if ((move_speed_x == 0) && (move_speed_y == 0)) { + debug_script_warn("Warning: MoveCharacter called for '%s' with walk speed 0", chin->name); + } + + set_route_move_speed(move_speed_x, move_speed_y); + set_color_depth(8); + int mslot=find_route(charX, charY, tox, toy, prepare_walkable_areas(chac), chac+CHMLSOFFS, 1, ignwal); + set_color_depth(game.GetColorDepth()); + if (mslot>0) { + chin->walking = mslot; + mls[mslot].direct = ignwal; + convert_move_path_to_room_resolution(&mls[mslot]); + + // cancel any pending waits on current animations + // or if they were already moving, keep the current wait - + // this prevents a glitch if MoveCharacter is called when they + // are already moving + if (autoWalkAnims) + { + chin->walkwait = waitWas; + charextra[chac].animwait = animWaitWas; + + if (mls[mslot].pos[0] != mls[mslot].pos[1]) { + fix_player_sprite(&mls[mslot],chin); + } + } + else + chin->flags |= CHF_MOVENOTWALK; + } + else if (autoWalkAnims) // pathfinder couldn't get a route, stand them still + chin->frame = 0; +} + +int find_looporder_index (int curloop) { + int rr; + for (rr = 0; rr < 8; rr++) { + if (turnlooporder[rr] == curloop) + return rr; + } + return 0; +} + +// returns 0 to use diagonal, 1 to not +int useDiagonal (CharacterInfo *char1) { + if ((views[char1->view].numLoops < 8) || ((char1->flags & CHF_NODIAGONAL)!=0)) + return 1; + // If they have just provided standing frames for loops 4-7, to + // provide smoother turning + if (views[char1->view].loops[4].numFrames < 2) + return 2; + return 0; +} + +// returns 1 normally, or 0 if they only have horizontal animations +int hasUpDownLoops(CharacterInfo *char1) { + // if no loops in the Down animation + // or no loops in the Up animation + if ((views[char1->view].loops[0].numFrames < 1) || + (views[char1->view].numLoops < 4) || + (views[char1->view].loops[3].numFrames < 1)) + { + return 0; + } + + return 1; +} + +void start_character_turning (CharacterInfo *chinf, int useloop, int no_diagonal) { + // work out how far round they have to turn + int fromidx = find_looporder_index (chinf->loop); + int toidx = find_looporder_index (useloop); + //Display("Curloop: %d, needloop: %d",chinf->loop, useloop); + int ii, go_anticlock = 0; + // work out whether anticlockwise is quicker or not + if ((toidx > fromidx) && ((toidx - fromidx) > 4)) + go_anticlock = 1; + if ((toidx < fromidx) && ((fromidx - toidx) < 4)) + go_anticlock = 1; + // strip any current turning_around stages + chinf->walking = chinf->walking % TURNING_AROUND; + if (go_anticlock) + chinf->walking += TURNING_BACKWARDS; + else + go_anticlock = -1; + + // Allow the diagonal frames just for turning + if (no_diagonal == 2) + no_diagonal = 0; + + for (ii = fromidx; ii != toidx; ii -= go_anticlock) { + if (ii < 0) + ii = 7; + if (ii >= 8) + ii = 0; + if (ii == toidx) + break; + if ((turnlooporder[ii] >= 4) && (no_diagonal > 0)) + continue; + if (views[chinf->view].loops[turnlooporder[ii]].numFrames < 1) + continue; + if (turnlooporder[ii] < views[chinf->view].numLoops) + chinf->walking += TURNING_AROUND; + } + +} + +void fix_player_sprite(MoveList*cmls,CharacterInfo*chinf) { + const fixed xpmove = cmls->xpermove[cmls->onstage]; + const fixed ypmove = cmls->ypermove[cmls->onstage]; + + // if not moving, do nothing + if ((xpmove == 0) && (ypmove == 0)) + return; + + const int useloop = GetDirectionalLoop(chinf, xpmove, ypmove); + + if ((game.options[OPT_ROTATECHARS] == 0) || ((chinf->flags & CHF_NOTURNING) != 0)) { + chinf->loop = useloop; + return; + } + if ((chinf->loop > kDirLoop_LastOrthogonal) && ((chinf->flags & CHF_NODIAGONAL)!=0)) { + // They've just been playing an animation with an extended loop number, + // so don't try and rotate using it + chinf->loop = useloop; + return; + } + if ((chinf->loop >= views[chinf->view].numLoops) || + (views[chinf->view].loops[chinf->loop].numFrames < 1) || + (hasUpDownLoops(chinf) == 0)) { + // Character is not currently on a valid loop, so don't try to rotate + // eg. left/right only view, but current loop 0 + chinf->loop = useloop; + return; + } + const int no_diagonal = useDiagonal (chinf); + start_character_turning (chinf, useloop, no_diagonal); +} + +// Check whether two characters have walked into each other +int has_hit_another_character(int sourceChar) { + + // if the character who's moving doesn't Bitmap *, don't bother checking + if (game.chars[sourceChar].flags & CHF_NOBLOCKING) + return -1; + + for (int ww = 0; ww < game.numcharacters; ww++) { + if (game.chars[ww].on != 1) continue; + if (game.chars[ww].room != displayed_room) continue; + if (ww == sourceChar) continue; + if (game.chars[ww].flags & CHF_NOBLOCKING) continue; + + if (is_char_on_another (sourceChar, ww, nullptr, nullptr)) { + // we are now overlapping character 'ww' + if ((game.chars[ww].walking) && + ((game.chars[ww].flags & CHF_AWAITINGMOVE) == 0)) + return ww; + } + + } + return -1; +} + +// Does the next move from the character's movelist. +// Returns 1 if they are now waiting for another char to move, +// otherwise returns 0 +int doNextCharMoveStep (CharacterInfo *chi, int &char_index, CharacterExtras *chex) { + int ntf=0, xwas = chi->x, ywas = chi->y; + + if (do_movelist_move(&chi->walking,&chi->x,&chi->y) == 2) + { + if ((chi->flags & CHF_MOVENOTWALK) == 0) + fix_player_sprite(&mls[chi->walking], chi); + } + + ntf = has_hit_another_character(char_index); + if (ntf >= 0) { + chi->walkwait = 30; + if (game.chars[ntf].walkspeed < 5) + chi->walkwait += (5 - game.chars[ntf].walkspeed) * 5; + // we are now waiting for the other char to move, so + // make sure he doesn't stop for us too + + chi->flags |= CHF_AWAITINGMOVE; + + if ((chi->flags & CHF_MOVENOTWALK) == 0) + { + chi->frame = 0; + chex->animwait = chi->walkwait; + } + + if ((chi->walking < 1) || (chi->walking >= TURNING_AROUND)) ; + else if (mls[chi->walking].onpart > 0) { + mls[chi->walking].onpart --; + chi->x = xwas; + chi->y = ywas; + } + debug_script_log("%s: Bumped into %s, waiting for them to move", chi->scrname, game.chars[ntf].scrname); + return 1; + } + return 0; +} + +int find_nearest_walkable_area_within(int *xx, int *yy, int range, int step) +{ + int ex, ey, nearest = 99999, thisis, nearx = 0, neary = 0; + int startx = 0, starty = 14; + int roomWidthLowRes = room_to_mask_coord(thisroom.Width); + int roomHeightLowRes = room_to_mask_coord(thisroom.Height); + int xwidth = roomWidthLowRes, yheight = roomHeightLowRes; + + int xLowRes = room_to_mask_coord(xx[0]); + int yLowRes = room_to_mask_coord(yy[0]); + int rightEdge = room_to_mask_coord(thisroom.Edges.Right); + int leftEdge = room_to_mask_coord(thisroom.Edges.Left); + int topEdge = room_to_mask_coord(thisroom.Edges.Top); + int bottomEdge = room_to_mask_coord(thisroom.Edges.Bottom); + + // tweak because people forget to move the edges sometimes + // if the player is already over the edge, ignore it + if (xLowRes >= rightEdge) rightEdge = roomWidthLowRes; + if (xLowRes <= leftEdge) leftEdge = 0; + if (yLowRes >= bottomEdge) bottomEdge = roomHeightLowRes; + if (yLowRes <= topEdge) topEdge = 0; + + if (range > 0) + { + startx = xLowRes - range; + starty = yLowRes - range; + xwidth = startx + range * 2; + yheight = starty + range * 2; + if (startx < 0) startx = 0; + if (starty < 10) starty = 10; + if (xwidth > roomWidthLowRes) xwidth = roomWidthLowRes; + if (yheight > roomHeightLowRes) yheight = roomHeightLowRes; + } + + for (ex = startx; ex < xwidth; ex += step) { + for (ey = starty; ey < yheight; ey += step) { + // non-walkalbe, so don't go here + if (thisroom.WalkAreaMask->GetPixel(ex,ey) == 0) continue; + // off a screen edge, don't move them there + if ((ex <= leftEdge) || (ex >= rightEdge) || + (ey <= topEdge) || (ey >= bottomEdge)) + continue; + // otherwise, calculate distance from target + thisis=(int) ::sqrt((double)((ex - xLowRes) * (ex - xLowRes) + (ey - yLowRes) * (ey - yLowRes))); + if (thisisGetPixel(room_to_mask_coord(xx[0]), room_to_mask_coord(yy[0])); + // only fix this code if the game was built with 2.61 or above + if (pixValue == 0 || (loaded_game_file_version >= kGameVersion_261 && pixValue < 1)) + { + // First, check every 2 pixels within immediate area + if (!find_nearest_walkable_area_within(xx, yy, 20, 2)) + { + // If not, check whole screen at 5 pixel intervals + find_nearest_walkable_area_within(xx, yy, -1, 5); + } + } + +} + +void FindReasonableLoopForCharacter(CharacterInfo *chap) { + + if (chap->loop >= views[chap->view].numLoops) + chap->loop=kDirLoop_Default; + if (views[chap->view].numLoops < 1) + quitprintf("!View %d does not have any loops", chap->view + 1); + + // if the current loop has no frames, find one that does + if (views[chap->view].loops[chap->loop].numFrames < 1) + { + for (int i = 0; i < views[chap->view].numLoops; i++) + { + if (views[chap->view].loops[i].numFrames > 0) { + chap->loop = i; + break; + } + } + } + +} + +void walk_or_move_character(CharacterInfo *chaa, int x, int y, int blocking, int direct, bool isWalk) +{ + if (chaa->on != 1) + { + debug_script_warn("MoveCharacterBlocking: character is turned off and cannot be moved"); + return; + } + + if ((direct == ANYWHERE) || (direct == 1)) + walk_character(chaa->index_id, x, y, 1, isWalk); + else if ((direct == WALKABLE_AREAS) || (direct == 0)) + walk_character(chaa->index_id, x, y, 0, isWalk); + else + quit("!Character.Walk: Direct must be ANYWHERE or WALKABLE_AREAS"); + + if ((blocking == BLOCKING) || (blocking == 1)) + GameLoopUntilNotMoving(&chaa->walking); + else if ((blocking != IN_BACKGROUND) && (blocking != 0)) + quit("!Character.Walk: Blocking must be BLOCKING or IN_BACKGRUOND"); + +} + +int is_valid_character(int newchar) { + if ((newchar < 0) || (newchar >= game.numcharacters)) return 0; + return 1; +} + +int wantMoveNow (CharacterInfo *chi, CharacterExtras *chex) { + // check most likely case first + if ((chex->zoom == 100) || ((chi->flags & CHF_SCALEMOVESPEED) == 0)) + return 1; + + // the % checks don't work when the counter is negative, so once + // it wraps round, correct it + while (chi->walkwaitcounter < 0) { + chi->walkwaitcounter += 12000; + } + + // scaling 170-200%, move 175% speed + if (chex->zoom >= 170) { + if ((chi->walkwaitcounter % 4) >= 1) + return 2; + else + return 1; + } + // scaling 140-170%, move 150% speed + else if (chex->zoom >= 140) { + if ((chi->walkwaitcounter % 2) == 1) + return 2; + else + return 1; + } + // scaling 115-140%, move 125% speed + else if (chex->zoom >= 115) { + if ((chi->walkwaitcounter % 4) >= 3) + return 2; + else + return 1; + } + // scaling 80-120%, normal speed + else if (chex->zoom >= 80) + return 1; + // scaling 60-80%, move 75% speed + if (chex->zoom >= 60) { + if ((chi->walkwaitcounter % 4) >= 1) + return -1; + else if (chex->xwas != INVALID_X) { + // move the second half of the movement to make it smoother + chi->x = chex->xwas; + chi->y = chex->ywas; + chex->xwas = INVALID_X; + } + } + // scaling 30-60%, move 50% speed + else if (chex->zoom >= 30) { + if ((chi->walkwaitcounter % 2) == 1) + return -1; + else if (chex->xwas != INVALID_X) { + // move the second half of the movement to make it smoother + chi->x = chex->xwas; + chi->y = chex->ywas; + chex->xwas = INVALID_X; + } + } + // scaling 0-30%, move 25% speed + else { + if ((chi->walkwaitcounter % 4) >= 3) + return -1; + if (((chi->walkwaitcounter % 4) == 1) && (chex->xwas != INVALID_X)) { + // move the second half of the movement to make it smoother + chi->x = chex->xwas; + chi->y = chex->ywas; + chex->xwas = INVALID_X; + } + + } + + return 0; +} + +void setup_player_character(int charid) { + game.playercharacter = charid; + playerchar = &game.chars[charid]; + _sc_PlayerCharPtr = ccGetObjectHandleFromAddress((char*)playerchar); + if (loaded_game_file_version < kGameVersion_270) { + ccAddExternalDynamicObject("player", playerchar, &ccDynamicCharacter); + } +} + +void animate_character(CharacterInfo *chap, int loopn,int sppd,int rept, int noidleoverride, int direction, int sframe) { + + if ((chap->view < 0) || (chap->view > game.numviews)) { + quitprintf("!AnimateCharacter: you need to set the view number first\n" + "(trying to animate '%s' using loop %d. View is currently %d).",chap->name,loopn,chap->view+1); + } + debug_script_log("%s: Start anim view %d loop %d, spd %d, repeat %d, frame: %d", chap->scrname, chap->view+1, loopn, sppd, rept, sframe); + if ((chap->idleleft < 0) && (noidleoverride == 0)) { + // if idle view in progress for the character (and this is not the + // "start idle animation" animate_character call), stop the idle anim + Character_UnlockView(chap); + chap->idleleft=chap->idletime; + } + if ((loopn < 0) || (loopn >= views[chap->view].numLoops)) + quit("!AnimateCharacter: invalid loop number specified"); + if ((sframe < 0) || (sframe >= views[chap->view].loops[loopn].numFrames)) + quit("!AnimateCharacter: invalid starting frame number specified"); + Character_StopMoving(chap); + chap->animating=1; + if (rept) chap->animating |= CHANIM_REPEAT; + if (direction) chap->animating |= CHANIM_BACKWARDS; + + chap->animating|=((sppd << 8) & 0xff00); + chap->loop=loopn; + // reverse animation starts at the *previous frame* + if (direction) { + sframe--; + if (sframe < 0) + sframe = views[chap->view].loops[loopn].numFrames - (-sframe); + } + chap->frame = sframe; + + chap->wait = sppd + views[chap->view].loops[loopn].frames[chap->frame].speed; + CheckViewFrameForCharacter(chap); +} + +void CheckViewFrameForCharacter(CharacterInfo *chi) { + + int soundVolume = SCR_NO_VALUE; + + if (chi->flags & CHF_SCALEVOLUME) { + // adjust the sound volume using the character's zoom level + int zoom_level = charextra[chi->index_id].zoom; + if (zoom_level == 0) + zoom_level = 100; + + soundVolume = zoom_level; + + if (soundVolume < 0) + soundVolume = 0; + if (soundVolume > 100) + soundVolume = 100; + } + + CheckViewFrame(chi->view, chi->loop, chi->frame, soundVolume); +} + +Bitmap *GetCharacterImage(int charid, int *isFlipped) +{ + if (!gfxDriver->HasAcceleratedTransform()) + { + if (actsps[charid + MAX_ROOM_OBJECTS] != nullptr) + { + // the actsps image is pre-flipped, so no longer register the image as such + if (isFlipped) + *isFlipped = 0; + return actsps[charid + MAX_ROOM_OBJECTS]; + } + } + CharacterInfo*chin=&game.chars[charid]; + int sppic = views[chin->view].loops[chin->loop].frames[chin->frame].pic; + return spriteset[sppic]; +} + +CharacterInfo *GetCharacterAtScreen(int xx, int yy) { + int hsnum = GetCharIDAtScreen(xx, yy); + if (hsnum < 0) + return nullptr; + return &game.chars[hsnum]; +} + +CharacterInfo *GetCharacterAtRoom(int x, int y) +{ + int hsnum = is_pos_on_character(x, y); + if (hsnum < 0) + return nullptr; + return &game.chars[hsnum]; +} + +extern int char_lowest_yp, obj_lowest_yp; + +int is_pos_on_character(int xx,int yy) { + int cc,sppic,lowestyp=0,lowestwas=-1; + for (cc=0;ccview < 0) || + (chin->loop >= views[chin->view].numLoops) || + (chin->frame >= views[chin->view].loops[chin->loop].numFrames)) + { + continue; + } + + sppic=views[chin->view].loops[chin->loop].frames[chin->frame].pic; + int usewid = charextra[cc].width; + int usehit = charextra[cc].height; + if (usewid==0) usewid=game.SpriteInfos[sppic].Width; + if (usehit==0) usehit= game.SpriteInfos[sppic].Height; + int xxx = chin->x - game_to_data_coord(usewid) / 2; + int yyy = chin->get_effective_y() - game_to_data_coord(usehit); + + int mirrored = views[chin->view].loops[chin->loop].frames[chin->frame].flags & VFLG_FLIPSPRITE; + Bitmap *theImage = GetCharacterImage(cc, &mirrored); + + if (is_pos_in_sprite(xx,yy,xxx,yyy, theImage, + game_to_data_coord(usewid), + game_to_data_coord(usehit), mirrored) == FALSE) + continue; + + int use_base = chin->get_baseline(); + if (use_base < lowestyp) continue; + lowestyp=use_base; + lowestwas=cc; + } + char_lowest_yp = lowestyp; + return lowestwas; +} + +void get_char_blocking_rect(int charid, int *x1, int *y1, int *width, int *y2) { + CharacterInfo *char1 = &game.chars[charid]; + int cwidth, fromx; + + if (char1->blocking_width < 1) + cwidth = game_to_data_coord(GetCharacterWidth(charid)) - 4; + else + cwidth = char1->blocking_width; + + fromx = char1->x - cwidth/2; + if (fromx < 0) { + cwidth += fromx; + fromx = 0; + } + if (fromx + cwidth >= mask_to_room_coord(walkable_areas_temp->GetWidth())) + cwidth = mask_to_room_coord(walkable_areas_temp->GetWidth()) - fromx; + + if (x1) + *x1 = fromx; + if (width) + *width = cwidth; + if (y1) + *y1 = char1->get_blocking_top(); + if (y2) + *y2 = char1->get_blocking_bottom(); +} + +// Check whether the source char has walked onto character ww +int is_char_on_another (int sourceChar, int ww, int*fromxptr, int*cwidptr) { + + int fromx, cwidth; + int y1, y2; + get_char_blocking_rect(ww, &fromx, &y1, &cwidth, &y2); + + if (fromxptr) + fromxptr[0] = fromx; + if (cwidptr) + cwidptr[0] = cwidth; + + // if the character trying to move is already on top of + // this char somehow, allow them through + if ((sourceChar >= 0) && + // x/width are left and width co-ords, so they need >= and < + (game.chars[sourceChar].x >= fromx) && + (game.chars[sourceChar].x < fromx + cwidth) && + // y1/y2 are the top/bottom co-ords, so they need >= / <= + (game.chars[sourceChar].y >= y1 ) && + (game.chars[sourceChar].y <= y2 )) + return 1; + + return 0; +} + +int my_getpixel(Bitmap *blk, int x, int y) { + if ((x < 0) || (y < 0) || (x >= blk->GetWidth()) || (y >= blk->GetHeight())) + return -1; + + // strip the alpha channel + // TODO: is there a way to do this vtable thing with Bitmap? + BITMAP *al_bmp = (BITMAP*)blk->GetAllegroBitmap(); + return al_bmp->vtable->getpixel(al_bmp, x, y) & 0x00ffffff; +} + +int check_click_on_character(int xx,int yy,int mood) { + int lowestwas=is_pos_on_character(xx,yy); + if (lowestwas>=0) { + RunCharacterInteraction (lowestwas, mood); + return 1; + } + return 0; +} + +void _DisplaySpeechCore(int chid, const char *displbuf) { + if (displbuf[0] == 0) { + // no text, just update the current character who's speaking + // this allows the portrait side to be switched with an empty + // speech line + play.swap_portrait_lastchar = chid; + return; + } + + // adjust timing of text (so that DisplaySpeech("%s", str) pauses + // for the length of the string not 2 frames) + int len = (int)strlen(displbuf); + if (len > source_text_length + 3) + source_text_length = len; + + DisplaySpeech(displbuf, chid); +} + +void _DisplayThoughtCore(int chid, const char *displbuf) { + // adjust timing of text (so that DisplayThought("%s", str) pauses + // for the length of the string not 2 frames) + int len = (int)strlen(displbuf); + if (len > source_text_length + 3) + source_text_length = len; + + int xpp = -1, ypp = -1, width = -1; + + if ((game.options[OPT_SPEECHTYPE] == 0) || (game.chars[chid].thinkview <= 0)) { + // lucasarts-style, so we want a speech bubble actually above + // their head (or if they have no think anim in Sierra-style) + width = data_to_game_coord(play.speech_bubble_width); + xpp = play.RoomToScreenX(data_to_game_coord(game.chars[chid].x)) - width / 2; + if (xpp < 0) + xpp = 0; + // -1 will automatically put it above the char's head + ypp = -1; + } + + _displayspeech ((char*)displbuf, chid, xpp, ypp, width, 1); +} + +void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int isThought) { + if (!is_valid_character(aschar)) + quit("!DisplaySpeech: invalid character"); + + CharacterInfo *speakingChar = &game.chars[aschar]; + if ((speakingChar->view < 0) || (speakingChar->view >= game.numviews)) + quit("!DisplaySpeech: character has invalid view"); + + if (is_text_overlay > 0) + { + debug_script_warn("DisplaySpeech: speech was already displayed (nested DisplaySpeech, perhaps room script and global script conflict?)"); + return; + } + + EndSkippingUntilCharStops(); + + said_speech_line = 1; + + if (play.bgspeech_stay_on_display == 0) { + // remove any background speech + for (size_t i = 0; i < screenover.size();) { + if (screenover[i].timeout > 0) + remove_screen_overlay(screenover[i].type); + else + i++; + } + } + said_text = 1; + + // the strings are pre-translated + //texx = get_translation(texx); + our_eip=150; + + int isPause = 1; + // if the message is all .'s, don't display anything + for (int aa = 0; texx[aa] != 0; aa++) { + if (texx[aa] != '.') { + isPause = 0; + break; + } + } + + play.messagetime = GetTextDisplayTime(texx); + play.speech_in_post_state = false; + + if (isPause) { + postpone_scheduled_music_update_by(std::chrono::milliseconds(play.messagetime * 1000 / frames_per_second)); + GameLoopUntilValueIsNegative(&play.messagetime); + return; + } + + int textcol = speakingChar->talkcolor; + + // if it's 0, it won't be recognised as speech + if (textcol == 0) + textcol = 16; + + Rect ui_view = play.GetUIViewport(); + int allowShrink = 0; + int bwidth = widd; + if (bwidth < 0) + bwidth = ui_view.GetWidth()/2 + ui_view.GetWidth()/4; + + our_eip=151; + + int useview = speakingChar->talkview; + if (isThought) { + useview = speakingChar->thinkview; + // view 0 is not valid for think views + if (useview == 0) + useview = -1; + // speech bubble can shrink to fit + allowShrink = 1; + if (speakingChar->room != displayed_room) { + // not in room, centre it + xx = -1; + yy = -1; + } + } + + if (useview >= game.numviews) + quitprintf("!Character.Say: attempted to use view %d for animation, but it does not exist", useview + 1); + + int tdxp = xx,tdyp = yy; + int oldview=-1, oldloop = -1; + int ovr_type = 0; + + text_lips_offset = 0; + text_lips_text = texx; + + Bitmap *closeupface=nullptr; + // TODO: we always call _display_at later which may also start voice-over; + // find out if this may be refactored and voice started only in one place. + try_auto_play_speech(texx, texx, aschar, true); + + if (game.options[OPT_SPEECHTYPE] == 3) + remove_screen_overlay(OVER_COMPLETE); + our_eip=1500; + + if (game.options[OPT_SPEECHTYPE] == 0) + allowShrink = 1; + + if (speakingChar->idleleft < 0) { + // if idle anim in progress for the character, stop it + ReleaseCharacterView(aschar); + // speakingChar->idleleft = speakingChar->idletime; + } + + bool overlayPositionFixed = false; + int charFrameWas = 0; + int viewWasLocked = 0; + if (speakingChar->flags & CHF_FIXVIEW) + viewWasLocked = 1; + + /*if ((speakingChar->room == displayed_room) || + ((useview >= 0) && (game.options[OPT_SPEECHTYPE] > 0)) ) {*/ + + if (speakingChar->room == displayed_room) { + // If the character is in this room, go for it - otherwise + // run the "else" clause which does text in the middle of + // the screen. + our_eip=1501; + + if (speakingChar->walking) + StopMoving(aschar); + + // save the frame we need to go back to + // if they were moving, this will be 0 (because we just called + // StopMoving); otherwise, it might be a specific animation + // frame which we should return to + if (viewWasLocked) + charFrameWas = speakingChar->frame; + + // if the current loop doesn't exist in talking view, use loop 0 + if (speakingChar->loop >= views[speakingChar->view].numLoops) + speakingChar->loop = 0; + + if ((speakingChar->view < 0) || + (speakingChar->loop >= views[speakingChar->view].numLoops) || + (views[speakingChar->view].loops[speakingChar->loop].numFrames < 1)) + { + quitprintf("Unable to display speech because the character %s has an invalid view frame (View %d, loop %d, frame %d)", speakingChar->scrname, speakingChar->view + 1, speakingChar->loop, speakingChar->frame); + } + + our_eip=1504; + + // Calculate speech position based on character's position on screen + auto view = FindNearestViewport(aschar); + if (tdxp < 0) + tdxp = view->RoomToScreen(data_to_game_coord(speakingChar->x), 0).first.X; + if (tdxp < 2) + tdxp = 2; + tdxp = -tdxp; // tell it to centre it ([ikm] not sure what's going on here... wrong comment?) + + if (tdyp < 0) + { + int sppic = views[speakingChar->view].loops[speakingChar->loop].frames[0].pic; + int height = (charextra[aschar].height < 1) ? game.SpriteInfos[sppic].Height : height = charextra[aschar].height; + tdyp = view->RoomToScreen(0, data_to_game_coord(game.chars[aschar].get_effective_y()) - height).first.Y + - get_fixed_pixel_size(5); + if (isThought) // if it's a thought, lift it a bit further up + tdyp -= get_fixed_pixel_size(10); + } + if (tdyp < 5) + tdyp = 5; + + our_eip=152; + + if ((useview >= 0) && (game.options[OPT_SPEECHTYPE] > 0)) { + // Sierra-style close-up portrait + + if (play.swap_portrait_lastchar != aschar) { + // if the portraits are set to Alternate, OR they are + // set to Left but swap_portrait has been set to 1 (the old + // method for enabling it), then swap them round + if ((game.options[OPT_PORTRAITSIDE] == PORTRAIT_ALTERNATE) || + ((game.options[OPT_PORTRAITSIDE] == 0) && + (play.swap_portrait_side > 0))) { + + if (play.swap_portrait_side == 2) + play.swap_portrait_side = 1; + else + play.swap_portrait_side = 2; + } + + if (game.options[OPT_PORTRAITSIDE] == PORTRAIT_XPOSITION) { + // Portrait side based on character X-positions + if (play.swap_portrait_lastchar < 0) { + // No previous character been spoken to + // therefore, assume it's the player + if(game.playercharacter != aschar && game.chars[game.playercharacter].room == speakingChar->room && game.chars[game.playercharacter].on == 1) + play.swap_portrait_lastchar = game.playercharacter; + else + // The player's not here. Find another character in this room + // that it could be + for (int ce = 0; ce < game.numcharacters; ce++) { + if ((game.chars[ce].room == speakingChar->room) && + (game.chars[ce].on == 1) && + (ce != aschar)) { + play.swap_portrait_lastchar = ce; + break; + } + } + } + + if (play.swap_portrait_lastchar >= 0) { + // if this character is right of the one before, put the + // portrait on the right + if (speakingChar->x > game.chars[play.swap_portrait_lastchar].x) + play.swap_portrait_side = -1; + else + play.swap_portrait_side = 0; + } + } + play.swap_portrait_lastlastchar = play.swap_portrait_lastchar; + play.swap_portrait_lastchar = aschar; + } + else + // If the portrait side is based on the character's X position and the same character is + // speaking, compare against the previous *previous* character to see where the speech should be + if (game.options[OPT_PORTRAITSIDE] == PORTRAIT_XPOSITION && play.swap_portrait_lastlastchar >= 0) { + if (speakingChar->x > game.chars[play.swap_portrait_lastlastchar].x) + play.swap_portrait_side = -1; + else + play.swap_portrait_side = 0; + } + + // Determine whether to display the portrait on the left or right + int portrait_on_right = 0; + + if (game.options[OPT_SPEECHTYPE] == 3) + { } // always on left with QFG-style speech + else if ((play.swap_portrait_side == 1) || + (play.swap_portrait_side == -1) || + (game.options[OPT_PORTRAITSIDE] == PORTRAIT_RIGHT)) + portrait_on_right = 1; + + + int bigx=0,bigy=0,kk; + ViewStruct*viptr=&views[useview]; + for (kk = 0; kk < viptr->loops[0].numFrames; kk++) + { + int tw = game.SpriteInfos[viptr->loops[0].frames[kk].pic].Width; + if (tw > bigx) bigx=tw; + tw = game.SpriteInfos[viptr->loops[0].frames[kk].pic].Height; + if (tw > bigy) bigy=tw; + } + + // if they accidentally used a large full-screen image as the sierra-style + // talk view, correct it + if ((game.options[OPT_SPEECHTYPE] != 3) && (bigx > ui_view.GetWidth() - get_fixed_pixel_size(50))) + bigx = ui_view.GetWidth() - get_fixed_pixel_size(50); + + if (widd > 0) + bwidth = widd - bigx; + + our_eip=153; + int ovr_yp = get_fixed_pixel_size(20); + int view_frame_x = 0; + int view_frame_y = 0; + facetalk_qfg4_override_placement_x = false; + facetalk_qfg4_override_placement_y = false; + + if (game.options[OPT_SPEECHTYPE] == 3) { + // QFG4-style whole screen picture + closeupface = BitmapHelper::CreateBitmap(ui_view.GetWidth(), ui_view.GetHeight(), spriteset[viptr->loops[0].frames[0].pic]->GetColorDepth()); + closeupface->Clear(0); + if (xx < 0 && play.speech_portrait_placement) + { + facetalk_qfg4_override_placement_x = true; + view_frame_x = play.speech_portrait_x; + } + if (yy < 0 && play.speech_portrait_placement) + { + facetalk_qfg4_override_placement_y = true; + view_frame_y = play.speech_portrait_y; + } + else + { + view_frame_y = ui_view.GetHeight()/2 - game.SpriteInfos[viptr->loops[0].frames[0].pic].Height/2; + } + bigx = ui_view.GetWidth()/2 - get_fixed_pixel_size(20); + ovr_type = OVER_COMPLETE; + ovr_yp = 0; + tdyp = -1; // center vertically + } + else { + // KQ6-style close-up face picture + if (yy < 0 && play.speech_portrait_placement) + { + ovr_yp = play.speech_portrait_y; + } + else if (yy < 0) + ovr_yp = adjust_y_for_guis (ovr_yp); + else + ovr_yp = yy; + + closeupface = BitmapHelper::CreateTransparentBitmap(bigx+1,bigy+1,spriteset[viptr->loops[0].frames[0].pic]->GetColorDepth()); + ovr_type = OVER_PICTURE; + + if (yy < 0) + tdyp = ovr_yp + get_textwindow_top_border_height(play.speech_textwindow_gui); + } + const ViewFrame *vf = &viptr->loops[0].frames[0]; + const bool closeupface_has_alpha = (game.SpriteInfos[vf->pic].Flags & SPF_ALPHACHANNEL) != 0; + DrawViewFrame(closeupface, vf, view_frame_x, view_frame_y); + + int overlay_x = get_fixed_pixel_size(10); + if (xx < 0) { + tdxp = bigx + get_textwindow_border_width(play.speech_textwindow_gui) / 2; + if (play.speech_portrait_placement) + { + overlay_x = play.speech_portrait_x; + tdxp += overlay_x + get_fixed_pixel_size(6); + } + else + { + tdxp += get_fixed_pixel_size(16); + } + + int maxWidth = (ui_view.GetWidth() - tdxp) - get_fixed_pixel_size(5) - + get_textwindow_border_width (play.speech_textwindow_gui) / 2; + + if (bwidth > maxWidth) + bwidth = maxWidth; + } + else { + tdxp = xx + bigx + get_fixed_pixel_size(8); + overlay_x = xx; + } + + // allow the text box to be shrunk to fit the text + allowShrink = 1; + + // if the portrait's on the right, swap it round + if (portrait_on_right) { + if ((xx < 0) || (widd < 0)) { + tdxp = get_fixed_pixel_size(9); + if (play.speech_portrait_placement) + { + overlay_x = (ui_view.GetWidth() - bigx) - play.speech_portrait_x; + int maxWidth = overlay_x - tdxp - get_fixed_pixel_size(9) - + get_textwindow_border_width (play.speech_textwindow_gui) / 2; + if (bwidth > maxWidth) + bwidth = maxWidth; + } + else + { + overlay_x = (ui_view.GetWidth() - bigx) - get_fixed_pixel_size(5); + } + } + else { + overlay_x = (xx + widd - bigx) - get_fixed_pixel_size(5); + tdxp = xx; + } + tdxp += get_textwindow_border_width(play.speech_textwindow_gui) / 2; + allowShrink = 2; + } + if (game.options[OPT_SPEECHTYPE] == 3) + overlay_x = 0; + face_talking=add_screen_overlay(overlay_x,ovr_yp,ovr_type,closeupface, closeupface_has_alpha); + facetalkframe = 0; + facetalkwait = viptr->loops[0].frames[0].speed + GetCharacterSpeechAnimationDelay(speakingChar); + facetalkloop = 0; + facetalkview = useview; + facetalkrepeat = (isThought) ? 0 : 1; + facetalkBlinkLoop = 0; + facetalkAllowBlink = 1; + if ((isThought) && (speakingChar->flags & CHF_NOBLINKANDTHINK)) + facetalkAllowBlink = 0; + facetalkchar = &game.chars[aschar]; + if (facetalkchar->blinktimer < 0) + facetalkchar->blinktimer = facetalkchar->blinkinterval; + textcol=-textcol; + overlayPositionFixed = true; + } + else if (useview >= 0) { + // Lucasarts-style speech + our_eip=154; + + oldview = speakingChar->view; + oldloop = speakingChar->loop; + speakingChar->animating = 1 | (GetCharacterSpeechAnimationDelay(speakingChar) << 8); + // only repeat if speech, not thought + if (!isThought) + speakingChar->animating |= CHANIM_REPEAT; + + speakingChar->view = useview; + speakingChar->frame=0; + speakingChar->flags|=CHF_FIXVIEW; + + if (speakingChar->loop >= views[speakingChar->view].numLoops) + { + // current character loop is outside the normal talking directions + speakingChar->loop = 0; + } + + facetalkBlinkLoop = speakingChar->loop; + + if (speakingChar->on && // don't bother checking if character is not visible (also fixes 'Trilby's Notes' legacy game) + ((speakingChar->loop >= views[speakingChar->view].numLoops) || + (views[speakingChar->view].loops[speakingChar->loop].numFrames < 1))) + { + quitprintf("!Unable to display speech because the character %s has an invalid speech view (View %d, loop %d, frame %d)", speakingChar->scrname, speakingChar->view + 1, speakingChar->loop, speakingChar->frame); + } + + // set up the speed of the first frame + speakingChar->wait = GetCharacterSpeechAnimationDelay(speakingChar) + + views[speakingChar->view].loops[speakingChar->loop].frames[0].speed; + + if (widd < 0) { + bwidth = ui_view.GetWidth()/2 + ui_view.GetWidth()/6; + // If they are close to the screen edge, make the text narrower + int relx = play.RoomToScreenX(data_to_game_coord(speakingChar->x)); + if ((relx < ui_view.GetWidth() / 4) || (relx > ui_view.GetWidth() - (ui_view.GetWidth() / 4))) + bwidth -= ui_view.GetWidth() / 5; + } + /* this causes the text to bob up and down as they talk + tdxp = OVR_AUTOPLACE; + tdyp = aschar;*/ + if (!isThought) // set up the lip sync if not thinking + char_speaking = aschar; + + } + } + else + allowShrink = 1; + + // it wants the centred position, so make it so + if ((xx >= 0) && (tdxp < 0)) + tdxp -= widd / 2; + + // if they used DisplaySpeechAt, then use the supplied width + if ((widd > 0) && (isThought == 0)) + allowShrink = 0; + + if (isThought) + char_thinking = aschar; + + our_eip=155; + _display_at(tdxp, tdyp, bwidth, texx, DISPLAYTEXT_SPEECH, textcol, isThought, allowShrink, overlayPositionFixed); + our_eip=156; + if ((play.in_conversation > 0) && (game.options[OPT_SPEECHTYPE] == 3)) + closeupface = nullptr; + if (closeupface!=nullptr) + remove_screen_overlay(ovr_type); + mark_screen_dirty(); + face_talking = -1; + facetalkchar = nullptr; + our_eip=157; + if (oldview>=0) { + speakingChar->flags &= ~CHF_FIXVIEW; + if (viewWasLocked) + speakingChar->flags |= CHF_FIXVIEW; + speakingChar->view=oldview; + + // Don't reset the loop in 2.x games + if (loaded_game_file_version > kGameVersion_272) + speakingChar->loop = oldloop; + + speakingChar->animating=0; + speakingChar->frame = charFrameWas; + speakingChar->wait=0; + speakingChar->idleleft = speakingChar->idletime; + // restart the idle animation straight away + charextra[aschar].process_idle_this_time = 1; + } + char_speaking = -1; + char_thinking = -1; + if (play.IsBlockingVoiceSpeech()) + stop_voice_speech(); +} + +int get_character_currently_talking() { + if ((face_talking >= 0) && (facetalkrepeat)) + return facetalkchar->index_id; + else if (char_speaking >= 0) + return char_speaking; + + return -1; +} + +void DisplaySpeech(const char*texx, int aschar) { + _displayspeech (texx, aschar, -1, -1, -1, 0); +} + +// Calculate which frame of the loop to use for this character of +// speech +int GetLipSyncFrame (const char *curtex, int *stroffs) { + /*char *frameletters[MAXLIPSYNCFRAMES] = + {"./,/ ", "A", "O", "F/V", "D/N/G/L/R", "B/P/M", + "Y/H/K/Q/C", "I/T/E/X/th", "U/W", "S/Z/J/ch", NULL, + NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL};*/ + + int bestfit_len = 0, bestfit = game.default_lipsync_frame; + for (int aa = 0; aa < MAXLIPSYNCFRAMES; aa++) { + char *tptr = game.lipSyncFrameLetters[aa]; + while (tptr[0] != 0) { + int lenthisbit = strlen(tptr); + if (strchr(tptr, '/')) + lenthisbit = strchr(tptr, '/') - tptr; + + if ((ags_strnicmp (curtex, tptr, lenthisbit) == 0) && (lenthisbit > bestfit_len)) { + bestfit = aa; + bestfit_len = lenthisbit; + } + tptr += lenthisbit; + while (tptr[0] == '/') + tptr++; + } + } + // If it's an unknown character, use the default frame + if (bestfit_len == 0) + bestfit_len = 1; + *stroffs += bestfit_len; + return bestfit; +} + +int update_lip_sync(int talkview, int talkloop, int *talkframeptr) { + int talkframe = talkframeptr[0]; + int talkwait = 0; + + // lip-sync speech + const char *nowsaying = &text_lips_text[text_lips_offset]; + // if it's an apostraphe, skip it (we'll, I'll, etc) + if (nowsaying[0] == '\'') { + text_lips_offset++; + nowsaying++; + } + + if (text_lips_offset >= (int)strlen(text_lips_text)) + talkframe = 0; + else { + talkframe = GetLipSyncFrame (nowsaying, &text_lips_offset); + if (talkframe >= views[talkview].loops[talkloop].numFrames) + talkframe = 0; + } + + talkwait = loops_per_character + views[talkview].loops[talkloop].frames[talkframe].speed; + + talkframeptr[0] = talkframe; + return talkwait; +} + +Rect GetCharacterRoomBBox(int charid, bool use_frame_0) +{ + int width, height; + const CharacterExtras& chex = charextra[charid]; + const CharacterInfo& chin = game.chars[charid]; + int frame = use_frame_0 ? 0 : chin.frame; + int pic = views[chin.view].loops[chin.loop].frames[frame].pic; + scale_sprite_size(pic, chex.zoom, &width, &height); + return RectWH(chin.x - width / 2, chin.y - height, width, height); +} + +PViewport FindNearestViewport(int charid) +{ + Rect bbox = GetCharacterRoomBBox(charid, true); + float min_dist = -1.f; + PViewport nearest_view; + for (int i = 0; i < play.GetRoomViewportCount(); ++i) + { + auto view = play.GetRoomViewport(i); + if (!view->IsVisible()) + continue; + auto cam = view->GetCamera(); + if (!cam) + continue; + Rect camr = cam->GetRect(); + float dist = DistanceBetween(bbox, camr); + if (dist == 0.f) + return view; + if (min_dist < 0.f || dist < min_dist) + { + min_dist = dist; + nearest_view = view; + } + } + return nearest_view ? nearest_view : play.GetRoomViewport(0); +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// void | CharacterInfo *chaa, ScriptInvItem *invi, int addIndex +RuntimeScriptValue Sc_Character_AddInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ_PINT(CharacterInfo, Character_AddInventory, ScriptInvItem); +} + +// void | CharacterInfo *chaa, int x, int y +RuntimeScriptValue Sc_Character_AddWaypoint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(CharacterInfo, Character_AddWaypoint); +} + +// void | CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction +RuntimeScriptValue Sc_Character_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5(CharacterInfo, Character_Animate); +} + +RuntimeScriptValue Sc_Character_AnimateFrom(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT6(CharacterInfo, Character_AnimateFrom); +} + +// void | CharacterInfo *chaa, int room, int x, int y +RuntimeScriptValue Sc_Character_ChangeRoom(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(CharacterInfo, Character_ChangeRoom); +} + +RuntimeScriptValue Sc_Character_ChangeRoomSetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(CharacterInfo, Character_ChangeRoomSetLoop); +} + +// void | CharacterInfo *chaa, int room, int newPos +RuntimeScriptValue Sc_Character_ChangeRoomAutoPosition(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(CharacterInfo, Character_ChangeRoomAutoPosition); +} + +// void | CharacterInfo *chap, int vii +RuntimeScriptValue Sc_Character_ChangeView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_ChangeView); +} + +// void | CharacterInfo *char1, CharacterInfo *char2, int blockingStyle +RuntimeScriptValue Sc_Character_FaceCharacter(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ_PINT(CharacterInfo, Character_FaceCharacter, CharacterInfo); +} + +// void | CharacterInfo *char1, int direction, int blockingStyle +RuntimeScriptValue Sc_Character_FaceDirection(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(CharacterInfo, Character_FaceDirection); +} + +// void | CharacterInfo *char1, int xx, int yy, int blockingStyle +RuntimeScriptValue Sc_Character_FaceLocation(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(CharacterInfo, Character_FaceLocation); +} + +// void | CharacterInfo *char1, ScriptObject *obj, int blockingStyle +RuntimeScriptValue Sc_Character_FaceObject(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ_PINT(CharacterInfo, Character_FaceObject, ScriptObject); +} + +// void | CharacterInfo *chaa, CharacterInfo *tofollow, int distaway, int eagerness +RuntimeScriptValue Sc_Character_FollowCharacter(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ_PINT2(CharacterInfo, Character_FollowCharacter, CharacterInfo); +} + +// int (CharacterInfo *chaa, const char *property) +RuntimeScriptValue Sc_Character_GetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(CharacterInfo, Character_GetProperty, const char); +} + +// void (CharacterInfo *chaa, const char *property, char *bufer) +RuntimeScriptValue Sc_Character_GetPropertyText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ2(CharacterInfo, Character_GetPropertyText, const char, char); +} + +// const char* (CharacterInfo *chaa, const char *property) +RuntimeScriptValue Sc_Character_GetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_POBJ(CharacterInfo, const char, myScriptStringImpl, Character_GetTextProperty, const char); +} + +RuntimeScriptValue Sc_Character_SetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ_PINT(CharacterInfo, Character_SetProperty, const char); +} + +RuntimeScriptValue Sc_Character_SetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ2(CharacterInfo, Character_SetTextProperty, const char, const char); +} + +// int (CharacterInfo *chaa, ScriptInvItem *invi) +RuntimeScriptValue Sc_Character_HasInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(CharacterInfo, Character_HasInventory, ScriptInvItem); +} + +// int (CharacterInfo *char1, CharacterInfo *char2) +RuntimeScriptValue Sc_Character_IsCollidingWithChar(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(CharacterInfo, Character_IsCollidingWithChar, CharacterInfo); +} + +// int (CharacterInfo *chin, ScriptObject *objid) +RuntimeScriptValue Sc_Character_IsCollidingWithObject(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(CharacterInfo, Character_IsCollidingWithObject, ScriptObject); +} + +RuntimeScriptValue Sc_Character_IsInteractionAvailable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_PINT(CharacterInfo, Character_IsInteractionAvailable); +} + +// void (CharacterInfo *chap, int vii) +RuntimeScriptValue Sc_Character_LockView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_LockView); +} + +// void (CharacterInfo *chap, int vii, int stopMoving) +RuntimeScriptValue Sc_Character_LockViewEx(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(CharacterInfo, Character_LockViewEx); +} + +// void (CharacterInfo *chap, int vii, int loop, int align) +RuntimeScriptValue Sc_Character_LockViewAligned_Old(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(CharacterInfo, Character_LockViewAligned_Old); +} + +// void (CharacterInfo *chap, int vii, int loop, int align, int stopMoving) +RuntimeScriptValue Sc_Character_LockViewAlignedEx_Old(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(CharacterInfo, Character_LockViewAlignedEx_Old); +} + +RuntimeScriptValue Sc_Character_LockViewAligned(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(CharacterInfo, Character_LockViewAligned); +} + +RuntimeScriptValue Sc_Character_LockViewAlignedEx(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(CharacterInfo, Character_LockViewAlignedEx); +} + +// void (CharacterInfo *chaa, int view, int loop, int frame) +RuntimeScriptValue Sc_Character_LockViewFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(CharacterInfo, Character_LockViewFrame); +} + +// void (CharacterInfo *chaa, int view, int loop, int frame, int stopMoving) +RuntimeScriptValue Sc_Character_LockViewFrameEx(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(CharacterInfo, Character_LockViewFrameEx); +} + +// void (CharacterInfo *chap, int vii, int xoffs, int yoffs) +RuntimeScriptValue Sc_Character_LockViewOffset(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(CharacterInfo, Character_LockViewOffset); +} + +// void (CharacterInfo *chap, int vii, int xoffs, int yoffs, int stopMoving) +RuntimeScriptValue Sc_Character_LockViewOffsetEx(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(CharacterInfo, Character_LockViewOffsetEx); +} + +// void (CharacterInfo *chap, ScriptInvItem *invi) +RuntimeScriptValue Sc_Character_LoseInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(CharacterInfo, Character_LoseInventory, ScriptInvItem); +} + +// void (CharacterInfo *chaa, int x, int y, int blocking, int direct) +RuntimeScriptValue Sc_Character_Move(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(CharacterInfo, Character_Move); +} + +// void (CharacterInfo *chap) +RuntimeScriptValue Sc_Character_PlaceOnWalkableArea(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(CharacterInfo, Character_PlaceOnWalkableArea); +} + +// void (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_RemoveTint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(CharacterInfo, Character_RemoveTint); +} + +// void (CharacterInfo *chaa, int mood) +RuntimeScriptValue Sc_Character_RunInteraction(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_RunInteraction); +} + +// void (CharacterInfo *chaa, const char *texx, ...) +RuntimeScriptValue Sc_Character_Say(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_SCRIPT_SPRINTF(Character_Say, 1); + Character_Say((CharacterInfo*)self, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (CharacterInfo *chaa, int x, int y, int width, const char *texx) +RuntimeScriptValue Sc_Character_SayAt(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3_POBJ(CharacterInfo, Character_SayAt, const char); +} + +// ScriptOverlay* (CharacterInfo *chaa, const char *texx) +RuntimeScriptValue Sc_Character_SayBackground(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJAUTO_POBJ(CharacterInfo, ScriptOverlay, Character_SayBackground, const char); +} + +// void (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_SetAsPlayer(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(CharacterInfo, Character_SetAsPlayer); +} + +// void (CharacterInfo *chaa, int iview, int itime) +RuntimeScriptValue Sc_Character_SetIdleView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(CharacterInfo, Character_SetIdleView); +} + +RuntimeScriptValue Sc_Character_HasExplicitLight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(CharacterInfo, Character_GetHasExplicitLight); +} + +RuntimeScriptValue Sc_Character_GetLightLevel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetLightLevel); +} + +RuntimeScriptValue Sc_Character_SetLightLevel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetLightLevel); +} + +RuntimeScriptValue Sc_Character_GetTintBlue(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetTintBlue); +} + +RuntimeScriptValue Sc_Character_GetTintGreen(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetTintGreen); +} + +RuntimeScriptValue Sc_Character_GetTintRed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetTintRed); +} + +RuntimeScriptValue Sc_Character_GetTintSaturation(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetTintSaturation); +} + +RuntimeScriptValue Sc_Character_GetTintLuminance(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetTintLuminance); +} + +/* +RuntimeScriptValue Sc_Character_SetOption(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ +} +*/ + +// void (CharacterInfo *chaa, int xspeed, int yspeed) +RuntimeScriptValue Sc_Character_SetSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(CharacterInfo, Character_SetSpeed); +} + +// void (CharacterInfo *charp) +RuntimeScriptValue Sc_Character_StopMoving(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(CharacterInfo, Character_StopMoving); +} + +// void (CharacterInfo *chaa, const char *texx, ...) +RuntimeScriptValue Sc_Character_Think(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_SCRIPT_SPRINTF(Character_Think, 1); + Character_Think((CharacterInfo*)self, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +//void (CharacterInfo *chaa, int red, int green, int blue, int opacity, int luminance) +RuntimeScriptValue Sc_Character_Tint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5(CharacterInfo, Character_Tint); +} + +// void (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_UnlockView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(CharacterInfo, Character_UnlockView); +} + +// void (CharacterInfo *chaa, int stopMoving) +RuntimeScriptValue Sc_Character_UnlockViewEx(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_UnlockViewEx); +} + +// void (CharacterInfo *chaa, int x, int y, int blocking, int direct) +RuntimeScriptValue Sc_Character_Walk(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(CharacterInfo, Character_Walk); +} + +// void (CharacterInfo *chaa, int xx, int yy, int blocking) +RuntimeScriptValue Sc_Character_WalkStraight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(CharacterInfo, Character_WalkStraight); +} + +RuntimeScriptValue Sc_GetCharacterAtRoom(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(CharacterInfo, ccDynamicCharacter, GetCharacterAtRoom); +} + +// CharacterInfo *(int xx, int yy) +RuntimeScriptValue Sc_GetCharacterAtScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(CharacterInfo, ccDynamicCharacter, GetCharacterAtScreen); +} + +// ScriptInvItem* (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetActiveInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(CharacterInfo, ScriptInvItem, ccDynamicInv, Character_GetActiveInventory); +} + +// void (CharacterInfo *chaa, ScriptInvItem* iit) +RuntimeScriptValue Sc_Character_SetActiveInventory(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(CharacterInfo, Character_SetActiveInventory, ScriptInvItem); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetAnimating(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetAnimating); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetAnimationSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetAnimationSpeed); +} + +// void (CharacterInfo *chaa, int newval) +RuntimeScriptValue Sc_Character_SetAnimationSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetAnimationSpeed); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetBaseline(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetBaseline); +} + +// void (CharacterInfo *chaa, int basel) +RuntimeScriptValue Sc_Character_SetBaseline(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBaseline); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetBlinkInterval(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetBlinkInterval); +} + +// void (CharacterInfo *chaa, int interval) +RuntimeScriptValue Sc_Character_SetBlinkInterval(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlinkInterval); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetBlinkView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetBlinkView); +} + +// void (CharacterInfo *chaa, int vii) +RuntimeScriptValue Sc_Character_SetBlinkView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlinkView); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetBlinkWhileThinking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetBlinkWhileThinking); +} + +// void (CharacterInfo *chaa, int yesOrNo) +RuntimeScriptValue Sc_Character_SetBlinkWhileThinking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlinkWhileThinking); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetBlockingHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetBlockingHeight); +} + +// void (CharacterInfo *chaa, int hit) +RuntimeScriptValue Sc_Character_SetBlockingHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlockingHeight); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetBlockingWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetBlockingWidth); +} + +// void (CharacterInfo *chaa, int wid) +RuntimeScriptValue Sc_Character_SetBlockingWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetBlockingWidth); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetClickable); +} + +// void (CharacterInfo *chaa, int clik) +RuntimeScriptValue Sc_Character_SetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetClickable); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetDiagonalWalking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetDiagonalWalking); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetDiagonalWalking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetDiagonalWalking); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetFrame); +} + +// void (CharacterInfo *chaa, int newval) +RuntimeScriptValue Sc_Character_SetFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetFrame); +} + +RuntimeScriptValue Sc_Character_GetHasExplicitTint_Old(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetHasExplicitTint_Old); +} + +RuntimeScriptValue Sc_Character_GetHasExplicitTint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetHasExplicitTint); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetID); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetIdleView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetIdleView); +} + +// int (CharacterInfo *chaa, int index) +RuntimeScriptValue Sc_Character_GetIInventoryQuantity(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT(CharacterInfo, Character_GetIInventoryQuantity); +} + +// void (CharacterInfo *chaa, int index, int quant) +RuntimeScriptValue Sc_Character_SetIInventoryQuantity(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(CharacterInfo, Character_SetIInventoryQuantity); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetIgnoreLighting(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetIgnoreLighting); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetIgnoreLighting(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetIgnoreLighting); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetIgnoreScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetIgnoreScaling); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetIgnoreScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetIgnoreScaling); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetIgnoreWalkbehinds(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetIgnoreWalkbehinds); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetIgnoreWalkbehinds(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetIgnoreWalkbehinds); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetLoop); +} + +// void (CharacterInfo *chaa, int newval) +RuntimeScriptValue Sc_Character_SetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetLoop); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetManualScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetManualScaling); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetMovementLinkedToAnimation(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetMovementLinkedToAnimation); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetMovementLinkedToAnimation(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetMovementLinkedToAnimation); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetMoving(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetMoving); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetDestinationX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetDestinationX); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetDestinationY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetDestinationY); +} + +// const char* (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetName(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(CharacterInfo, const char, myScriptStringImpl, Character_GetName); +} + +// void (CharacterInfo *chaa, const char *newName) +RuntimeScriptValue Sc_Character_SetName(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(CharacterInfo, Character_SetName, const char); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetNormalView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetNormalView); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetPreviousRoom(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetPreviousRoom); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetRoom(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetRoom); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetScaleMoveSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetScaleMoveSpeed); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetScaleMoveSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetScaleMoveSpeed); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetScaleVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetScaleVolume); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetScaleVolume(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetScaleVolume); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetScaling); +} + +// void (CharacterInfo *chaa, int zoomlevel) +RuntimeScriptValue Sc_Character_SetScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetScaling); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetSolid(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetSolid); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetSolid(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetSolid); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetSpeaking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetSpeaking); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetSpeakingFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetSpeakingFrame); +} + +// int (CharacterInfo *cha) +RuntimeScriptValue Sc_GetCharacterSpeechAnimationDelay(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, GetCharacterSpeechAnimationDelay); +} + +// void (CharacterInfo *chaa, int newDelay) +RuntimeScriptValue Sc_Character_SetSpeechAnimationDelay(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetSpeechAnimationDelay); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetSpeechColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetSpeechColor); +} + +// void (CharacterInfo *chaa, int ncol) +RuntimeScriptValue Sc_Character_SetSpeechColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetSpeechColor); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetSpeechView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetSpeechView); +} + +// void (CharacterInfo *chaa, int vii) +RuntimeScriptValue Sc_Character_SetSpeechView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetSpeechView); +} + +RuntimeScriptValue Sc_Character_GetThinking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(CharacterInfo, Character_GetThinking); +} + +RuntimeScriptValue Sc_Character_GetThinkingFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetThinkingFrame); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetThinkView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetThinkView); +} + +// void (CharacterInfo *chaa, int vii) +RuntimeScriptValue Sc_Character_SetThinkView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetThinkView); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetTransparency); +} + +// void (CharacterInfo *chaa, int trans) +RuntimeScriptValue Sc_Character_SetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetTransparency); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetTurnBeforeWalking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetTurnBeforeWalking); +} + +// void (CharacterInfo *chaa, int yesorno) +RuntimeScriptValue Sc_Character_SetTurnBeforeWalking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetTurnBeforeWalking); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetView); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetWalkSpeedX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetWalkSpeedX); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetWalkSpeedY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetWalkSpeedY); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetX); +} + +// void (CharacterInfo *chaa, int newval) +RuntimeScriptValue Sc_Character_SetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetX); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetY); +} + +// void (CharacterInfo *chaa, int newval) +RuntimeScriptValue Sc_Character_SetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetY); +} + +// int (CharacterInfo *chaa) +RuntimeScriptValue Sc_Character_GetZ(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(CharacterInfo, Character_GetZ); +} + +// void (CharacterInfo *chaa, int newval) +RuntimeScriptValue Sc_Character_SetZ(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(CharacterInfo, Character_SetZ); +} + +//============================================================================= +// +// Exclusive API for Plugins +// +//============================================================================= + +// void (CharacterInfo *chaa, const char *texx, ...) +void ScPl_Character_Say(CharacterInfo *chaa, const char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + Character_Say(chaa, scsf_buffer); +} + +// void (CharacterInfo *chaa, const char *texx, ...) +void ScPl_Character_Think(CharacterInfo *chaa, const char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + Character_Think(chaa, scsf_buffer); +} + +void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion compat_api) +{ + ccAddExternalObjectFunction("Character::AddInventory^2", Sc_Character_AddInventory); + ccAddExternalObjectFunction("Character::AddWaypoint^2", Sc_Character_AddWaypoint); + ccAddExternalObjectFunction("Character::Animate^5", Sc_Character_Animate); + ccAddExternalObjectFunction("Character::Animate^6", Sc_Character_AnimateFrom); + ccAddExternalObjectFunction("Character::ChangeRoom^3", Sc_Character_ChangeRoom); + ccAddExternalObjectFunction("Character::ChangeRoom^4", Sc_Character_ChangeRoomSetLoop); + ccAddExternalObjectFunction("Character::ChangeRoomAutoPosition^2", Sc_Character_ChangeRoomAutoPosition); + ccAddExternalObjectFunction("Character::ChangeView^1", Sc_Character_ChangeView); + ccAddExternalObjectFunction("Character::FaceCharacter^2", Sc_Character_FaceCharacter); + ccAddExternalObjectFunction("Character::FaceDirection^2", Sc_Character_FaceDirection); + ccAddExternalObjectFunction("Character::FaceLocation^3", Sc_Character_FaceLocation); + ccAddExternalObjectFunction("Character::FaceObject^2", Sc_Character_FaceObject); + ccAddExternalObjectFunction("Character::FollowCharacter^3", Sc_Character_FollowCharacter); + ccAddExternalObjectFunction("Character::GetProperty^1", Sc_Character_GetProperty); + ccAddExternalObjectFunction("Character::GetPropertyText^2", Sc_Character_GetPropertyText); + ccAddExternalObjectFunction("Character::GetTextProperty^1", Sc_Character_GetTextProperty); + ccAddExternalObjectFunction("Character::SetProperty^2", Sc_Character_SetProperty); + ccAddExternalObjectFunction("Character::SetTextProperty^2", Sc_Character_SetTextProperty); + ccAddExternalObjectFunction("Character::HasInventory^1", Sc_Character_HasInventory); + ccAddExternalObjectFunction("Character::IsCollidingWithChar^1", Sc_Character_IsCollidingWithChar); + ccAddExternalObjectFunction("Character::IsCollidingWithObject^1", Sc_Character_IsCollidingWithObject); + ccAddExternalObjectFunction("Character::IsInteractionAvailable^1", Sc_Character_IsInteractionAvailable); + ccAddExternalObjectFunction("Character::LockView^1", Sc_Character_LockView); + ccAddExternalObjectFunction("Character::LockView^2", Sc_Character_LockViewEx); + if (base_api < kScriptAPI_v350) + { + ccAddExternalObjectFunction("Character::LockViewAligned^3", Sc_Character_LockViewAligned_Old); + ccAddExternalObjectFunction("Character::LockViewAligned^4", Sc_Character_LockViewAlignedEx_Old); + } + else + { + ccAddExternalObjectFunction("Character::LockViewAligned^3", Sc_Character_LockViewAligned); + ccAddExternalObjectFunction("Character::LockViewAligned^4", Sc_Character_LockViewAlignedEx); + } + ccAddExternalObjectFunction("Character::LockViewFrame^3", Sc_Character_LockViewFrame); + ccAddExternalObjectFunction("Character::LockViewFrame^4", Sc_Character_LockViewFrameEx); + ccAddExternalObjectFunction("Character::LockViewOffset^3", Sc_Character_LockViewOffset); + ccAddExternalObjectFunction("Character::LockViewOffset^4", Sc_Character_LockViewOffsetEx); + ccAddExternalObjectFunction("Character::LoseInventory^1", Sc_Character_LoseInventory); + ccAddExternalObjectFunction("Character::Move^4", Sc_Character_Move); + ccAddExternalObjectFunction("Character::PlaceOnWalkableArea^0", Sc_Character_PlaceOnWalkableArea); + ccAddExternalObjectFunction("Character::RemoveTint^0", Sc_Character_RemoveTint); + ccAddExternalObjectFunction("Character::RunInteraction^1", Sc_Character_RunInteraction); + ccAddExternalObjectFunction("Character::Say^101", Sc_Character_Say); + ccAddExternalObjectFunction("Character::SayAt^4", Sc_Character_SayAt); + ccAddExternalObjectFunction("Character::SayBackground^1", Sc_Character_SayBackground); + ccAddExternalObjectFunction("Character::SetAsPlayer^0", Sc_Character_SetAsPlayer); + ccAddExternalObjectFunction("Character::SetIdleView^2", Sc_Character_SetIdleView); + ccAddExternalObjectFunction("Character::SetLightLevel^1", Sc_Character_SetLightLevel); + //ccAddExternalObjectFunction("Character::SetOption^2", Sc_Character_SetOption); + ccAddExternalObjectFunction("Character::SetWalkSpeed^2", Sc_Character_SetSpeed); + ccAddExternalObjectFunction("Character::StopMoving^0", Sc_Character_StopMoving); + ccAddExternalObjectFunction("Character::Think^101", Sc_Character_Think); + ccAddExternalObjectFunction("Character::Tint^5", Sc_Character_Tint); + ccAddExternalObjectFunction("Character::UnlockView^0", Sc_Character_UnlockView); + ccAddExternalObjectFunction("Character::UnlockView^1", Sc_Character_UnlockViewEx); + ccAddExternalObjectFunction("Character::Walk^4", Sc_Character_Walk); + ccAddExternalObjectFunction("Character::WalkStraight^3", Sc_Character_WalkStraight); + + ccAddExternalStaticFunction("Character::GetAtRoomXY^2", Sc_GetCharacterAtRoom); + ccAddExternalStaticFunction("Character::GetAtScreenXY^2", Sc_GetCharacterAtScreen); + + ccAddExternalObjectFunction("Character::get_ActiveInventory", Sc_Character_GetActiveInventory); + ccAddExternalObjectFunction("Character::set_ActiveInventory", Sc_Character_SetActiveInventory); + ccAddExternalObjectFunction("Character::get_Animating", Sc_Character_GetAnimating); + ccAddExternalObjectFunction("Character::get_AnimationSpeed", Sc_Character_GetAnimationSpeed); + ccAddExternalObjectFunction("Character::set_AnimationSpeed", Sc_Character_SetAnimationSpeed); + ccAddExternalObjectFunction("Character::get_Baseline", Sc_Character_GetBaseline); + ccAddExternalObjectFunction("Character::set_Baseline", Sc_Character_SetBaseline); + ccAddExternalObjectFunction("Character::get_BlinkInterval", Sc_Character_GetBlinkInterval); + ccAddExternalObjectFunction("Character::set_BlinkInterval", Sc_Character_SetBlinkInterval); + ccAddExternalObjectFunction("Character::get_BlinkView", Sc_Character_GetBlinkView); + ccAddExternalObjectFunction("Character::set_BlinkView", Sc_Character_SetBlinkView); + ccAddExternalObjectFunction("Character::get_BlinkWhileThinking", Sc_Character_GetBlinkWhileThinking); + ccAddExternalObjectFunction("Character::set_BlinkWhileThinking", Sc_Character_SetBlinkWhileThinking); + ccAddExternalObjectFunction("Character::get_BlockingHeight", Sc_Character_GetBlockingHeight); + ccAddExternalObjectFunction("Character::set_BlockingHeight", Sc_Character_SetBlockingHeight); + ccAddExternalObjectFunction("Character::get_BlockingWidth", Sc_Character_GetBlockingWidth); + ccAddExternalObjectFunction("Character::set_BlockingWidth", Sc_Character_SetBlockingWidth); + ccAddExternalObjectFunction("Character::get_Clickable", Sc_Character_GetClickable); + ccAddExternalObjectFunction("Character::set_Clickable", Sc_Character_SetClickable); + ccAddExternalObjectFunction("Character::get_DestinationX", Sc_Character_GetDestinationX); + ccAddExternalObjectFunction("Character::get_DestinationY", Sc_Character_GetDestinationY); + ccAddExternalObjectFunction("Character::get_DiagonalLoops", Sc_Character_GetDiagonalWalking); + ccAddExternalObjectFunction("Character::set_DiagonalLoops", Sc_Character_SetDiagonalWalking); + ccAddExternalObjectFunction("Character::get_Frame", Sc_Character_GetFrame); + ccAddExternalObjectFunction("Character::set_Frame", Sc_Character_SetFrame); + if (base_api < kScriptAPI_v341) + ccAddExternalObjectFunction("Character::get_HasExplicitTint", Sc_Character_GetHasExplicitTint_Old); + else + ccAddExternalObjectFunction("Character::get_HasExplicitTint", Sc_Character_GetHasExplicitTint); + ccAddExternalObjectFunction("Character::get_ID", Sc_Character_GetID); + ccAddExternalObjectFunction("Character::get_IdleView", Sc_Character_GetIdleView); + ccAddExternalObjectFunction("Character::geti_InventoryQuantity", Sc_Character_GetIInventoryQuantity); + ccAddExternalObjectFunction("Character::seti_InventoryQuantity", Sc_Character_SetIInventoryQuantity); + ccAddExternalObjectFunction("Character::get_IgnoreLighting", Sc_Character_GetIgnoreLighting); + ccAddExternalObjectFunction("Character::set_IgnoreLighting", Sc_Character_SetIgnoreLighting); + ccAddExternalObjectFunction("Character::get_IgnoreScaling", Sc_Character_GetIgnoreScaling); + ccAddExternalObjectFunction("Character::set_IgnoreScaling", Sc_Character_SetIgnoreScaling); + ccAddExternalObjectFunction("Character::get_IgnoreWalkbehinds", Sc_Character_GetIgnoreWalkbehinds); + ccAddExternalObjectFunction("Character::set_IgnoreWalkbehinds", Sc_Character_SetIgnoreWalkbehinds); + ccAddExternalObjectFunction("Character::get_Loop", Sc_Character_GetLoop); + ccAddExternalObjectFunction("Character::set_Loop", Sc_Character_SetLoop); + ccAddExternalObjectFunction("Character::get_ManualScaling", Sc_Character_GetIgnoreScaling); + ccAddExternalObjectFunction("Character::set_ManualScaling", Sc_Character_SetManualScaling); + ccAddExternalObjectFunction("Character::get_MovementLinkedToAnimation",Sc_Character_GetMovementLinkedToAnimation); + ccAddExternalObjectFunction("Character::set_MovementLinkedToAnimation",Sc_Character_SetMovementLinkedToAnimation); + ccAddExternalObjectFunction("Character::get_Moving", Sc_Character_GetMoving); + ccAddExternalObjectFunction("Character::get_Name", Sc_Character_GetName); + ccAddExternalObjectFunction("Character::set_Name", Sc_Character_SetName); + ccAddExternalObjectFunction("Character::get_NormalView", Sc_Character_GetNormalView); + ccAddExternalObjectFunction("Character::get_PreviousRoom", Sc_Character_GetPreviousRoom); + ccAddExternalObjectFunction("Character::get_Room", Sc_Character_GetRoom); + ccAddExternalObjectFunction("Character::get_ScaleMoveSpeed", Sc_Character_GetScaleMoveSpeed); + ccAddExternalObjectFunction("Character::set_ScaleMoveSpeed", Sc_Character_SetScaleMoveSpeed); + ccAddExternalObjectFunction("Character::get_ScaleVolume", Sc_Character_GetScaleVolume); + ccAddExternalObjectFunction("Character::set_ScaleVolume", Sc_Character_SetScaleVolume); + ccAddExternalObjectFunction("Character::get_Scaling", Sc_Character_GetScaling); + ccAddExternalObjectFunction("Character::set_Scaling", Sc_Character_SetScaling); + ccAddExternalObjectFunction("Character::get_Solid", Sc_Character_GetSolid); + ccAddExternalObjectFunction("Character::set_Solid", Sc_Character_SetSolid); + ccAddExternalObjectFunction("Character::get_Speaking", Sc_Character_GetSpeaking); + ccAddExternalObjectFunction("Character::get_SpeakingFrame", Sc_Character_GetSpeakingFrame); + ccAddExternalObjectFunction("Character::get_SpeechAnimationDelay", Sc_GetCharacterSpeechAnimationDelay); + ccAddExternalObjectFunction("Character::set_SpeechAnimationDelay", Sc_Character_SetSpeechAnimationDelay); + ccAddExternalObjectFunction("Character::get_SpeechColor", Sc_Character_GetSpeechColor); + ccAddExternalObjectFunction("Character::set_SpeechColor", Sc_Character_SetSpeechColor); + ccAddExternalObjectFunction("Character::get_SpeechView", Sc_Character_GetSpeechView); + ccAddExternalObjectFunction("Character::set_SpeechView", Sc_Character_SetSpeechView); + ccAddExternalObjectFunction("Character::get_Thinking", Sc_Character_GetThinking); + ccAddExternalObjectFunction("Character::get_ThinkingFrame", Sc_Character_GetThinkingFrame); + ccAddExternalObjectFunction("Character::get_ThinkView", Sc_Character_GetThinkView); + ccAddExternalObjectFunction("Character::set_ThinkView", Sc_Character_SetThinkView); + ccAddExternalObjectFunction("Character::get_Transparency", Sc_Character_GetTransparency); + ccAddExternalObjectFunction("Character::set_Transparency", Sc_Character_SetTransparency); + ccAddExternalObjectFunction("Character::get_TurnBeforeWalking", Sc_Character_GetTurnBeforeWalking); + ccAddExternalObjectFunction("Character::set_TurnBeforeWalking", Sc_Character_SetTurnBeforeWalking); + ccAddExternalObjectFunction("Character::get_View", Sc_Character_GetView); + ccAddExternalObjectFunction("Character::get_WalkSpeedX", Sc_Character_GetWalkSpeedX); + ccAddExternalObjectFunction("Character::get_WalkSpeedY", Sc_Character_GetWalkSpeedY); + ccAddExternalObjectFunction("Character::get_X", Sc_Character_GetX); + ccAddExternalObjectFunction("Character::set_X", Sc_Character_SetX); + ccAddExternalObjectFunction("Character::get_x", Sc_Character_GetX); + ccAddExternalObjectFunction("Character::set_x", Sc_Character_SetX); + ccAddExternalObjectFunction("Character::get_Y", Sc_Character_GetY); + ccAddExternalObjectFunction("Character::set_Y", Sc_Character_SetY); + ccAddExternalObjectFunction("Character::get_y", Sc_Character_GetY); + ccAddExternalObjectFunction("Character::set_y", Sc_Character_SetY); + ccAddExternalObjectFunction("Character::get_Z", Sc_Character_GetZ); + ccAddExternalObjectFunction("Character::set_Z", Sc_Character_SetZ); + ccAddExternalObjectFunction("Character::get_z", Sc_Character_GetZ); + ccAddExternalObjectFunction("Character::set_z", Sc_Character_SetZ); + + ccAddExternalObjectFunction("Character::get_HasExplicitLight", Sc_Character_HasExplicitLight); + ccAddExternalObjectFunction("Character::get_LightLevel", Sc_Character_GetLightLevel); + ccAddExternalObjectFunction("Character::get_TintBlue", Sc_Character_GetTintBlue); + ccAddExternalObjectFunction("Character::get_TintGreen", Sc_Character_GetTintGreen); + ccAddExternalObjectFunction("Character::get_TintRed", Sc_Character_GetTintRed); + ccAddExternalObjectFunction("Character::get_TintSaturation", Sc_Character_GetTintSaturation); + ccAddExternalObjectFunction("Character::get_TintLuminance", Sc_Character_GetTintLuminance); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Character::AddInventory^2", (void*)Character_AddInventory); + ccAddExternalFunctionForPlugin("Character::AddWaypoint^2", (void*)Character_AddWaypoint); + ccAddExternalFunctionForPlugin("Character::Animate^5", (void*)Character_Animate); + ccAddExternalFunctionForPlugin("Character::ChangeRoom^3", (void*)Character_ChangeRoom); + ccAddExternalFunctionForPlugin("Character::ChangeRoomAutoPosition^2", (void*)Character_ChangeRoomAutoPosition); + ccAddExternalFunctionForPlugin("Character::ChangeView^1", (void*)Character_ChangeView); + ccAddExternalFunctionForPlugin("Character::FaceCharacter^2", (void*)Character_FaceCharacter); + ccAddExternalFunctionForPlugin("Character::FaceDirection^2", (void*)Character_FaceDirection); + ccAddExternalFunctionForPlugin("Character::FaceLocation^3", (void*)Character_FaceLocation); + ccAddExternalFunctionForPlugin("Character::FaceObject^2", (void*)Character_FaceObject); + ccAddExternalFunctionForPlugin("Character::FollowCharacter^3", (void*)Character_FollowCharacter); + ccAddExternalFunctionForPlugin("Character::GetProperty^1", (void*)Character_GetProperty); + ccAddExternalFunctionForPlugin("Character::GetPropertyText^2", (void*)Character_GetPropertyText); + ccAddExternalFunctionForPlugin("Character::GetTextProperty^1", (void*)Character_GetTextProperty); + ccAddExternalFunctionForPlugin("Character::HasInventory^1", (void*)Character_HasInventory); + ccAddExternalFunctionForPlugin("Character::IsCollidingWithChar^1", (void*)Character_IsCollidingWithChar); + ccAddExternalFunctionForPlugin("Character::IsCollidingWithObject^1", (void*)Character_IsCollidingWithObject); + ccAddExternalFunctionForPlugin("Character::LockView^1", (void*)Character_LockView); + ccAddExternalFunctionForPlugin("Character::LockView^2", (void*)Character_LockViewEx); + if (base_api < kScriptAPI_v341) + { + ccAddExternalFunctionForPlugin("Character::LockViewAligned^3", (void*)Character_LockViewAligned_Old); + ccAddExternalFunctionForPlugin("Character::LockViewAligned^4", (void*)Character_LockViewAlignedEx_Old); + } + else + { + ccAddExternalFunctionForPlugin("Character::LockViewAligned^3", (void*)Character_LockViewAligned); + ccAddExternalFunctionForPlugin("Character::LockViewAligned^4", (void*)Character_LockViewAlignedEx); + } + ccAddExternalFunctionForPlugin("Character::LockViewFrame^3", (void*)Character_LockViewFrame); + ccAddExternalFunctionForPlugin("Character::LockViewFrame^4", (void*)Character_LockViewFrameEx); + ccAddExternalFunctionForPlugin("Character::LockViewOffset^3", (void*)Character_LockViewOffset); + ccAddExternalFunctionForPlugin("Character::LockViewOffset^4", (void*)Character_LockViewOffset); + ccAddExternalFunctionForPlugin("Character::LoseInventory^1", (void*)Character_LoseInventory); + ccAddExternalFunctionForPlugin("Character::Move^4", (void*)Character_Move); + ccAddExternalFunctionForPlugin("Character::PlaceOnWalkableArea^0", (void*)Character_PlaceOnWalkableArea); + ccAddExternalFunctionForPlugin("Character::RemoveTint^0", (void*)Character_RemoveTint); + ccAddExternalFunctionForPlugin("Character::RunInteraction^1", (void*)Character_RunInteraction); + ccAddExternalFunctionForPlugin("Character::Say^101", (void*)ScPl_Character_Say); + ccAddExternalFunctionForPlugin("Character::SayAt^4", (void*)Character_SayAt); + ccAddExternalFunctionForPlugin("Character::SayBackground^1", (void*)Character_SayBackground); + ccAddExternalFunctionForPlugin("Character::SetAsPlayer^0", (void*)Character_SetAsPlayer); + ccAddExternalFunctionForPlugin("Character::SetIdleView^2", (void*)Character_SetIdleView); + //ccAddExternalFunctionForPlugin("Character::SetOption^2", (void*)Character_SetOption); + ccAddExternalFunctionForPlugin("Character::SetWalkSpeed^2", (void*)Character_SetSpeed); + ccAddExternalFunctionForPlugin("Character::StopMoving^0", (void*)Character_StopMoving); + ccAddExternalFunctionForPlugin("Character::Think^101", (void*)ScPl_Character_Think); + ccAddExternalFunctionForPlugin("Character::Tint^5", (void*)Character_Tint); + ccAddExternalFunctionForPlugin("Character::UnlockView^0", (void*)Character_UnlockView); + ccAddExternalFunctionForPlugin("Character::UnlockView^1", (void*)Character_UnlockViewEx); + ccAddExternalFunctionForPlugin("Character::Walk^4", (void*)Character_Walk); + ccAddExternalFunctionForPlugin("Character::WalkStraight^3", (void*)Character_WalkStraight); + ccAddExternalFunctionForPlugin("Character::GetAtRoomXY^2", (void*)GetCharacterAtRoom); + ccAddExternalFunctionForPlugin("Character::GetAtScreenXY^2", (void*)GetCharacterAtScreen); + ccAddExternalFunctionForPlugin("Character::get_ActiveInventory", (void*)Character_GetActiveInventory); + ccAddExternalFunctionForPlugin("Character::set_ActiveInventory", (void*)Character_SetActiveInventory); + ccAddExternalFunctionForPlugin("Character::get_Animating", (void*)Character_GetAnimating); + ccAddExternalFunctionForPlugin("Character::get_AnimationSpeed", (void*)Character_GetAnimationSpeed); + ccAddExternalFunctionForPlugin("Character::set_AnimationSpeed", (void*)Character_SetAnimationSpeed); + ccAddExternalFunctionForPlugin("Character::get_Baseline", (void*)Character_GetBaseline); + ccAddExternalFunctionForPlugin("Character::set_Baseline", (void*)Character_SetBaseline); + ccAddExternalFunctionForPlugin("Character::get_BlinkInterval", (void*)Character_GetBlinkInterval); + ccAddExternalFunctionForPlugin("Character::set_BlinkInterval", (void*)Character_SetBlinkInterval); + ccAddExternalFunctionForPlugin("Character::get_BlinkView", (void*)Character_GetBlinkView); + ccAddExternalFunctionForPlugin("Character::set_BlinkView", (void*)Character_SetBlinkView); + ccAddExternalFunctionForPlugin("Character::get_BlinkWhileThinking", (void*)Character_GetBlinkWhileThinking); + ccAddExternalFunctionForPlugin("Character::set_BlinkWhileThinking", (void*)Character_SetBlinkWhileThinking); + ccAddExternalFunctionForPlugin("Character::get_BlockingHeight", (void*)Character_GetBlockingHeight); + ccAddExternalFunctionForPlugin("Character::set_BlockingHeight", (void*)Character_SetBlockingHeight); + ccAddExternalFunctionForPlugin("Character::get_BlockingWidth", (void*)Character_GetBlockingWidth); + ccAddExternalFunctionForPlugin("Character::set_BlockingWidth", (void*)Character_SetBlockingWidth); + ccAddExternalFunctionForPlugin("Character::get_Clickable", (void*)Character_GetClickable); + ccAddExternalFunctionForPlugin("Character::set_Clickable", (void*)Character_SetClickable); + ccAddExternalFunctionForPlugin("Character::get_DestinationX", (void*)Character_GetDestinationX); + ccAddExternalFunctionForPlugin("Character::get_DestinationY", (void*)Character_GetDestinationY); + ccAddExternalFunctionForPlugin("Character::get_DiagonalLoops", (void*)Character_GetDiagonalWalking); + ccAddExternalFunctionForPlugin("Character::set_DiagonalLoops", (void*)Character_SetDiagonalWalking); + ccAddExternalFunctionForPlugin("Character::get_Frame", (void*)Character_GetFrame); + ccAddExternalFunctionForPlugin("Character::set_Frame", (void*)Character_SetFrame); + if (base_api < kScriptAPI_v341) + ccAddExternalFunctionForPlugin("Character::get_HasExplicitTint", (void*)Character_GetHasExplicitTint_Old); + else + ccAddExternalFunctionForPlugin("Character::get_HasExplicitTint", (void*)Character_GetHasExplicitTint); + ccAddExternalFunctionForPlugin("Character::get_ID", (void*)Character_GetID); + ccAddExternalFunctionForPlugin("Character::get_IdleView", (void*)Character_GetIdleView); + ccAddExternalFunctionForPlugin("Character::geti_InventoryQuantity", (void*)Character_GetIInventoryQuantity); + ccAddExternalFunctionForPlugin("Character::seti_InventoryQuantity", (void*)Character_SetIInventoryQuantity); + ccAddExternalFunctionForPlugin("Character::get_IgnoreLighting", (void*)Character_GetIgnoreLighting); + ccAddExternalFunctionForPlugin("Character::set_IgnoreLighting", (void*)Character_SetIgnoreLighting); + ccAddExternalFunctionForPlugin("Character::get_IgnoreScaling", (void*)Character_GetIgnoreScaling); + ccAddExternalFunctionForPlugin("Character::set_IgnoreScaling", (void*)Character_SetIgnoreScaling); + ccAddExternalFunctionForPlugin("Character::get_IgnoreWalkbehinds", (void*)Character_GetIgnoreWalkbehinds); + ccAddExternalFunctionForPlugin("Character::set_IgnoreWalkbehinds", (void*)Character_SetIgnoreWalkbehinds); + ccAddExternalFunctionForPlugin("Character::get_Loop", (void*)Character_GetLoop); + ccAddExternalFunctionForPlugin("Character::set_Loop", (void*)Character_SetLoop); + ccAddExternalFunctionForPlugin("Character::get_ManualScaling", (void*)Character_GetIgnoreScaling); + ccAddExternalFunctionForPlugin("Character::set_ManualScaling", (void*)Character_SetManualScaling); + ccAddExternalFunctionForPlugin("Character::get_MovementLinkedToAnimation",(void*)Character_GetMovementLinkedToAnimation); + ccAddExternalFunctionForPlugin("Character::set_MovementLinkedToAnimation",(void*)Character_SetMovementLinkedToAnimation); + ccAddExternalFunctionForPlugin("Character::get_Moving", (void*)Character_GetMoving); + ccAddExternalFunctionForPlugin("Character::get_Name", (void*)Character_GetName); + ccAddExternalFunctionForPlugin("Character::set_Name", (void*)Character_SetName); + ccAddExternalFunctionForPlugin("Character::get_NormalView", (void*)Character_GetNormalView); + ccAddExternalFunctionForPlugin("Character::get_PreviousRoom", (void*)Character_GetPreviousRoom); + ccAddExternalFunctionForPlugin("Character::get_Room", (void*)Character_GetRoom); + ccAddExternalFunctionForPlugin("Character::get_ScaleMoveSpeed", (void*)Character_GetScaleMoveSpeed); + ccAddExternalFunctionForPlugin("Character::set_ScaleMoveSpeed", (void*)Character_SetScaleMoveSpeed); + ccAddExternalFunctionForPlugin("Character::get_ScaleVolume", (void*)Character_GetScaleVolume); + ccAddExternalFunctionForPlugin("Character::set_ScaleVolume", (void*)Character_SetScaleVolume); + ccAddExternalFunctionForPlugin("Character::get_Scaling", (void*)Character_GetScaling); + ccAddExternalFunctionForPlugin("Character::set_Scaling", (void*)Character_SetScaling); + ccAddExternalFunctionForPlugin("Character::get_Solid", (void*)Character_GetSolid); + ccAddExternalFunctionForPlugin("Character::set_Solid", (void*)Character_SetSolid); + ccAddExternalFunctionForPlugin("Character::get_Speaking", (void*)Character_GetSpeaking); + ccAddExternalFunctionForPlugin("Character::get_SpeakingFrame", (void*)Character_GetSpeakingFrame); + ccAddExternalFunctionForPlugin("Character::get_SpeechAnimationDelay", (void*)GetCharacterSpeechAnimationDelay); + ccAddExternalFunctionForPlugin("Character::set_SpeechAnimationDelay", (void*)Character_SetSpeechAnimationDelay); + ccAddExternalFunctionForPlugin("Character::get_SpeechColor", (void*)Character_GetSpeechColor); + ccAddExternalFunctionForPlugin("Character::set_SpeechColor", (void*)Character_SetSpeechColor); + ccAddExternalFunctionForPlugin("Character::get_SpeechView", (void*)Character_GetSpeechView); + ccAddExternalFunctionForPlugin("Character::set_SpeechView", (void*)Character_SetSpeechView); + ccAddExternalFunctionForPlugin("Character::get_ThinkView", (void*)Character_GetThinkView); + ccAddExternalFunctionForPlugin("Character::set_ThinkView", (void*)Character_SetThinkView); + ccAddExternalFunctionForPlugin("Character::get_Transparency", (void*)Character_GetTransparency); + ccAddExternalFunctionForPlugin("Character::set_Transparency", (void*)Character_SetTransparency); + ccAddExternalFunctionForPlugin("Character::get_TurnBeforeWalking", (void*)Character_GetTurnBeforeWalking); + ccAddExternalFunctionForPlugin("Character::set_TurnBeforeWalking", (void*)Character_SetTurnBeforeWalking); + ccAddExternalFunctionForPlugin("Character::get_View", (void*)Character_GetView); + ccAddExternalFunctionForPlugin("Character::get_WalkSpeedX", (void*)Character_GetWalkSpeedX); + ccAddExternalFunctionForPlugin("Character::get_WalkSpeedY", (void*)Character_GetWalkSpeedY); + ccAddExternalFunctionForPlugin("Character::get_X", (void*)Character_GetX); + ccAddExternalFunctionForPlugin("Character::set_X", (void*)Character_SetX); + ccAddExternalFunctionForPlugin("Character::get_x", (void*)Character_GetX); + ccAddExternalFunctionForPlugin("Character::set_x", (void*)Character_SetX); + ccAddExternalFunctionForPlugin("Character::get_Y", (void*)Character_GetY); + ccAddExternalFunctionForPlugin("Character::set_Y", (void*)Character_SetY); + ccAddExternalFunctionForPlugin("Character::get_y", (void*)Character_GetY); + ccAddExternalFunctionForPlugin("Character::set_y", (void*)Character_SetY); + ccAddExternalFunctionForPlugin("Character::get_Z", (void*)Character_GetZ); + ccAddExternalFunctionForPlugin("Character::set_Z", (void*)Character_SetZ); + ccAddExternalFunctionForPlugin("Character::get_z", (void*)Character_GetZ); + ccAddExternalFunctionForPlugin("Character::set_z", (void*)Character_SetZ); +} diff --git a/engines/ags/engine/ac/character.h b/engines/ags/engine/ac/character.h new file mode 100644 index 00000000000..cb678fda3a1 --- /dev/null +++ b/engines/ags/engine/ac/character.h @@ -0,0 +1,219 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__CHARACTER_H +#define __AGS_EE_AC__CHARACTER_H + +#include "ac/characterinfo.h" +#include "ac/characterextras.h" +#include "ac/dynobj/scriptobject.h" +#include "ac/dynobj/scriptinvitem.h" +#include "ac/dynobj/scriptoverlay.h" +#include "game/viewport.h" +#include "util/geometry.h" + +// **** CHARACTER: FUNCTIONS **** + +void Character_AddInventory(CharacterInfo *chaa, ScriptInvItem *invi, int addIndex); +void Character_AddWaypoint(CharacterInfo *chaa, int x, int y); +void Character_Animate(CharacterInfo *chaa, int loop, int delay, int repeat, int blocking, int direction); +void Character_ChangeRoomAutoPosition(CharacterInfo *chaa, int room, int newPos); +void Character_ChangeRoom(CharacterInfo *chaa, int room, int x, int y); +void Character_ChangeRoomSetLoop(CharacterInfo *chaa, int room, int x, int y, int direction); +void Character_ChangeView(CharacterInfo *chap, int vii); +void Character_FaceDirection(CharacterInfo *char1, int direction, int blockingStyle); +void Character_FaceCharacter(CharacterInfo *char1, CharacterInfo *char2, int blockingStyle); +void Character_FaceLocation(CharacterInfo *char1, int xx, int yy, int blockingStyle); +void Character_FaceObject(CharacterInfo *char1, ScriptObject *obj, int blockingStyle); +void Character_FollowCharacter(CharacterInfo *chaa, CharacterInfo *tofollow, int distaway, int eagerness); +int Character_IsCollidingWithChar(CharacterInfo *char1, CharacterInfo *char2); +int Character_IsCollidingWithObject(CharacterInfo *chin, ScriptObject *objid); +bool Character_IsInteractionAvailable(CharacterInfo *cchar, int mood); +void Character_LockView(CharacterInfo *chap, int vii); +void Character_LockViewEx(CharacterInfo *chap, int vii, int stopMoving); +void Character_LockViewAligned(CharacterInfo *chap, int vii, int loop, int align); +void Character_LockViewAlignedEx(CharacterInfo *chap, int vii, int loop, int align, int stopMoving); +void Character_LockViewFrame(CharacterInfo *chaa, int view, int loop, int frame); +void Character_LockViewFrameEx(CharacterInfo *chaa, int view, int loop, int frame, int stopMoving); +void Character_LockViewOffset(CharacterInfo *chap, int vii, int xoffs, int yoffs); +void Character_LockViewOffsetEx(CharacterInfo *chap, int vii, int xoffs, int yoffs, int stopMoving); +void Character_LoseInventory(CharacterInfo *chap, ScriptInvItem *invi); +void Character_PlaceOnWalkableArea(CharacterInfo *chap); +void Character_RemoveTint(CharacterInfo *chaa); +int Character_GetHasExplicitTint(CharacterInfo *chaa); +void Character_Say(CharacterInfo *chaa, const char *text); +void Character_SayAt(CharacterInfo *chaa, int x, int y, int width, const char *texx); +ScriptOverlay* Character_SayBackground(CharacterInfo *chaa, const char *texx); +void Character_SetAsPlayer(CharacterInfo *chaa); +void Character_SetIdleView(CharacterInfo *chaa, int iview, int itime); +void Character_SetOption(CharacterInfo *chaa, int flag, int yesorno); +void Character_SetSpeed(CharacterInfo *chaa, int xspeed, int yspeed); +void Character_StopMoving(CharacterInfo *charp); +void Character_Tint(CharacterInfo *chaa, int red, int green, int blue, int opacity, int luminance); +void Character_Think(CharacterInfo *chaa, const char *text); +void Character_UnlockView(CharacterInfo *chaa); +void Character_UnlockViewEx(CharacterInfo *chaa, int stopMoving); +void Character_Walk(CharacterInfo *chaa, int x, int y, int blocking, int direct); +void Character_Move(CharacterInfo *chaa, int x, int y, int blocking, int direct); +void Character_WalkStraight(CharacterInfo *chaa, int xx, int yy, int blocking); + +void Character_RunInteraction(CharacterInfo *chaa, int mood); + +// **** CHARACTER: PROPERTIES **** + +int Character_GetProperty(CharacterInfo *chaa, const char *property); +void Character_GetPropertyText(CharacterInfo *chaa, const char *property, char *bufer); +const char* Character_GetTextProperty(CharacterInfo *chaa, const char *property); + +ScriptInvItem* Character_GetActiveInventory(CharacterInfo *chaa); +void Character_SetActiveInventory(CharacterInfo *chaa, ScriptInvItem* iit); +int Character_GetAnimating(CharacterInfo *chaa); +int Character_GetAnimationSpeed(CharacterInfo *chaa); +void Character_SetAnimationSpeed(CharacterInfo *chaa, int newval); +int Character_GetBaseline(CharacterInfo *chaa); +void Character_SetBaseline(CharacterInfo *chaa, int basel); +int Character_GetBlinkInterval(CharacterInfo *chaa); +void Character_SetBlinkInterval(CharacterInfo *chaa, int interval); +int Character_GetBlinkView(CharacterInfo *chaa); +void Character_SetBlinkView(CharacterInfo *chaa, int vii); +int Character_GetBlinkWhileThinking(CharacterInfo *chaa); +void Character_SetBlinkWhileThinking(CharacterInfo *chaa, int yesOrNo); +int Character_GetBlockingHeight(CharacterInfo *chaa); +void Character_SetBlockingHeight(CharacterInfo *chaa, int hit); +int Character_GetBlockingWidth(CharacterInfo *chaa); +void Character_SetBlockingWidth(CharacterInfo *chaa, int wid); +int Character_GetDiagonalWalking(CharacterInfo *chaa); +void Character_SetDiagonalWalking(CharacterInfo *chaa, int yesorno); +int Character_GetClickable(CharacterInfo *chaa); +void Character_SetClickable(CharacterInfo *chaa, int clik); +int Character_GetID(CharacterInfo *chaa); +int Character_GetFrame(CharacterInfo *chaa); +void Character_SetFrame(CharacterInfo *chaa, int newval); +int Character_GetIdleView(CharacterInfo *chaa); +int Character_GetIInventoryQuantity(CharacterInfo *chaa, int index); +int Character_HasInventory(CharacterInfo *chaa, ScriptInvItem *invi); +void Character_SetIInventoryQuantity(CharacterInfo *chaa, int index, int quant); +int Character_GetIgnoreLighting(CharacterInfo *chaa); +void Character_SetIgnoreLighting(CharacterInfo *chaa, int yesorno); +int Character_GetIgnoreScaling(CharacterInfo *chaa); +void Character_SetIgnoreScaling(CharacterInfo *chaa, int yesorno); +void Character_SetManualScaling(CharacterInfo *chaa, int yesorno); +int Character_GetIgnoreWalkbehinds(CharacterInfo *chaa); +void Character_SetIgnoreWalkbehinds(CharacterInfo *chaa, int yesorno); +int Character_GetMovementLinkedToAnimation(CharacterInfo *chaa); +void Character_SetMovementLinkedToAnimation(CharacterInfo *chaa, int yesorno); +int Character_GetLoop(CharacterInfo *chaa); +void Character_SetLoop(CharacterInfo *chaa, int newval); +int Character_GetMoving(CharacterInfo *chaa); +const char* Character_GetName(CharacterInfo *chaa); +void Character_SetName(CharacterInfo *chaa, const char *newName); +int Character_GetNormalView(CharacterInfo *chaa); +int Character_GetPreviousRoom(CharacterInfo *chaa); +int Character_GetRoom(CharacterInfo *chaa); +int Character_GetScaleMoveSpeed(CharacterInfo *chaa); +void Character_SetScaleMoveSpeed(CharacterInfo *chaa, int yesorno); +int Character_GetScaleVolume(CharacterInfo *chaa); +void Character_SetScaleVolume(CharacterInfo *chaa, int yesorno); +int Character_GetScaling(CharacterInfo *chaa); +void Character_SetScaling(CharacterInfo *chaa, int zoomlevel); +int Character_GetSolid(CharacterInfo *chaa); +void Character_SetSolid(CharacterInfo *chaa, int yesorno); +int Character_GetSpeaking(CharacterInfo *chaa); +int Character_GetSpeechColor(CharacterInfo *chaa); +void Character_SetSpeechColor(CharacterInfo *chaa, int ncol); +void Character_SetSpeechAnimationDelay(CharacterInfo *chaa, int newDelay); +int Character_GetSpeechView(CharacterInfo *chaa); +void Character_SetSpeechView(CharacterInfo *chaa, int vii); +int Character_GetThinkView(CharacterInfo *chaa); +void Character_SetThinkView(CharacterInfo *chaa, int vii); +int Character_GetTransparency(CharacterInfo *chaa); +void Character_SetTransparency(CharacterInfo *chaa, int trans); +int Character_GetTurnBeforeWalking(CharacterInfo *chaa); +void Character_SetTurnBeforeWalking(CharacterInfo *chaa, int yesorno); +int Character_GetView(CharacterInfo *chaa); +int Character_GetWalkSpeedX(CharacterInfo *chaa); +int Character_GetWalkSpeedY(CharacterInfo *chaa); +int Character_GetX(CharacterInfo *chaa); +void Character_SetX(CharacterInfo *chaa, int newval); +int Character_GetY(CharacterInfo *chaa); +void Character_SetY(CharacterInfo *chaa, int newval); +int Character_GetZ(CharacterInfo *chaa); +void Character_SetZ(CharacterInfo *chaa, int newval); +int Character_GetSpeakingFrame(CharacterInfo *chaa); + +//============================================================================= + +struct MoveList; +namespace AGS { namespace Common { class Bitmap; } } +using namespace AGS; // FIXME later + +void animate_character(CharacterInfo *chap, int loopn,int sppd,int rept, int noidleoverride = 0, int direction = 0, int sframe = 0); +void walk_character(int chac,int tox,int toy,int ignwal, bool autoWalkAnims); +int find_looporder_index (int curloop); +// returns 0 to use diagonal, 1 to not +int useDiagonal (CharacterInfo *char1); +// returns 1 normally, or 0 if they only have horizontal animations +int hasUpDownLoops(CharacterInfo *char1); +void start_character_turning (CharacterInfo *chinf, int useloop, int no_diagonal); +void fix_player_sprite(MoveList*cmls,CharacterInfo*chinf); +// Check whether two characters have walked into each other +int has_hit_another_character(int sourceChar); +int doNextCharMoveStep (CharacterInfo *chi, int &char_index, CharacterExtras *chex); +int find_nearest_walkable_area_within(int *xx, int *yy, int range, int step); +void find_nearest_walkable_area (int *xx, int *yy); +void walk_character(int chac,int tox,int toy,int ignwal, bool autoWalkAnims); +void FindReasonableLoopForCharacter(CharacterInfo *chap); +void walk_or_move_character(CharacterInfo *chaa, int x, int y, int blocking, int direct, bool isWalk); +int is_valid_character(int newchar); +int wantMoveNow (CharacterInfo *chi, CharacterExtras *chex); +void setup_player_character(int charid); +void CheckViewFrameForCharacter(CharacterInfo *chi); +Common::Bitmap *GetCharacterImage(int charid, int *isFlipped); +CharacterInfo *GetCharacterAtScreen(int xx, int yy); +// Get character ID at the given room coordinates +int is_pos_on_character(int xx,int yy); +void get_char_blocking_rect(int charid, int *x1, int *y1, int *width, int *y2); +// Check whether the source char has walked onto character ww +int is_char_on_another (int sourceChar, int ww, int*fromxptr, int*cwidptr); +int my_getpixel(Common::Bitmap *blk, int x, int y); +// X and Y co-ordinates must be in 320x200 format +int check_click_on_character(int xx,int yy,int mood); +int is_pos_on_character(int xx,int yy); +void _DisplaySpeechCore(int chid, const char *displbuf); +void _DisplayThoughtCore(int chid, const char *displbuf); +void _displayspeech(const char*texx, int aschar, int xx, int yy, int widd, int isThought); +int get_character_currently_talking(); +void DisplaySpeech(const char*texx, int aschar); +int update_lip_sync(int talkview, int talkloop, int *talkframeptr); + +// Calculates character's bounding box in room coordinates (takes only in-room transform into account) +// use_frame_0 optionally tells to use frame 0 of current loop instead of current frame. +Rect GetCharacterRoomBBox(int charid, bool use_frame_0 = false); +// Find a closest viewport given character is to. Checks viewports in their order in game's array, +// and returns either first viewport character's bounding box intersects with (or rather with its camera), +// or the one that is least far away from its camera; calculated as a perpendicular distance between two AABBs. +PViewport FindNearestViewport(int charid); + +extern CharacterInfo*playerchar; +extern CharacterExtras *charextra; +extern MoveList *mls; +extern int32_t _sc_PlayerCharPtr; + +// order of loops to turn character in circle from down to down +extern int turnlooporder[8]; + +#endif // __AGS_EE_AC__CHARACTER_H diff --git a/engines/ags/engine/ac/charactercache.h b/engines/ags/engine/ac/charactercache.h new file mode 100644 index 00000000000..f4b5c640120 --- /dev/null +++ b/engines/ags/engine/ac/charactercache.h @@ -0,0 +1,35 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__CHARACTERCACHE_H +#define __AGS_EE_AC__CHARACTERCACHE_H + +namespace AGS { namespace Common { class Bitmap; } } +using namespace AGS; // FIXME later + +// stores cached info about the character +struct CharacterCache { + Common::Bitmap *image; + int sppic; + int scaling; + int inUse; + short tintredwas, tintgrnwas, tintbluwas, tintamntwas; + short lightlevwas, tintlightwas; + // no mirroredWas is required, since the code inverts the sprite number +}; + +#endif // __AGS_EE_AC__CHARACTERCACHE_H diff --git a/engines/ags/engine/ac/characterextras.cpp b/engines/ags/engine/ac/characterextras.cpp new file mode 100644 index 00000000000..13c8f813505 --- /dev/null +++ b/engines/ags/engine/ac/characterextras.cpp @@ -0,0 +1,56 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/characterextras.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +void CharacterExtras::ReadFromFile(Stream *in) +{ + in->ReadArrayOfInt16(invorder, MAX_INVORDER); + invorder_count = in->ReadInt16(); + width = in->ReadInt16(); + height = in->ReadInt16(); + zoom = in->ReadInt16(); + xwas = in->ReadInt16(); + ywas = in->ReadInt16(); + tint_r = in->ReadInt16(); + tint_g = in->ReadInt16(); + tint_b = in->ReadInt16(); + tint_level = in->ReadInt16(); + tint_light = in->ReadInt16(); + process_idle_this_time = in->ReadInt8(); + slow_move_counter = in->ReadInt8(); + animwait = in->ReadInt16(); +} + +void CharacterExtras::WriteToFile(Stream *out) +{ + out->WriteArrayOfInt16(invorder, MAX_INVORDER); + out->WriteInt16(invorder_count); + out->WriteInt16(width); + out->WriteInt16(height); + out->WriteInt16(zoom); + out->WriteInt16(xwas); + out->WriteInt16(ywas); + out->WriteInt16(tint_r); + out->WriteInt16(tint_g); + out->WriteInt16(tint_b); + out->WriteInt16(tint_level); + out->WriteInt16(tint_light); + out->WriteInt8(process_idle_this_time); + out->WriteInt8(slow_move_counter); + out->WriteInt16(animwait); +} diff --git a/engines/ags/engine/ac/characterextras.h b/engines/ags/engine/ac/characterextras.h new file mode 100644 index 00000000000..2e9d6e6098d --- /dev/null +++ b/engines/ags/engine/ac/characterextras.h @@ -0,0 +1,52 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__CHARACTEREXTRAS_H +#define __AGS_EE_AC__CHARACTEREXTRAS_H + +#include "ac/runtime_defines.h" + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +struct CharacterExtras { + // UGLY UGLY UGLY!! The CharacterInfo struct size is fixed because it's + // used in the scripts, therefore overflowing stuff has to go here + short invorder[MAX_INVORDER]; + short invorder_count; + // TODO: implement full AABB and keep updated, so that engine could rely on these cached values all time; + // TODO: consider having both fixed AABB and volatile one that changes with animation frame (unless you change how anims work) + short width; + short height; + short zoom; + short xwas; + short ywas; + short tint_r; + short tint_g; + short tint_b; + short tint_level; + short tint_light; + char process_idle_this_time; + char slow_move_counter; + short animwait; + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); +}; + +#endif // __AGS_EE_AC__CHARACTEREXTRAS_H diff --git a/engines/ags/engine/ac/characterinfo_engine.cpp b/engines/ags/engine/ac/characterinfo_engine.cpp new file mode 100644 index 00000000000..8f6f1a5a837 --- /dev/null +++ b/engines/ags/engine/ac/characterinfo_engine.cpp @@ -0,0 +1,508 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/characterinfo.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/character.h" +#include "ac/characterextras.h" +#include "ac/gamestate.h" +#include "ac/global_character.h" +#include "ac/math.h" +#include "ac/viewframe.h" +#include "debug/debug_log.h" +#include "game/roomstruct.h" +#include "main/maindefines_ex.h" // RETURN_CONTINUE +#include "main/update.h" +#include "media/audio/audio_system.h" + +using namespace AGS::Common; + +extern ViewStruct*views; +extern GameSetupStruct game; +extern int displayed_room; +extern GameState play; +extern int char_speaking; +extern RoomStruct thisroom; +extern unsigned int loopcounter; + +#define Random __Rand + +int CharacterInfo::get_effective_y() { + return y - z; +} +int CharacterInfo::get_baseline() { + if (baseline < 1) + return y; + return baseline; +} +int CharacterInfo::get_blocking_top() { + if (blocking_height > 0) + return y - blocking_height / 2; + return y - 2; +} +int CharacterInfo::get_blocking_bottom() { + // the blocking_bottom should be 1 less than the top + height + // since the code does <= checks on it rather than < checks + if (blocking_height > 0) + return (y + (blocking_height + 1) / 2) - 1; + return y + 3; +} + +void CharacterInfo::UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, int &numSheep, int *followingAsSheep) +{ + int res; + + if (on != 1) return; + + // walking + res = update_character_walking(chex); + // [IKM] Yes, it should return! upon getting RETURN_CONTINUE here + if (res == RETURN_CONTINUE) { // [IKM] now, this is one of those places... + return; // must be careful not to screw things up + } + + // Make sure it doesn't flash up a blue cup + if (view < 0) ; + else if (loop >= views[view].numLoops) + loop = 0; + + int doing_nothing = 1; + + update_character_moving(char_index, chex, doing_nothing); + + // [IKM] 2012-06-28: + // Character index value is used to set up some variables in there, so I cannot just cease using it + res = update_character_animating(char_index, doing_nothing); + // [IKM] Yes, it should return! upon getting RETURN_CONTINUE here + if (res == RETURN_CONTINUE) { // [IKM] now, this is one of those places... + return; // must be careful not to screw things up + } + + update_character_follower(char_index, numSheep, followingAsSheep, doing_nothing); + + update_character_idle(chex, doing_nothing); + + chex->process_idle_this_time = 0; +} + +void CharacterInfo::UpdateFollowingExactlyCharacter() +{ + x = game.chars[following].x; + y = game.chars[following].y; + z = game.chars[following].z; + room = game.chars[following].room; + prevroom = game.chars[following].prevroom; + + int usebase = game.chars[following].get_baseline(); + + if (flags & CHF_BEHINDSHEPHERD) + baseline = usebase - 1; + else + baseline = usebase + 1; +} + +int CharacterInfo::update_character_walking(CharacterExtras *chex) +{ + if (walking >= TURNING_AROUND) { + // Currently rotating to correct direction + if (walkwait > 0) walkwait--; + else { + // Work out which direction is next + int wantloop = find_looporder_index(loop) + 1; + // going anti-clockwise, take one before instead + if (walking >= TURNING_BACKWARDS) + wantloop -= 2; + while (1) { + if (wantloop >= 8) + wantloop = 0; + if (wantloop < 0) + wantloop = 7; + if ((turnlooporder[wantloop] >= views[view].numLoops) || + (views[view].loops[turnlooporder[wantloop]].numFrames < 1) || + ((turnlooporder[wantloop] >= 4) && ((flags & CHF_NODIAGONAL)!=0))) { + if (walking >= TURNING_BACKWARDS) + wantloop--; + else + wantloop++; + } + else break; + } + loop = turnlooporder[wantloop]; + walking -= TURNING_AROUND; + // if still turning, wait for next frame + if (walking % TURNING_BACKWARDS >= TURNING_AROUND) + walkwait = animspeed; + else + walking = walking % TURNING_BACKWARDS; + chex->animwait = 0; + } + return RETURN_CONTINUE; + //continue; + } + + return 0; +} + +void CharacterInfo::update_character_moving(int &char_index, CharacterExtras *chex, int &doing_nothing) +{ + if ((walking > 0) && (room == displayed_room)) + { + if (walkwait > 0) walkwait--; + else + { + flags &= ~CHF_AWAITINGMOVE; + + // Move the character + int numSteps = wantMoveNow(this, chex); + + if ((numSteps) && (chex->xwas != INVALID_X)) { + // if the zoom level changed mid-move, the walkcounter + // might not have come round properly - so sort it out + x = chex->xwas; + y = chex->ywas; + chex->xwas = INVALID_X; + } + + int oldxp = x, oldyp = y; + + for (int ff = 0; ff < abs(numSteps); ff++) { + if (doNextCharMoveStep (this, char_index, chex)) + break; + if ((walking == 0) || (walking >= TURNING_AROUND)) + break; + } + + if (numSteps < 0) { + // very small scaling, intersperse the movement + // to stop it being jumpy + chex->xwas = x; + chex->ywas = y; + x = ((x) - oldxp) / 2 + oldxp; + y = ((y) - oldyp) / 2 + oldyp; + } + else if (numSteps > 0) + chex->xwas = INVALID_X; + + if ((flags & CHF_ANTIGLIDE) == 0) + walkwaitcounter++; + } + + if (loop >= views[view].numLoops) + quitprintf("Unable to render character %d (%s) because loop %d does not exist in view %d", index_id, name, loop, view + 1); + + // check don't overflow loop + int framesInLoop = views[view].loops[loop].numFrames; + if (frame > framesInLoop) + { + frame = 1; + + if (framesInLoop < 2) + frame = 0; + + if (framesInLoop < 1) + quitprintf("Unable to render character %d (%s) because there are no frames in loop %d", index_id, name, loop); + } + + if (walking<1) { + chex->process_idle_this_time = 1; + doing_nothing=1; + walkwait=0; + chex->animwait = 0; + // use standing pic + Character_StopMoving(this); + frame = 0; + CheckViewFrameForCharacter(this); + } + else if (chex->animwait > 0) chex->animwait--; + else { + if (flags & CHF_ANTIGLIDE) + walkwaitcounter++; + + if ((flags & CHF_MOVENOTWALK) == 0) + { + frame++; + if (frame >= views[view].loops[loop].numFrames) + { + // end of loop, so loop back round skipping the standing frame + frame = 1; + + if (views[view].loops[loop].numFrames < 2) + frame = 0; + } + + chex->animwait = views[view].loops[loop].frames[frame].speed + animspeed; + + if (flags & CHF_ANTIGLIDE) + walkwait = chex->animwait; + else + walkwait = 0; + + CheckViewFrameForCharacter(this); + } + } + doing_nothing = 0; + } +} + +int CharacterInfo::update_character_animating(int &aa, int &doing_nothing) +{ + // not moving, but animating + // idleleft is <0 while idle view is playing (.animating is 0) + if (((animating != 0) || (idleleft < 0)) && + ((walking == 0) || ((flags & CHF_MOVENOTWALK) != 0)) && + (room == displayed_room)) + { + doing_nothing = 0; + // idle anim doesn't count as doing something + if (idleleft < 0) + doing_nothing = 1; + + if (wait>0) wait--; + else if ((char_speaking == aa) && (game.options[OPT_LIPSYNCTEXT] != 0)) { + // currently talking with lip-sync speech + int fraa = frame; + wait = update_lip_sync (view, loop, &fraa) - 1; + // closed mouth at end of sentence + // NOTE: standard lip-sync is synchronized with text timer, not voice file + if (play.speech_in_post_state || + ((play.messagetime >= 0) && (play.messagetime < play.close_mouth_speech_time))) + frame = 0; + + if (frame != fraa) { + frame = fraa; + CheckViewFrameForCharacter(this); + } + + //continue; + return RETURN_CONTINUE; + } + else { + int oldframe = frame; + if (animating & CHANIM_BACKWARDS) { + frame--; + if (frame < 0) { + // if the previous loop is a Run Next Loop one, go back to it + if ((loop > 0) && + (views[view].loops[loop - 1].RunNextLoop())) { + + loop --; + frame = views[view].loops[loop].numFrames - 1; + } + else if (animating & CHANIM_REPEAT) { + + frame = views[view].loops[loop].numFrames - 1; + + while (views[view].loops[loop].RunNextLoop()) { + loop++; + frame = views[view].loops[loop].numFrames - 1; + } + } + else { + frame++; + animating = 0; + } + } + } + else + frame++; + + if ((aa == char_speaking) && + (play.speech_in_post_state || + ((!play.speech_has_voice) && + (play.close_mouth_speech_time > 0) && + (play.messagetime < play.close_mouth_speech_time)))) { + // finished talking - stop animation + animating = 0; + frame = 0; + } + + if (frame >= views[view].loops[loop].numFrames) { + + if (views[view].loops[loop].RunNextLoop()) + { + if (loop+1 >= views[view].numLoops) + quit("!Animating character tried to overrun last loop in view"); + loop++; + frame=0; + } + else if ((animating & CHANIM_REPEAT)==0) { + animating=0; + frame--; + // end of idle anim + if (idleleft < 0) { + // constant anim, reset (need this cos animating==0) + if (idletime == 0) + frame = 0; + // one-off anim, stop + else { + ReleaseCharacterView(aa); + idleleft=idletime; + } + } + } + else { + frame=0; + // if it's a multi-loop animation, go back to start + if (play.no_multiloop_repeat == 0) { + while ((loop > 0) && + (views[view].loops[loop - 1].RunNextLoop())) + loop--; + } + } + } + wait = views[view].loops[loop].frames[frame].speed; + // idle anim doesn't have speed stored cos animating==0 + if (idleleft < 0) + wait += animspeed+5; + else + wait += (animating >> 8) & 0x00ff; + + if (frame != oldframe) + CheckViewFrameForCharacter(this); + } + } + + return 0; +} + +void CharacterInfo::update_character_follower(int &aa, int &numSheep, int *followingAsSheep, int &doing_nothing) +{ + if ((following >= 0) && (followinfo == FOLLOW_ALWAYSONTOP)) { + // an always-on-top follow + if (numSheep >= MAX_SHEEP) + quit("too many sheep"); + followingAsSheep[numSheep] = aa; + numSheep++; + } + // not moving, but should be following another character + else if ((following >= 0) && (doing_nothing == 1)) { + short distaway=(followinfo >> 8) & 0x00ff; + // no character in this room + if ((game.chars[following].on == 0) || (on == 0)) ; + else if (room < 0) { + room ++; + if (room == 0) { + // appear in the new room + room = game.chars[following].room; + x = play.entered_at_x; + y = play.entered_at_y; + } + } + // wait a bit, so we're not constantly walking + else if (Random(100) < (followinfo & 0x00ff)) ; + // the followed character has changed room + else if ((room != game.chars[following].room) + && (game.chars[following].on == 0)) + ; // do nothing if the player isn't visible + else if (room != game.chars[following].room) { + prevroom = room; + room = game.chars[following].room; + + if (room == displayed_room) { + // only move to the room-entered position if coming into + // the current room + if (play.entered_at_x > (thisroom.Width - 8)) { + x = thisroom.Width+8; + y = play.entered_at_y; + } + else if (play.entered_at_x < 8) { + x = -8; + y = play.entered_at_y; + } + else if (play.entered_at_y > (thisroom.Height - 8)) { + y = thisroom.Height+8; + x = play.entered_at_x; + } + else if (play.entered_at_y < thisroom.Edges.Top+8) { + y = thisroom.Edges.Top+1; + x = play.entered_at_x; + } + else { + // not at one of the edges + // delay for a few seconds to let the player move + room = -play.follow_change_room_timer; + } + if (room >= 0) { + walk_character(aa,play.entered_at_x,play.entered_at_y,1, true); + doing_nothing = 0; + } + } + } + else if (room != displayed_room) { + // if the characetr is following another character and + // neither is in the current room, don't try to move + } + else if ((abs(game.chars[following].x - x) > distaway+30) | + (abs(game.chars[following].y - y) > distaway+30) | + ((followinfo & 0x00ff) == 0)) { + // in same room + int goxoffs=(Random(50)-25); + // make sure he's not standing on top of the other man + if (goxoffs < 0) goxoffs-=distaway; + else goxoffs+=distaway; + walk_character(aa,game.chars[following].x + goxoffs, + game.chars[following].y + (Random(50)-25),0, true); + doing_nothing = 0; + } + } +} + +void CharacterInfo::update_character_idle(CharacterExtras *chex, int &doing_nothing) +{ + // no idle animation, so skip this bit + if (idleview < 1) ; + // currently playing idle anim + else if (idleleft < 0) ; + // not in the current room + else if (room != displayed_room) ; + // they are moving or animating (or the view is locked), so + // reset idle timeout + else if ((doing_nothing == 0) || ((flags & CHF_FIXVIEW) != 0)) + idleleft = idletime; + // count idle time + else if ((loopcounter%40==0) || (chex->process_idle_this_time == 1)) { + idleleft--; + if (idleleft == -1) { + int useloop=loop; + debug_script_log("%s: Now idle (view %d)", scrname, idleview+1); + Character_LockView(this, idleview+1); + // SetCharView resets it to 0 + idleleft = -2; + int maxLoops = views[idleview].numLoops; + // if the char is set to "no diagonal loops", don't try + // to use diagonal idle loops either + if ((maxLoops > 4) && (useDiagonal(this))) + maxLoops = 4; + // If it's not a "swimming"-type idleanim, choose a random loop + // if there arent enough loops to do the current one. + if ((idletime > 0) && (useloop >= maxLoops)) { + do { + useloop = rand() % maxLoops; + // don't select a loop which is a continuation of a previous one + } while ((useloop > 0) && (views[idleview].loops[useloop-1].RunNextLoop())); + } + // Normal idle anim - just reset to loop 0 if not enough to + // use the current one + else if (useloop >= maxLoops) + useloop = 0; + + animate_character(this,useloop, + animspeed+5,(idletime == 0) ? 1 : 0, 1); + + // don't set Animating while the idle anim plays + animating = 0; + } + } // end do idle animation +} diff --git a/engines/ags/engine/ac/datetime.cpp b/engines/ags/engine/ac/datetime.cpp new file mode 100644 index 00000000000..d2f8b230b21 --- /dev/null +++ b/engines/ags/engine/ac/datetime.cpp @@ -0,0 +1,141 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/datetime.h" +#include "platform/base/agsplatformdriver.h" +#include "script/runtimescriptvalue.h" + +ScriptDateTime* DateTime_Now_Core() { + ScriptDateTime *sdt = new ScriptDateTime(); + + platform->GetSystemTime(sdt); + + return sdt; +} + +ScriptDateTime* DateTime_Now() { + ScriptDateTime *sdt = DateTime_Now_Core(); + ccRegisterManagedObject(sdt, sdt); + return sdt; +} + +int DateTime_GetYear(ScriptDateTime *sdt) { + return sdt->year; +} + +int DateTime_GetMonth(ScriptDateTime *sdt) { + return sdt->month; +} + +int DateTime_GetDayOfMonth(ScriptDateTime *sdt) { + return sdt->day; +} + +int DateTime_GetHour(ScriptDateTime *sdt) { + return sdt->hour; +} + +int DateTime_GetMinute(ScriptDateTime *sdt) { + return sdt->minute; +} + +int DateTime_GetSecond(ScriptDateTime *sdt) { + return sdt->second; +} + +int DateTime_GetRawTime(ScriptDateTime *sdt) { + return sdt->rawUnixTime; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// ScriptDateTime* () +RuntimeScriptValue Sc_DateTime_Now(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO(ScriptDateTime, DateTime_Now); +} + +// int (ScriptDateTime *sdt) +RuntimeScriptValue Sc_DateTime_GetYear(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDateTime, DateTime_GetYear); +} + +// int (ScriptDateTime *sdt) +RuntimeScriptValue Sc_DateTime_GetMonth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDateTime, DateTime_GetMonth); +} + +// int (ScriptDateTime *sdt) +RuntimeScriptValue Sc_DateTime_GetDayOfMonth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDateTime, DateTime_GetDayOfMonth); +} + +// int (ScriptDateTime *sdt) +RuntimeScriptValue Sc_DateTime_GetHour(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDateTime, DateTime_GetHour); +} + +// int (ScriptDateTime *sdt) +RuntimeScriptValue Sc_DateTime_GetMinute(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDateTime, DateTime_GetMinute); +} + +// int (ScriptDateTime *sdt) +RuntimeScriptValue Sc_DateTime_GetSecond(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDateTime, DateTime_GetSecond); +} + +// int (ScriptDateTime *sdt) +RuntimeScriptValue Sc_DateTime_GetRawTime(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDateTime, DateTime_GetRawTime); +} + +void RegisterDateTimeAPI() +{ + ccAddExternalStaticFunction("DateTime::get_Now", Sc_DateTime_Now); + ccAddExternalObjectFunction("DateTime::get_DayOfMonth", Sc_DateTime_GetDayOfMonth); + ccAddExternalObjectFunction("DateTime::get_Hour", Sc_DateTime_GetHour); + ccAddExternalObjectFunction("DateTime::get_Minute", Sc_DateTime_GetMinute); + ccAddExternalObjectFunction("DateTime::get_Month", Sc_DateTime_GetMonth); + ccAddExternalObjectFunction("DateTime::get_RawTime", Sc_DateTime_GetRawTime); + ccAddExternalObjectFunction("DateTime::get_Second", Sc_DateTime_GetSecond); + ccAddExternalObjectFunction("DateTime::get_Year", Sc_DateTime_GetYear); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("DateTime::get_Now", (void*)DateTime_Now); + ccAddExternalFunctionForPlugin("DateTime::get_DayOfMonth", (void*)DateTime_GetDayOfMonth); + ccAddExternalFunctionForPlugin("DateTime::get_Hour", (void*)DateTime_GetHour); + ccAddExternalFunctionForPlugin("DateTime::get_Minute", (void*)DateTime_GetMinute); + ccAddExternalFunctionForPlugin("DateTime::get_Month", (void*)DateTime_GetMonth); + ccAddExternalFunctionForPlugin("DateTime::get_RawTime", (void*)DateTime_GetRawTime); + ccAddExternalFunctionForPlugin("DateTime::get_Second", (void*)DateTime_GetSecond); + ccAddExternalFunctionForPlugin("DateTime::get_Year", (void*)DateTime_GetYear); +} diff --git a/engines/ags/engine/ac/datetime.h b/engines/ags/engine/ac/datetime.h new file mode 100644 index 00000000000..76d474ee006 --- /dev/null +++ b/engines/ags/engine/ac/datetime.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__DATETIME_H +#define __AGS_EE_AC__DATETIME_H + +#include "ac/dynobj/scriptdatetime.h" + +ScriptDateTime* DateTime_Now_Core(); +ScriptDateTime* DateTime_Now(); +int DateTime_GetYear(ScriptDateTime *sdt); +int DateTime_GetMonth(ScriptDateTime *sdt); +int DateTime_GetDayOfMonth(ScriptDateTime *sdt); +int DateTime_GetHour(ScriptDateTime *sdt); +int DateTime_GetMinute(ScriptDateTime *sdt); +int DateTime_GetSecond(ScriptDateTime *sdt); +int DateTime_GetRawTime(ScriptDateTime *sdt); + +#endif // __AGS_EE_AC__DATETIME_H diff --git a/engines/ags/engine/ac/dialog.cpp b/engines/ags/engine/ac/dialog.cpp new file mode 100644 index 00000000000..d27fe02f942 --- /dev/null +++ b/engines/ags/engine/ac/dialog.cpp @@ -0,0 +1,1324 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dialog.h" +#include "ac/common.h" +#include "ac/character.h" +#include "ac/characterinfo.h" +#include "ac/dialogtopic.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/gamestate.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_character.h" +#include "ac/global_dialog.h" +#include "ac/global_display.h" +#include "ac/global_game.h" +#include "ac/global_gui.h" +#include "ac/global_room.h" +#include "ac/global_translation.h" +#include "ac/keycode.h" +#include "ac/overlay.h" +#include "ac/mouse.h" +#include "ac/parser.h" +#include "ac/sys_events.h" +#include "ac/string.h" +#include "ac/dynobj/scriptdialogoptionsrendering.h" +#include "ac/dynobj/scriptdrawingsurface.h" +#include "ac/system.h" +#include "debug/debug_log.h" +#include "font/fonts.h" +#include "script/cc_instance.h" +#include "gui/guimain.h" +#include "gui/guitextbox.h" +#include "main/game_run.h" +#include "platform/base/agsplatformdriver.h" +#include "script/script.h" +#include "ac/spritecache.h" +#include "gfx/ddb.h" +#include "gfx/gfx_util.h" +#include "gfx/graphicsdriver.h" +#include "ac/mouse.h" +#include "media/audio/audio_system.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; +extern GameState play; +extern ccInstance *dialogScriptsInst; +extern int in_new_room; +extern CharacterInfo*playerchar; +extern SpriteCache spriteset; +extern AGSPlatformDriver *platform; +extern int cur_mode,cur_cursor; +extern IGraphicsDriver *gfxDriver; + +DialogTopic *dialog; +ScriptDialogOptionsRendering ccDialogOptionsRendering; +ScriptDrawingSurface* dialogOptionsRenderingSurface; + +int said_speech_line; // used while in dialog to track whether screen needs updating + +// Old dialog support +std::vector< std::shared_ptr > old_dialog_scripts; +std::vector old_speech_lines; + +int said_text = 0; +int longestline = 0; + + + + +void Dialog_Start(ScriptDialog *sd) { + RunDialog(sd->id); +} + +#define CHOSE_TEXTPARSER -3053 +#define SAYCHOSEN_USEFLAG 1 +#define SAYCHOSEN_YES 2 +#define SAYCHOSEN_NO 3 + +int Dialog_DisplayOptions(ScriptDialog *sd, int sayChosenOption) +{ + if ((sayChosenOption < 1) || (sayChosenOption > 3)) + quit("!Dialog.DisplayOptions: invalid parameter passed"); + + int chose = show_dialog_options(sd->id, sayChosenOption, (game.options[OPT_RUNGAMEDLGOPTS] != 0)); + if (chose != CHOSE_TEXTPARSER) + { + chose++; + } + return chose; +} + +void Dialog_SetOptionState(ScriptDialog *sd, int option, int newState) { + SetDialogOption(sd->id, option, newState); +} + +int Dialog_GetOptionState(ScriptDialog *sd, int option) { + return GetDialogOption(sd->id, option); +} + +int Dialog_HasOptionBeenChosen(ScriptDialog *sd, int option) +{ + if ((option < 1) || (option > dialog[sd->id].numoptions)) + quit("!Dialog.HasOptionBeenChosen: Invalid option number specified"); + option--; + + if (dialog[sd->id].optionflags[option] & DFLG_HASBEENCHOSEN) + return 1; + return 0; +} + +void Dialog_SetHasOptionBeenChosen(ScriptDialog *sd, int option, bool chosen) +{ + if (option < 1 || option > dialog[sd->id].numoptions) + { + quit("!Dialog.HasOptionBeenChosen: Invalid option number specified"); + } + option--; + if (chosen) + { + dialog[sd->id].optionflags[option] |= DFLG_HASBEENCHOSEN; + } + else + { + dialog[sd->id].optionflags[option] &= ~DFLG_HASBEENCHOSEN; + } +} + +int Dialog_GetOptionCount(ScriptDialog *sd) +{ + return dialog[sd->id].numoptions; +} + +int Dialog_GetShowTextParser(ScriptDialog *sd) +{ + return (dialog[sd->id].topicFlags & DTFLG_SHOWPARSER) ? 1 : 0; +} + +const char* Dialog_GetOptionText(ScriptDialog *sd, int option) +{ + if ((option < 1) || (option > dialog[sd->id].numoptions)) + quit("!Dialog.GetOptionText: Invalid option number specified"); + + option--; + + return CreateNewScriptString(get_translation(dialog[sd->id].optionnames[option])); +} + +int Dialog_GetID(ScriptDialog *sd) { + return sd->id; +} + +//============================================================================= + +#define RUN_DIALOG_STAY -1 +#define RUN_DIALOG_STOP_DIALOG -2 +#define RUN_DIALOG_GOTO_PREVIOUS -4 +// dialog manager stuff + +void get_dialog_script_parameters(unsigned char* &script, unsigned short* param1, unsigned short* param2) +{ + script++; + *param1 = *script; + script++; + *param1 += *script * 256; + script++; + + if (param2) + { + *param2 = *script; + script++; + *param2 += *script * 256; + script++; + } +} + +int run_dialog_script(DialogTopic*dtpp, int dialogID, int offse, int optionIndex) { + said_speech_line = 0; + int result = RUN_DIALOG_STAY; + + if (dialogScriptsInst) + { + char funcName[100]; + sprintf(funcName, "_run_dialog%d", dialogID); + RunTextScriptIParam(dialogScriptsInst, funcName, RuntimeScriptValue().SetInt32(optionIndex)); + result = dialogScriptsInst->returnValue; + } + else + { + // old dialog format + if (offse == -1) + return result; + + unsigned char* script = old_dialog_scripts[dialogID].get() + offse; + + unsigned short param1 = 0; + unsigned short param2 = 0; + bool script_running = true; + + while (script_running) + { + switch (*script) + { + case DCMD_SAY: + get_dialog_script_parameters(script, ¶m1, ¶m2); + + if (param1 == DCHAR_PLAYER) + param1 = game.playercharacter; + + if (param1 == DCHAR_NARRATOR) + Display(get_translation(old_speech_lines[param2])); + else + DisplaySpeech(get_translation(old_speech_lines[param2]), param1); + + said_speech_line = 1; + break; + + case DCMD_OPTOFF: + get_dialog_script_parameters(script, ¶m1, nullptr); + SetDialogOption(dialogID, param1 + 1, 0, true); + break; + + case DCMD_OPTON: + get_dialog_script_parameters(script, ¶m1, nullptr); + SetDialogOption(dialogID, param1 + 1, DFLG_ON, true); + break; + + case DCMD_RETURN: + script_running = false; + break; + + case DCMD_STOPDIALOG: + result = RUN_DIALOG_STOP_DIALOG; + script_running = false; + break; + + case DCMD_OPTOFFFOREVER: + get_dialog_script_parameters(script, ¶m1, nullptr); + SetDialogOption(dialogID, param1 + 1, DFLG_OFFPERM, true); + break; + + case DCMD_RUNTEXTSCRIPT: + get_dialog_script_parameters(script, ¶m1, nullptr); + result = run_dialog_request(param1); + script_running = (result == RUN_DIALOG_STAY); + break; + + case DCMD_GOTODIALOG: + get_dialog_script_parameters(script, ¶m1, nullptr); + result = param1; + script_running = false; + break; + + case DCMD_PLAYSOUND: + get_dialog_script_parameters(script, ¶m1, nullptr); + play_sound(param1); + break; + + case DCMD_ADDINV: + get_dialog_script_parameters(script, ¶m1, nullptr); + add_inventory(param1); + break; + + case DCMD_SETSPCHVIEW: + get_dialog_script_parameters(script, ¶m1, ¶m2); + SetCharacterSpeechView(param1, param2); + break; + + case DCMD_NEWROOM: + get_dialog_script_parameters(script, ¶m1, nullptr); + NewRoom(param1); + in_new_room = 1; + result = RUN_DIALOG_STOP_DIALOG; + script_running = false; + break; + + case DCMD_SETGLOBALINT: + get_dialog_script_parameters(script, ¶m1, ¶m2); + SetGlobalInt(param1, param2); + break; + + case DCMD_GIVESCORE: + get_dialog_script_parameters(script, ¶m1, nullptr); + GiveScore(param1); + break; + + case DCMD_GOTOPREVIOUS: + result = RUN_DIALOG_GOTO_PREVIOUS; + script_running = false; + break; + + case DCMD_LOSEINV: + get_dialog_script_parameters(script, ¶m1, nullptr); + lose_inventory(param1); + break; + + case DCMD_ENDSCRIPT: + result = RUN_DIALOG_STOP_DIALOG; + script_running = false; + break; + } + } + } + + if (in_new_room > 0) + return RUN_DIALOG_STOP_DIALOG; + + if (said_speech_line > 0) { + // the line below fixes the problem with the close-up face remaining on the + // screen after they finish talking; however, it makes the dialog options + // area flicker when going between topics. + DisableInterface(); + UpdateGameOnce(); // redraw the screen to make sure it looks right + EnableInterface(); + // if we're not about to abort the dialog, switch back to arrow + if (result != RUN_DIALOG_STOP_DIALOG) + set_mouse_cursor(CURS_ARROW); + } + + return result; +} + +int write_dialog_options(Bitmap *ds, bool ds_has_alpha, int dlgxp, int curyp, int numdisp, int mouseison, int areawid, + int bullet_wid, int usingfont, DialogTopic*dtop, char*disporder, short*dispyp, + int linespacing, int utextcol, int padding) { + int ww; + + color_t text_color; + for (ww=0;wwoptionflags[disporder[ww]] & DFLG_HASBEENCHOSEN) && + (play.read_dialog_option_colour >= 0)) { + // 'read' colour + text_color = ds->GetCompatibleColor(play.read_dialog_option_colour); + } + else { + // 'unread' colour + text_color = ds->GetCompatibleColor(playerchar->talkcolor); + } + + if (mouseison==ww) { + if (text_color == ds->GetCompatibleColor(utextcol)) + text_color = ds->GetCompatibleColor(13); // the normal colour is the same as highlight col + else text_color = ds->GetCompatibleColor(utextcol); + } + + break_up_text_into_lines(get_translation(dtop->optionnames[disporder[ww]]), Lines, areawid-(2*padding+2+bullet_wid), usingfont); + dispyp[ww]=curyp; + if (game.dialog_bullet > 0) + { + draw_gui_sprite_v330(ds, game.dialog_bullet, dlgxp, curyp, ds_has_alpha); + } + if (game.options[OPT_DIALOGNUMBERED] == kDlgOptNumbering) { + char tempbfr[20]; + int actualpicwid = 0; + if (game.dialog_bullet > 0) + actualpicwid = game.SpriteInfos[game.dialog_bullet].Width+3; + + sprintf (tempbfr, "%d.", ww + 1); + wouttext_outline (ds, dlgxp + actualpicwid, curyp, usingfont, text_color, tempbfr); + } + for (size_t cc=0;ccoptionnames[disporder[i]]), Lines, areawid-(2*padding+2+bullet_wid), usingfont);\ + needheight += getheightoflines(usingfont, Lines.Count()) + data_to_game_coord(game.options[OPT_DIALOGGAP]);\ + }\ + if (parserInput) needheight += parserInput->Height + data_to_game_coord(game.options[OPT_DIALOGGAP]);\ + } + + +void draw_gui_for_dialog_options(Bitmap *ds, GUIMain *guib, int dlgxp, int dlgyp) { + if (guib->BgColor != 0) { + color_t draw_color = ds->GetCompatibleColor(guib->BgColor); + ds->FillRect(Rect(dlgxp, dlgyp, dlgxp + guib->Width, dlgyp + guib->Height), draw_color); + } + if (guib->BgImage > 0) + GfxUtil::DrawSpriteWithTransparency(ds, spriteset[guib->BgImage], dlgxp, dlgyp); +} + +bool get_custom_dialog_options_dimensions(int dlgnum) +{ + ccDialogOptionsRendering.Reset(); + ccDialogOptionsRendering.dialogID = dlgnum; + + getDialogOptionsDimensionsFunc.params[0].SetDynamicObject(&ccDialogOptionsRendering, &ccDialogOptionsRendering); + run_function_on_non_blocking_thread(&getDialogOptionsDimensionsFunc); + + if ((ccDialogOptionsRendering.width > 0) && + (ccDialogOptionsRendering.height > 0)) + { + return true; + } + return false; +} + +#define MAX_TOPIC_HISTORY 50 +#define DLG_OPTION_PARSER 99 + +struct DialogOptions +{ + int dlgnum; + bool runGameLoopsInBackground; + + int dlgxp; + int dlgyp; + int dialog_abs_x; // absolute dialog position on screen + int padding; + int usingfont; + int lineheight; + int linespacing; + int curswas; + int bullet_wid; + int needheight; + IDriverDependantBitmap *ddb; + Bitmap *subBitmap; + GUITextBox *parserInput; + DialogTopic*dtop; + + char disporder[MAXTOPICOPTIONS]; + short dispyp[MAXTOPICOPTIONS]; + + int numdisp; + int chose; + + Bitmap *tempScrn; + int parserActivated; + + int curyp; + bool wantRefresh; + bool usingCustomRendering; + int orixp; + int oriyp; + int areawid; + int is_textwindow; + int dirtyx; + int dirtyy; + int dirtywidth; + int dirtyheight; + + int mouseison; + int mousewason; + + int forecol; + + void Prepare(int _dlgnum, bool _runGameLoopsInBackground); + void Show(); + void Redraw(); + bool Run(); + void Close(); +}; + +void DialogOptions::Prepare(int _dlgnum, bool _runGameLoopsInBackground) +{ + dlgnum = _dlgnum; + runGameLoopsInBackground = _runGameLoopsInBackground; + + dlgyp = get_fixed_pixel_size(160); + usingfont=FONT_NORMAL; + lineheight = getfontheight_outlined(usingfont); + linespacing = getfontspacing_outlined(usingfont); + curswas=cur_cursor; + bullet_wid = 0; + ddb = nullptr; + subBitmap = nullptr; + parserInput = nullptr; + dtop = nullptr; + + if ((dlgnum < 0) || (dlgnum >= game.numdialog)) + quit("!RunDialog: invalid dialog number specified"); + + can_run_delayed_command(); + + play.in_conversation ++; + + update_polled_stuff_if_runtime(); + + if (game.dialog_bullet > 0) + bullet_wid = game.SpriteInfos[game.dialog_bullet].Width+3; + + // numbered options, leave space for the numbers + if (game.options[OPT_DIALOGNUMBERED] == kDlgOptNumbering) + bullet_wid += wgettextwidth_compensate("9. ", usingfont); + + said_text = 0; + + update_polled_stuff_if_runtime(); + + const Rect &ui_view = play.GetUIViewport(); + tempScrn = BitmapHelper::CreateBitmap(ui_view.GetWidth(), ui_view.GetHeight(), game.GetColorDepth()); + + set_mouse_cursor(CURS_ARROW); + + dtop=&dialog[dlgnum]; + + chose=-1; + numdisp=0; + + parserActivated = 0; + if ((dtop->topicFlags & DTFLG_SHOWPARSER) && (play.disable_dialog_parser == 0)) { + parserInput = new GUITextBox(); + parserInput->Height = lineheight + get_fixed_pixel_size(4); + parserInput->SetShowBorder(true); + parserInput->Font = usingfont; + } + + numdisp=0; + for (int i = 0; i < dtop->numoptions; ++i) { + if ((dtop->optionflags[i] & DFLG_ON)==0) continue; + ensure_text_valid_for_font(dtop->optionnames[i], usingfont); + disporder[numdisp]=i; + numdisp++; + } +} + +void DialogOptions::Show() +{ + if (numdisp<1) quit("!DoDialog: all options have been turned off"); + // Don't display the options if there is only one and the parser + // is not enabled. + if (!((numdisp > 1) || (parserInput != nullptr) || (play.show_single_dialog_option))) + { + chose = disporder[0]; // only one choice, so select it + return; + } + + is_textwindow = 0; + forecol = play.dialog_options_highlight_color; + + mouseison=-1; + mousewason=-10; + const Rect &ui_view = play.GetUIViewport(); + dirtyx = 0; + dirtyy = 0; + dirtywidth = ui_view.GetWidth(); + dirtyheight = ui_view.GetHeight(); + usingCustomRendering = false; + + + dlgxp = 1; + if (get_custom_dialog_options_dimensions(dlgnum)) + { + usingCustomRendering = true; + dirtyx = data_to_game_coord(ccDialogOptionsRendering.x); + dirtyy = data_to_game_coord(ccDialogOptionsRendering.y); + dirtywidth = data_to_game_coord(ccDialogOptionsRendering.width); + dirtyheight = data_to_game_coord(ccDialogOptionsRendering.height); + dialog_abs_x = dirtyx; + } + else if (game.options[OPT_DIALOGIFACE] > 0) + { + GUIMain*guib=&guis[game.options[OPT_DIALOGIFACE]]; + if (guib->IsTextWindow()) { + // text-window, so do the QFG4-style speech options + is_textwindow = 1; + forecol = guib->FgColor; + } + else { + dlgxp = guib->X; + dlgyp = guib->Y; + + dirtyx = dlgxp; + dirtyy = dlgyp; + dirtywidth = guib->Width; + dirtyheight = guib->Height; + dialog_abs_x = guib->X; + + areawid=guib->Width - 5; + padding = TEXTWINDOW_PADDING_DEFAULT; + + GET_OPTIONS_HEIGHT + + if (game.options[OPT_DIALOGUPWARDS]) { + // They want the options upwards from the bottom + dlgyp = (guib->Y + guib->Height) - needheight; + } + + } + } + else { + //dlgyp=(play.viewport.GetHeight()-numdisp*txthit)-1; + const Rect &ui_view = play.GetUIViewport(); + areawid= ui_view.GetWidth()-5; + padding = TEXTWINDOW_PADDING_DEFAULT; + GET_OPTIONS_HEIGHT + dlgyp = ui_view.GetHeight() - needheight; + + dirtyx = 0; + dirtyy = dlgyp - 1; + dirtywidth = ui_view.GetWidth(); + dirtyheight = ui_view.GetHeight() - dirtyy; + dialog_abs_x = 0; + } + if (!is_textwindow) + areawid -= data_to_game_coord(play.dialog_options_x) * 2; + + orixp = dlgxp; + oriyp = dlgyp; + wantRefresh = false; + mouseison=-10; + + update_polled_stuff_if_runtime(); + if (!play.mouse_cursor_hidden) + ags_domouse(DOMOUSE_ENABLE); + update_polled_stuff_if_runtime(); + + Redraw(); + while(Run()); + + if (!play.mouse_cursor_hidden) + ags_domouse(DOMOUSE_DISABLE); +} + +void DialogOptions::Redraw() +{ + wantRefresh = true; + + if (usingCustomRendering) + { + tempScrn = recycle_bitmap(tempScrn, game.GetColorDepth(), + data_to_game_coord(ccDialogOptionsRendering.width), + data_to_game_coord(ccDialogOptionsRendering.height)); + } + + tempScrn->ClearTransparent(); + Bitmap *ds = tempScrn; + + dlgxp = orixp; + dlgyp = oriyp; + const Rect &ui_view = play.GetUIViewport(); + + bool options_surface_has_alpha = false; + + if (usingCustomRendering) + { + ccDialogOptionsRendering.surfaceToRenderTo = dialogOptionsRenderingSurface; + ccDialogOptionsRendering.surfaceAccessed = false; + dialogOptionsRenderingSurface->linkedBitmapOnly = tempScrn; + dialogOptionsRenderingSurface->hasAlphaChannel = ccDialogOptionsRendering.hasAlphaChannel; + options_surface_has_alpha = dialogOptionsRenderingSurface->hasAlphaChannel != 0; + + renderDialogOptionsFunc.params[0].SetDynamicObject(&ccDialogOptionsRendering, &ccDialogOptionsRendering); + run_function_on_non_blocking_thread(&renderDialogOptionsFunc); + + if (!ccDialogOptionsRendering.surfaceAccessed) + debug_script_warn("dialog_options_get_dimensions was implemented, but no dialog_options_render function drew anything to the surface"); + + if (parserInput) + { + parserInput->X = data_to_game_coord(ccDialogOptionsRendering.parserTextboxX); + curyp = data_to_game_coord(ccDialogOptionsRendering.parserTextboxY); + areawid = data_to_game_coord(ccDialogOptionsRendering.parserTextboxWidth); + if (areawid == 0) + areawid = tempScrn->GetWidth(); + } + ccDialogOptionsRendering.needRepaint = false; + } + else if (is_textwindow) { + // text window behind the options + areawid = data_to_game_coord(play.max_dialogoption_width); + int biggest = 0; + padding = guis[game.options[OPT_DIALOGIFACE]].Padding; + for (int i = 0; i < numdisp; ++i) { + break_up_text_into_lines(get_translation(dtop->optionnames[disporder[i]]), Lines, areawid-((2*padding+2)+bullet_wid), usingfont); + if (longestline > biggest) + biggest = longestline; + } + if (biggest < areawid - ((2*padding+6)+bullet_wid)) + areawid = biggest + ((2*padding+6)+bullet_wid); + + if (areawid < data_to_game_coord(play.min_dialogoption_width)) { + areawid = data_to_game_coord(play.min_dialogoption_width); + if (play.min_dialogoption_width > play.max_dialogoption_width) + quit("!game.min_dialogoption_width is larger than game.max_dialogoption_width"); + } + + GET_OPTIONS_HEIGHT + + int savedwid = areawid; + int txoffs=0,tyoffs=0,yspos = ui_view.GetHeight()/2-(2*padding+needheight)/2; + int xspos = ui_view.GetWidth()/2 - areawid/2; + // shift window to the right if QG4-style full-screen pic + if ((game.options[OPT_SPEECHTYPE] == 3) && (said_text > 0)) + xspos = (ui_view.GetWidth() - areawid) - get_fixed_pixel_size(10); + + // needs to draw the right text window, not the default + Bitmap *text_window_ds = nullptr; + draw_text_window(&text_window_ds, false, &txoffs,&tyoffs,&xspos,&yspos,&areawid,nullptr,needheight, game.options[OPT_DIALOGIFACE]); + options_surface_has_alpha = guis[game.options[OPT_DIALOGIFACE]].HasAlphaChannel(); + // since draw_text_window incrases the width, restore it + areawid = savedwid; + + dirtyx = xspos; + dirtyy = yspos; + dirtywidth = text_window_ds->GetWidth(); + dirtyheight = text_window_ds->GetHeight(); + dialog_abs_x = txoffs + xspos; + + GfxUtil::DrawSpriteWithTransparency(ds, text_window_ds, xspos, yspos); + // TODO: here we rely on draw_text_window always assigning new bitmap to text_window_ds; + // should make this more explicit + delete text_window_ds; + + // Ignore the dialog_options_x/y offsets when using a text window + txoffs += xspos; + tyoffs += yspos; + dlgyp = tyoffs; + curyp = write_dialog_options(ds, options_surface_has_alpha, txoffs,tyoffs,numdisp,mouseison,areawid,bullet_wid,usingfont,dtop,disporder,dispyp,linespacing,forecol,padding); + if (parserInput) + parserInput->X = txoffs; + } + else { + + if (wantRefresh) { + // redraw the black background so that anti-alias + // fonts don't re-alias themselves + if (game.options[OPT_DIALOGIFACE] == 0) { + color_t draw_color = ds->GetCompatibleColor(16); + ds->FillRect(Rect(0,dlgyp-1, ui_view.GetWidth()-1, ui_view.GetHeight()-1), draw_color); + } + else { + GUIMain* guib = &guis[game.options[OPT_DIALOGIFACE]]; + if (!guib->IsTextWindow()) + draw_gui_for_dialog_options(ds, guib, dlgxp, dlgyp); + } + } + + dirtyx = 0; + dirtywidth = ui_view.GetWidth(); + + if (game.options[OPT_DIALOGIFACE] > 0) + { + // the whole GUI area should be marked dirty in order + // to ensure it gets drawn + GUIMain* guib = &guis[game.options[OPT_DIALOGIFACE]]; + dirtyheight = guib->Height; + dirtyy = dlgyp; + options_surface_has_alpha = guib->HasAlphaChannel(); + } + else + { + dirtyy = dlgyp - 1; + dirtyheight = needheight + 1; + options_surface_has_alpha = false; + } + + dlgxp += data_to_game_coord(play.dialog_options_x); + dlgyp += data_to_game_coord(play.dialog_options_y); + + // if they use a negative dialog_options_y, make sure the + // area gets marked as dirty + if (dlgyp < dirtyy) + dirtyy = dlgyp; + + //curyp = dlgyp + 1; + curyp = dlgyp; + curyp = write_dialog_options(ds, options_surface_has_alpha, dlgxp,curyp,numdisp,mouseison,areawid,bullet_wid,usingfont,dtop,disporder,dispyp,linespacing,forecol,padding); + + /*if (curyp > play.viewport.GetHeight()) { + dlgyp = play.viewport.GetHeight() - (curyp - dlgyp); + ds->FillRect(Rect(0,dlgyp-1,play.viewport.GetWidth()-1,play.viewport.GetHeight()-1); + goto redraw_options; + }*/ + if (parserInput) + parserInput->X = dlgxp; + } + + if (parserInput) { + // Set up the text box, if present + parserInput->Y = curyp + data_to_game_coord(game.options[OPT_DIALOGGAP]); + parserInput->Width = areawid - get_fixed_pixel_size(10); + parserInput->TextColor = playerchar->talkcolor; + if (mouseison == DLG_OPTION_PARSER) + parserInput->TextColor = forecol; + + if (game.dialog_bullet) // the parser X will get moved in a second + { + draw_gui_sprite_v330(ds, game.dialog_bullet, parserInput->X, parserInput->Y, options_surface_has_alpha); + } + + parserInput->Width -= bullet_wid; + parserInput->X += bullet_wid; + + parserInput->Draw(ds); + parserInput->IsActivated = false; + } + + wantRefresh = false; + + update_polled_stuff_if_runtime(); + + subBitmap = recycle_bitmap(subBitmap, tempScrn->GetColorDepth(), dirtywidth, dirtyheight); + subBitmap = ReplaceBitmapWithSupportedFormat(subBitmap); + + update_polled_stuff_if_runtime(); + + if (usingCustomRendering) + { + subBitmap->Blit(tempScrn, 0, 0, 0, 0, tempScrn->GetWidth(), tempScrn->GetHeight()); + invalidate_rect(dirtyx, dirtyy, dirtyx + subBitmap->GetWidth(), dirtyy + subBitmap->GetHeight(), false); + } + else + { + subBitmap->Blit(tempScrn, dirtyx, dirtyy, 0, 0, dirtywidth, dirtyheight); + } + + if ((ddb != nullptr) && + ((ddb->GetWidth() != dirtywidth) || + (ddb->GetHeight() != dirtyheight))) + { + gfxDriver->DestroyDDB(ddb); + ddb = nullptr; + } + + if (ddb == nullptr) + ddb = gfxDriver->CreateDDBFromBitmap(subBitmap, options_surface_has_alpha, false); + else + gfxDriver->UpdateDDBFromBitmap(ddb, subBitmap, options_surface_has_alpha); + + if (runGameLoopsInBackground) + { + render_graphics(ddb, dirtyx, dirtyy); + } +} + +bool DialogOptions::Run() +{ + const bool new_custom_render = usingCustomRendering && game.options[OPT_DIALOGOPTIONSAPI] >= 0; + + if (runGameLoopsInBackground) + { + play.disabled_user_interface++; + UpdateGameOnce(false, ddb, dirtyx, dirtyy); + play.disabled_user_interface--; + } + else + { + update_audio_system_on_game_loop(); + render_graphics(ddb, dirtyx, dirtyy); + } + + if (new_custom_render) + { + runDialogOptionRepExecFunc.params[0].SetDynamicObject(&ccDialogOptionsRendering, &ccDialogOptionsRendering); + run_function_on_non_blocking_thread(&runDialogOptionRepExecFunc); + } + + int gkey; + if (run_service_key_controls(gkey) && !play.IsIgnoringInput()) { + if (parserInput) { + wantRefresh = true; + // type into the parser + if ((gkey == 361) || ((gkey == ' ') && (strlen(parserInput->Text) == 0))) { + // write previous contents into textbox (F3 or Space when box is empty) + for (unsigned int i = strlen(parserInput->Text); i < strlen(play.lastParserEntry); i++) { + parserInput->OnKeyPress(play.lastParserEntry[i]); + } + //ags_domouse(DOMOUSE_DISABLE); + Redraw(); + return true; // continue running loop + } + else if ((gkey >= 32) || (gkey == 13) || (gkey == 8)) { + parserInput->OnKeyPress(gkey); + if (!parserInput->IsActivated) { + //ags_domouse(DOMOUSE_DISABLE); + Redraw(); + return true; // continue running loop + } + } + } + else if (new_custom_render) + { + runDialogOptionKeyPressHandlerFunc.params[0].SetDynamicObject(&ccDialogOptionsRendering, &ccDialogOptionsRendering); + runDialogOptionKeyPressHandlerFunc.params[1].SetInt32(GetKeyForKeyPressCb(gkey)); + run_function_on_non_blocking_thread(&runDialogOptionKeyPressHandlerFunc); + } + // Allow selection of options by keyboard shortcuts + else if (game.options[OPT_DIALOGNUMBERED] >= kDlgOptKeysOnly && + gkey >= '1' && gkey <= '9') + { + gkey -= '1'; + if (gkey < numdisp) { + chose = disporder[gkey]; + return false; // end dialog options running loop + } + } + } + mousewason=mouseison; + mouseison=-1; + if (new_custom_render); // do not automatically detect option under mouse + else if (usingCustomRendering) + { + if ((mousex >= dirtyx) && (mousey >= dirtyy) && + (mousex < dirtyx + tempScrn->GetWidth()) && + (mousey < dirtyy + tempScrn->GetHeight())) + { + getDialogOptionUnderCursorFunc.params[0].SetDynamicObject(&ccDialogOptionsRendering, &ccDialogOptionsRendering); + run_function_on_non_blocking_thread(&getDialogOptionUnderCursorFunc); + + if (!getDialogOptionUnderCursorFunc.atLeastOneImplementationExists) + quit("!The script function dialog_options_get_active is not implemented. It must be present to use a custom dialogue system."); + + mouseison = ccDialogOptionsRendering.activeOptionID; + } + else + { + ccDialogOptionsRendering.activeOptionID = -1; + } + } + else if (mousex >= dialog_abs_x && mousex < (dialog_abs_x + areawid) && + mousey >= dlgyp && mousey < curyp) + { + mouseison=numdisp-1; + for (int i = 0; i < numdisp; ++i) { + if (mousey < dispyp[i]) { mouseison=i-1; break; } + } + if ((mouseison<0) | (mouseison>=numdisp)) mouseison=-1; + } + + if (parserInput != nullptr) { + int relativeMousey = mousey; + if (usingCustomRendering) + relativeMousey -= dirtyy; + + if ((relativeMousey > parserInput->Y) && + (relativeMousey < parserInput->Y + parserInput->Height)) + mouseison = DLG_OPTION_PARSER; + + if (parserInput->IsActivated) + parserActivated = 1; + } + + int mouseButtonPressed = NONE; + int mouseWheelTurn = 0; + if (run_service_mb_controls(mouseButtonPressed, mouseWheelTurn) && mouseButtonPressed >= 0 && + !play.IsIgnoringInput()) + { + if (mouseison < 0 && !new_custom_render) + { + if (usingCustomRendering) + { + runDialogOptionMouseClickHandlerFunc.params[0].SetDynamicObject(&ccDialogOptionsRendering, &ccDialogOptionsRendering); + runDialogOptionMouseClickHandlerFunc.params[1].SetInt32(mouseButtonPressed + 1); + run_function_on_non_blocking_thread(&runDialogOptionMouseClickHandlerFunc); + + if (runDialogOptionMouseClickHandlerFunc.atLeastOneImplementationExists) + { + Redraw(); + return true; // continue running loop + } + } + return true; // continue running loop + } + if (mouseison == DLG_OPTION_PARSER) { + // they clicked the text box + parserActivated = 1; + } + else if (new_custom_render) + { + runDialogOptionMouseClickHandlerFunc.params[0].SetDynamicObject(&ccDialogOptionsRendering, &ccDialogOptionsRendering); + runDialogOptionMouseClickHandlerFunc.params[1].SetInt32(mouseButtonPressed + 1); + run_function_on_non_blocking_thread(&runDialogOptionMouseClickHandlerFunc); + } + else if (usingCustomRendering) + { + chose = mouseison; + return false; // end dialog options running loop + } + else { + chose=disporder[mouseison]; + return false; // end dialog options running loop + } + } + + if (usingCustomRendering) + { + if (mouseWheelTurn != 0) + { + runDialogOptionMouseClickHandlerFunc.params[0].SetDynamicObject(&ccDialogOptionsRendering, &ccDialogOptionsRendering); + runDialogOptionMouseClickHandlerFunc.params[1].SetInt32((mouseWheelTurn < 0) ? 9 : 8); + run_function_on_non_blocking_thread(&runDialogOptionMouseClickHandlerFunc); + + if (!new_custom_render) + { + if (runDialogOptionMouseClickHandlerFunc.atLeastOneImplementationExists) + Redraw(); + return true; // continue running loop + } + } + } + + if (parserActivated) { + // They have selected a custom parser-based option + if (!parserInput->Text.IsEmpty() != 0) { + chose = DLG_OPTION_PARSER; + return false; // end dialog options running loop + } + else { + parserActivated = 0; + parserInput->IsActivated = 0; + } + } + if (mousewason != mouseison) { + //ags_domouse(DOMOUSE_DISABLE); + Redraw(); + return true; // continue running loop + } + if (new_custom_render) + { + if (ccDialogOptionsRendering.chosenOptionID >= 0) + { + chose = ccDialogOptionsRendering.chosenOptionID; + ccDialogOptionsRendering.chosenOptionID = -1; + return false; // end dialog options running loop + } + if (ccDialogOptionsRendering.needRepaint) + { + Redraw(); + return true; // continue running loop + } + } + + update_polled_stuff_if_runtime(); + + if (play.fast_forward == 0) + { + WaitForNextFrame(); + } + + return true; // continue running loop +} + +void DialogOptions::Close() +{ + ags_clear_input_buffer(); + invalidate_screen(); + + if (parserActivated) + { + strcpy (play.lastParserEntry, parserInput->Text); + ParseText (parserInput->Text); + chose = CHOSE_TEXTPARSER; + } + + if (parserInput) { + delete parserInput; + parserInput = nullptr; + } + + if (ddb != nullptr) + gfxDriver->DestroyDDB(ddb); + delete subBitmap; + + set_mouse_cursor(curswas); + // In case it's the QFG4 style dialog, remove the black screen + play.in_conversation--; + remove_screen_overlay(OVER_COMPLETE); + + delete tempScrn; +} + +DialogOptions DlgOpt; + +int show_dialog_options(int _dlgnum, int sayChosenOption, bool _runGameLoopsInBackground) +{ + DlgOpt.Prepare(_dlgnum, _runGameLoopsInBackground); + DlgOpt.Show(); + DlgOpt.Close(); + + int dialog_choice = DlgOpt.chose; + if (dialog_choice != CHOSE_TEXTPARSER) + { + DialogTopic *dialog_topic = DlgOpt.dtop; + int &option_flags = dialog_topic->optionflags[dialog_choice]; + const char *option_name = DlgOpt.dtop->optionnames[dialog_choice]; + + option_flags |= DFLG_HASBEENCHOSEN; + bool sayTheOption = false; + if (sayChosenOption == SAYCHOSEN_YES) + { + sayTheOption = true; + } + else if (sayChosenOption == SAYCHOSEN_USEFLAG) + { + sayTheOption = ((option_flags & DFLG_NOREPEAT) == 0); + } + + if (sayTheOption) + DisplaySpeech(get_translation(option_name), game.playercharacter); + } + + return dialog_choice; +} + +void do_conversation(int dlgnum) +{ + EndSkippingUntilCharStops(); + + // AGS 2.x always makes the mouse cursor visible when displaying a dialog. + if (loaded_game_file_version <= kGameVersion_272) + play.mouse_cursor_hidden = 0; + + int dlgnum_was = dlgnum; + int previousTopics[MAX_TOPIC_HISTORY]; + int numPrevTopics = 0; + DialogTopic *dtop = &dialog[dlgnum]; + + // run the startup script + int tocar = run_dialog_script(dtop, dlgnum, dtop->startupentrypoint, 0); + if ((tocar == RUN_DIALOG_STOP_DIALOG) || + (tocar == RUN_DIALOG_GOTO_PREVIOUS)) + { + // 'stop' or 'goto-previous' from first startup script + remove_screen_overlay(OVER_COMPLETE); + play.in_conversation--; + return; + } + else if (tocar >= 0) + dlgnum = tocar; + + while (dlgnum >= 0) + { + if (dlgnum >= game.numdialog) + quit("!RunDialog: invalid dialog number specified"); + + dtop = &dialog[dlgnum]; + + if (dlgnum != dlgnum_was) + { + // dialog topic changed, so play the startup + // script for the new topic + tocar = run_dialog_script(dtop, dlgnum, dtop->startupentrypoint, 0); + dlgnum_was = dlgnum; + if (tocar == RUN_DIALOG_GOTO_PREVIOUS) { + if (numPrevTopics < 1) { + // goto-previous on first topic -- end dialog + tocar = RUN_DIALOG_STOP_DIALOG; + } + else { + tocar = previousTopics[numPrevTopics - 1]; + numPrevTopics--; + } + } + if (tocar == RUN_DIALOG_STOP_DIALOG) + break; + else if (tocar >= 0) { + // save the old topic number in the history + if (numPrevTopics < MAX_TOPIC_HISTORY) { + previousTopics[numPrevTopics] = dlgnum; + numPrevTopics++; + } + dlgnum = tocar; + continue; + } + } + + int chose = show_dialog_options(dlgnum, SAYCHOSEN_USEFLAG, (game.options[OPT_RUNGAMEDLGOPTS] != 0)); + + if (chose == CHOSE_TEXTPARSER) + { + said_speech_line = 0; + + tocar = run_dialog_request(dlgnum); + + if (said_speech_line > 0) { + // fix the problem with the close-up face remaining on screen + DisableInterface(); + UpdateGameOnce(); // redraw the screen to make sure it looks right + EnableInterface(); + set_mouse_cursor(CURS_ARROW); + } + } + else + { + tocar = run_dialog_script(dtop, dlgnum, dtop->entrypoints[chose], chose + 1); + } + + if (tocar == RUN_DIALOG_GOTO_PREVIOUS) { + if (numPrevTopics < 1) { + tocar = RUN_DIALOG_STOP_DIALOG; + } + else { + tocar = previousTopics[numPrevTopics - 1]; + numPrevTopics--; + } + } + if (tocar == RUN_DIALOG_STOP_DIALOG) break; + else if (tocar >= 0) { + // save the old topic number in the history + if (numPrevTopics < MAX_TOPIC_HISTORY) { + previousTopics[numPrevTopics] = dlgnum; + numPrevTopics++; + } + dlgnum = tocar; + } + + } + +} + +// end dialog manager + + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// int (ScriptDialog *sd) +RuntimeScriptValue Sc_Dialog_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialog, Dialog_GetID); +} + +// int (ScriptDialog *sd) +RuntimeScriptValue Sc_Dialog_GetOptionCount(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialog, Dialog_GetOptionCount); +} + +// int (ScriptDialog *sd) +RuntimeScriptValue Sc_Dialog_GetShowTextParser(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialog, Dialog_GetShowTextParser); +} + +// int (ScriptDialog *sd, int sayChosenOption) +RuntimeScriptValue Sc_Dialog_DisplayOptions(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT(ScriptDialog, Dialog_DisplayOptions); +} + +// int (ScriptDialog *sd, int option) +RuntimeScriptValue Sc_Dialog_GetOptionState(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT(ScriptDialog, Dialog_GetOptionState); +} + +// const char* (ScriptDialog *sd, int option) +RuntimeScriptValue Sc_Dialog_GetOptionText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT(ScriptDialog, const char, myScriptStringImpl, Dialog_GetOptionText); +} + +// int (ScriptDialog *sd, int option) +RuntimeScriptValue Sc_Dialog_HasOptionBeenChosen(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT(ScriptDialog, Dialog_HasOptionBeenChosen); +} + +RuntimeScriptValue Sc_Dialog_SetHasOptionBeenChosen(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT_PBOOL(ScriptDialog, Dialog_SetHasOptionBeenChosen); +} + +// void (ScriptDialog *sd, int option, int newState) +RuntimeScriptValue Sc_Dialog_SetOptionState(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(ScriptDialog, Dialog_SetOptionState); +} + +// void (ScriptDialog *sd) +RuntimeScriptValue Sc_Dialog_Start(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptDialog, Dialog_Start); +} + +void RegisterDialogAPI() +{ + ccAddExternalObjectFunction("Dialog::get_ID", Sc_Dialog_GetID); + ccAddExternalObjectFunction("Dialog::get_OptionCount", Sc_Dialog_GetOptionCount); + ccAddExternalObjectFunction("Dialog::get_ShowTextParser", Sc_Dialog_GetShowTextParser); + ccAddExternalObjectFunction("Dialog::DisplayOptions^1", Sc_Dialog_DisplayOptions); + ccAddExternalObjectFunction("Dialog::GetOptionState^1", Sc_Dialog_GetOptionState); + ccAddExternalObjectFunction("Dialog::GetOptionText^1", Sc_Dialog_GetOptionText); + ccAddExternalObjectFunction("Dialog::HasOptionBeenChosen^1", Sc_Dialog_HasOptionBeenChosen); + ccAddExternalObjectFunction("Dialog::SetHasOptionBeenChosen^2", Sc_Dialog_SetHasOptionBeenChosen); + ccAddExternalObjectFunction("Dialog::SetOptionState^2", Sc_Dialog_SetOptionState); + ccAddExternalObjectFunction("Dialog::Start^0", Sc_Dialog_Start); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Dialog::get_ID", (void*)Dialog_GetID); + ccAddExternalFunctionForPlugin("Dialog::get_OptionCount", (void*)Dialog_GetOptionCount); + ccAddExternalFunctionForPlugin("Dialog::get_ShowTextParser", (void*)Dialog_GetShowTextParser); + ccAddExternalFunctionForPlugin("Dialog::DisplayOptions^1", (void*)Dialog_DisplayOptions); + ccAddExternalFunctionForPlugin("Dialog::GetOptionState^1", (void*)Dialog_GetOptionState); + ccAddExternalFunctionForPlugin("Dialog::GetOptionText^1", (void*)Dialog_GetOptionText); + ccAddExternalFunctionForPlugin("Dialog::HasOptionBeenChosen^1", (void*)Dialog_HasOptionBeenChosen); + ccAddExternalFunctionForPlugin("Dialog::SetOptionState^2", (void*)Dialog_SetOptionState); + ccAddExternalFunctionForPlugin("Dialog::Start^0", (void*)Dialog_Start); +} diff --git a/engines/ags/engine/ac/dialog.h b/engines/ags/engine/ac/dialog.h new file mode 100644 index 00000000000..93fcf4868c2 --- /dev/null +++ b/engines/ags/engine/ac/dialog.h @@ -0,0 +1,39 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__DIALOG_H +#define __AGS_EE_AC__DIALOG_H + +#include +#include "ac/dynobj/scriptdialog.h" + +int Dialog_GetID(ScriptDialog *sd); +int Dialog_GetOptionCount(ScriptDialog *sd); +int Dialog_GetShowTextParser(ScriptDialog *sd); +const char* Dialog_GetOptionText(ScriptDialog *sd, int option); +int Dialog_DisplayOptions(ScriptDialog *sd, int sayChosenOption); +int Dialog_GetOptionState(ScriptDialog *sd, int option); +int Dialog_HasOptionBeenChosen(ScriptDialog *sd, int option); +void Dialog_SetOptionState(ScriptDialog *sd, int option, int newState); +void Dialog_Start(ScriptDialog *sd); + +void do_conversation(int dlgnum); +int show_dialog_options(int dlgnum, int sayChosenOption, bool runGameLoopsInBackground) ; + +extern ScriptDialog *scrDialog; + +#endif // __AGS_EE_AC__DIALOG_H diff --git a/engines/ags/engine/ac/dialogoptionsrendering.cpp b/engines/ags/engine/ac/dialogoptionsrendering.cpp new file mode 100644 index 00000000000..08f2e876abf --- /dev/null +++ b/engines/ags/engine/ac/dialogoptionsrendering.cpp @@ -0,0 +1,332 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dialog.h" +#include "ac/dialogtopic.h" +#include "ac/dialogoptionsrendering.h" +#include "ac/gamestructdefines.h" +#include "debug/debug_log.h" +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_dialog.h" + +extern DialogTopic *dialog; +extern CCDialog ccDynamicDialog; + +// ** SCRIPT DIALOGOPTIONSRENDERING OBJECT + +void DialogOptionsRendering_Update(ScriptDialogOptionsRendering *dlgOptRender) +{ + dlgOptRender->needRepaint = true; +} + +bool DialogOptionsRendering_RunActiveOption(ScriptDialogOptionsRendering *dlgOptRender) +{ + dlgOptRender->chosenOptionID = dlgOptRender->activeOptionID; + return dlgOptRender->chosenOptionID >= 0; +} + +int DialogOptionsRendering_GetX(ScriptDialogOptionsRendering *dlgOptRender) +{ + return dlgOptRender->x; +} + +void DialogOptionsRendering_SetX(ScriptDialogOptionsRendering *dlgOptRender, int newX) +{ + dlgOptRender->x = newX; +} + +int DialogOptionsRendering_GetY(ScriptDialogOptionsRendering *dlgOptRender) +{ + return dlgOptRender->y; +} + +void DialogOptionsRendering_SetY(ScriptDialogOptionsRendering *dlgOptRender, int newY) +{ + dlgOptRender->y = newY; +} + +int DialogOptionsRendering_GetWidth(ScriptDialogOptionsRendering *dlgOptRender) +{ + return dlgOptRender->width; +} + +void DialogOptionsRendering_SetWidth(ScriptDialogOptionsRendering *dlgOptRender, int newWidth) +{ + dlgOptRender->width = newWidth; +} + +int DialogOptionsRendering_GetHeight(ScriptDialogOptionsRendering *dlgOptRender) +{ + return dlgOptRender->height; +} + +void DialogOptionsRendering_SetHeight(ScriptDialogOptionsRendering *dlgOptRender, int newHeight) +{ + dlgOptRender->height = newHeight; +} + +int DialogOptionsRendering_GetHasAlphaChannel(ScriptDialogOptionsRendering *dlgOptRender) +{ + return dlgOptRender->hasAlphaChannel; +} + +void DialogOptionsRendering_SetHasAlphaChannel(ScriptDialogOptionsRendering *dlgOptRender, bool hasAlphaChannel) +{ + dlgOptRender->hasAlphaChannel = hasAlphaChannel; +} + +int DialogOptionsRendering_GetParserTextboxX(ScriptDialogOptionsRendering *dlgOptRender) +{ + return dlgOptRender->parserTextboxX; +} + +void DialogOptionsRendering_SetParserTextboxX(ScriptDialogOptionsRendering *dlgOptRender, int newX) +{ + dlgOptRender->parserTextboxX = newX; +} + +int DialogOptionsRendering_GetParserTextboxY(ScriptDialogOptionsRendering *dlgOptRender) +{ + return dlgOptRender->parserTextboxY; +} + +void DialogOptionsRendering_SetParserTextboxY(ScriptDialogOptionsRendering *dlgOptRender, int newY) +{ + dlgOptRender->parserTextboxY = newY; +} + +int DialogOptionsRendering_GetParserTextboxWidth(ScriptDialogOptionsRendering *dlgOptRender) +{ + return dlgOptRender->parserTextboxWidth; +} + +void DialogOptionsRendering_SetParserTextboxWidth(ScriptDialogOptionsRendering *dlgOptRender, int newWidth) +{ + dlgOptRender->parserTextboxWidth = newWidth; +} + +ScriptDialog* DialogOptionsRendering_GetDialogToRender(ScriptDialogOptionsRendering *dlgOptRender) +{ + return &scrDialog[dlgOptRender->dialogID]; +} + +ScriptDrawingSurface* DialogOptionsRendering_GetSurface(ScriptDialogOptionsRendering *dlgOptRender) +{ + dlgOptRender->surfaceAccessed = true; + return dlgOptRender->surfaceToRenderTo; +} + +int DialogOptionsRendering_GetActiveOptionID(ScriptDialogOptionsRendering *dlgOptRender) +{ + return dlgOptRender->activeOptionID + 1; +} + +void DialogOptionsRendering_SetActiveOptionID(ScriptDialogOptionsRendering *dlgOptRender, int activeOptionID) +{ + int optionCount = dialog[scrDialog[dlgOptRender->dialogID].id].numoptions; + if ((activeOptionID < 0) || (activeOptionID > optionCount)) + quitprintf("DialogOptionsRenderingInfo.ActiveOptionID: invalid ID specified for this dialog (specified %d, valid range: 1..%d)", activeOptionID, optionCount); + + if (dlgOptRender->activeOptionID != activeOptionID - 1) + { + dlgOptRender->activeOptionID = activeOptionID - 1; + dlgOptRender->needRepaint = true; + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +RuntimeScriptValue Sc_DialogOptionsRendering_Update(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptDialogOptionsRendering, DialogOptionsRendering_Update); +} + +RuntimeScriptValue Sc_DialogOptionsRendering_RunActiveOption(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(ScriptDialogOptionsRendering, DialogOptionsRendering_RunActiveOption); +} + +// int (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetActiveOptionID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialogOptionsRendering, DialogOptionsRendering_GetActiveOptionID); +} + +// void (ScriptDialogOptionsRendering *dlgOptRender, int activeOptionID) +RuntimeScriptValue Sc_DialogOptionsRendering_SetActiveOptionID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDialogOptionsRendering, DialogOptionsRendering_SetActiveOptionID); +} + +// ScriptDialog* (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetDialogToRender(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptDialogOptionsRendering, ScriptDialog, ccDynamicDialog, DialogOptionsRendering_GetDialogToRender); +} + +// int (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialogOptionsRendering, DialogOptionsRendering_GetHeight); +} + +// void (ScriptDialogOptionsRendering *dlgOptRender, int newHeight) +RuntimeScriptValue Sc_DialogOptionsRendering_SetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDialogOptionsRendering, DialogOptionsRendering_SetHeight); +} + +// int (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetParserTextboxX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialogOptionsRendering, DialogOptionsRendering_GetParserTextboxX); +} + +// void (ScriptDialogOptionsRendering *dlgOptRender, int newX) +RuntimeScriptValue Sc_DialogOptionsRendering_SetParserTextboxX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDialogOptionsRendering, DialogOptionsRendering_SetParserTextboxX); +} + +// int (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetParserTextboxY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialogOptionsRendering, DialogOptionsRendering_GetParserTextboxY); +} + +// void (ScriptDialogOptionsRendering *dlgOptRender, int newY) +RuntimeScriptValue Sc_DialogOptionsRendering_SetParserTextboxY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDialogOptionsRendering, DialogOptionsRendering_SetParserTextboxY); +} + +// int (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetParserTextboxWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialogOptionsRendering, DialogOptionsRendering_GetParserTextboxWidth); +} + +// void (ScriptDialogOptionsRendering *dlgOptRender, int newWidth) +RuntimeScriptValue Sc_DialogOptionsRendering_SetParserTextboxWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDialogOptionsRendering, DialogOptionsRendering_SetParserTextboxWidth); +} + +// ScriptDrawingSurface* (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetSurface(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJAUTO(ScriptDialogOptionsRendering, ScriptDrawingSurface, DialogOptionsRendering_GetSurface); +} + +// int (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialogOptionsRendering, DialogOptionsRendering_GetWidth); +} + +// void (ScriptDialogOptionsRendering *dlgOptRender, int newWidth) +RuntimeScriptValue Sc_DialogOptionsRendering_SetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDialogOptionsRendering, DialogOptionsRendering_SetWidth); +} + +// int (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialogOptionsRendering, DialogOptionsRendering_GetX); +} + +// void (ScriptDialogOptionsRendering *dlgOptRender, int newX) +RuntimeScriptValue Sc_DialogOptionsRendering_SetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDialogOptionsRendering, DialogOptionsRendering_SetX); +} + +// int (ScriptDialogOptionsRendering *dlgOptRender) +RuntimeScriptValue Sc_DialogOptionsRendering_GetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialogOptionsRendering, DialogOptionsRendering_GetY); +} + +// void (ScriptDialogOptionsRendering *dlgOptRender, int newY) +RuntimeScriptValue Sc_DialogOptionsRendering_SetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDialogOptionsRendering, DialogOptionsRendering_SetY); +} + +RuntimeScriptValue Sc_DialogOptionsRendering_GetHasAlphaChannel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDialogOptionsRendering, DialogOptionsRendering_GetHasAlphaChannel); +} + +RuntimeScriptValue Sc_DialogOptionsRendering_SetHasAlphaChannel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PBOOL(ScriptDialogOptionsRendering, DialogOptionsRendering_SetHasAlphaChannel); +} + + +void RegisterDialogOptionsRenderingAPI() +{ + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::Update^0", Sc_DialogOptionsRendering_Update); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::RunActiveOption^0", Sc_DialogOptionsRendering_RunActiveOption); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_ActiveOptionID", Sc_DialogOptionsRendering_GetActiveOptionID); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_ActiveOptionID", Sc_DialogOptionsRendering_SetActiveOptionID); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_DialogToRender", Sc_DialogOptionsRendering_GetDialogToRender); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_Height", Sc_DialogOptionsRendering_GetHeight); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_Height", Sc_DialogOptionsRendering_SetHeight); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_ParserTextBoxX", Sc_DialogOptionsRendering_GetParserTextboxX); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_ParserTextBoxX", Sc_DialogOptionsRendering_SetParserTextboxX); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_ParserTextBoxY", Sc_DialogOptionsRendering_GetParserTextboxY); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_ParserTextBoxY", Sc_DialogOptionsRendering_SetParserTextboxY); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_ParserTextBoxWidth", Sc_DialogOptionsRendering_GetParserTextboxWidth); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_ParserTextBoxWidth", Sc_DialogOptionsRendering_SetParserTextboxWidth); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_Surface", Sc_DialogOptionsRendering_GetSurface); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_Width", Sc_DialogOptionsRendering_GetWidth); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_Width", Sc_DialogOptionsRendering_SetWidth); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_X", Sc_DialogOptionsRendering_GetX); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_X", Sc_DialogOptionsRendering_SetX); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_Y", Sc_DialogOptionsRendering_GetY); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_Y", Sc_DialogOptionsRendering_SetY); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::get_HasAlphaChannel", Sc_DialogOptionsRendering_GetHasAlphaChannel); + ccAddExternalObjectFunction("DialogOptionsRenderingInfo::set_HasAlphaChannel", Sc_DialogOptionsRendering_SetHasAlphaChannel); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_ActiveOptionID", (void*)DialogOptionsRendering_GetActiveOptionID); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::set_ActiveOptionID", (void*)DialogOptionsRendering_SetActiveOptionID); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_DialogToRender", (void*)DialogOptionsRendering_GetDialogToRender); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_Height", (void*)DialogOptionsRendering_GetHeight); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::set_Height", (void*)DialogOptionsRendering_SetHeight); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_ParserTextBoxX", (void*)DialogOptionsRendering_GetParserTextboxX); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::set_ParserTextBoxX", (void*)DialogOptionsRendering_SetParserTextboxX); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_ParserTextBoxY", (void*)DialogOptionsRendering_GetParserTextboxY); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::set_ParserTextBoxY", (void*)DialogOptionsRendering_SetParserTextboxY); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_ParserTextBoxWidth", (void*)DialogOptionsRendering_GetParserTextboxWidth); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::set_ParserTextBoxWidth", (void*)DialogOptionsRendering_SetParserTextboxWidth); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_Surface", (void*)DialogOptionsRendering_GetSurface); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_Width", (void*)DialogOptionsRendering_GetWidth); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::set_Width", (void*)DialogOptionsRendering_SetWidth); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_X", (void*)DialogOptionsRendering_GetX); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::set_X", (void*)DialogOptionsRendering_SetX); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::get_Y", (void*)DialogOptionsRendering_GetY); + ccAddExternalFunctionForPlugin("DialogOptionsRenderingInfo::set_Y", (void*)DialogOptionsRendering_SetY); +} diff --git a/engines/ags/engine/ac/dialogoptionsrendering.h b/engines/ags/engine/ac/dialogoptionsrendering.h new file mode 100644 index 00000000000..f08366df3d9 --- /dev/null +++ b/engines/ags/engine/ac/dialogoptionsrendering.h @@ -0,0 +1,45 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__DIALOGOPTIONSRENDERING_H +#define __AGS_EE_AC__DIALOGOPTIONSRENDERING_H + +#include "ac/dynobj/scriptdialog.h" +#include "ac/dynobj/scriptdialogoptionsrendering.h" + +int DialogOptionsRendering_GetX(ScriptDialogOptionsRendering *dlgOptRender); +void DialogOptionsRendering_SetX(ScriptDialogOptionsRendering *dlgOptRender, int newX); +int DialogOptionsRendering_GetY(ScriptDialogOptionsRendering *dlgOptRender); +void DialogOptionsRendering_SetY(ScriptDialogOptionsRendering *dlgOptRender, int newY); +int DialogOptionsRendering_GetWidth(ScriptDialogOptionsRendering *dlgOptRender); +void DialogOptionsRendering_SetWidth(ScriptDialogOptionsRendering *dlgOptRender, int newWidth); +int DialogOptionsRendering_GetHeight(ScriptDialogOptionsRendering *dlgOptRender); +void DialogOptionsRendering_SetHeight(ScriptDialogOptionsRendering *dlgOptRender, int newHeight); +int DialogOptionsRendering_GetHasAlphaChannel(ScriptDialogOptionsRendering *dlgOptRender); +void DialogOptionsRendering_SetHasAlphaChannel(ScriptDialogOptionsRendering *dlgOptRender, bool hasAlphaChannel); +int DialogOptionsRendering_GetParserTextboxX(ScriptDialogOptionsRendering *dlgOptRender); +void DialogOptionsRendering_SetParserTextboxX(ScriptDialogOptionsRendering *dlgOptRender, int newX); +int DialogOptionsRendering_GetParserTextboxY(ScriptDialogOptionsRendering *dlgOptRender); +void DialogOptionsRendering_SetParserTextboxY(ScriptDialogOptionsRendering *dlgOptRender, int newY); +int DialogOptionsRendering_GetParserTextboxWidth(ScriptDialogOptionsRendering *dlgOptRender); +void DialogOptionsRendering_SetParserTextboxWidth(ScriptDialogOptionsRendering *dlgOptRender, int newWidth); +ScriptDialog* DialogOptionsRendering_GetDialogToRender(ScriptDialogOptionsRendering *dlgOptRender); +ScriptDrawingSurface* DialogOptionsRendering_GetSurface(ScriptDialogOptionsRendering *dlgOptRender); +int DialogOptionsRendering_GetActiveOptionID(ScriptDialogOptionsRendering *dlgOptRender); +void DialogOptionsRendering_SetActiveOptionID(ScriptDialogOptionsRendering *dlgOptRender, int activeOptionID); + +#endif // __AGS_EE_AC__DIALOGOPTIONSRENDERING_H diff --git a/engines/ags/engine/ac/display.cpp b/engines/ags/engine/ac/display.cpp new file mode 100644 index 00000000000..f31db064140 --- /dev/null +++ b/engines/ags/engine/ac/display.cpp @@ -0,0 +1,832 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include + +#include "ac/display.h" +#include "ac/common.h" +#include "font/agsfontrenderer.h" +#include "font/fonts.h" +#include "ac/character.h" +#include "ac/draw.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_audio.h" +#include "ac/global_game.h" +#include "ac/gui.h" +#include "ac/mouse.h" +#include "ac/overlay.h" +#include "ac/sys_events.h" +#include "ac/screenoverlay.h" +#include "ac/speech.h" +#include "ac/string.h" +#include "ac/system.h" +#include "ac/topbarsettings.h" +#include "debug/debug_log.h" +#include "gfx/blender.h" +#include "gui/guibutton.h" +#include "gui/guimain.h" +#include "main/game_run.h" +#include "platform/base/agsplatformdriver.h" +#include "ac/spritecache.h" +#include "gfx/gfx_util.h" +#include "util/string_utils.h" +#include "ac/mouse.h" +#include "media/audio/audio_system.h" +#include "ac/timer.h" + +using namespace AGS::Common; +using namespace AGS::Common::BitmapHelper; + +extern GameState play; +extern GameSetupStruct game; +extern int longestline; +extern AGSPlatformDriver *platform; +extern int loops_per_character; +extern SpriteCache spriteset; + +int display_message_aschar=0; + + +TopBarSettings topBar; +struct DisplayVars +{ + int lineheight; // font's height of single line + int linespacing; // font's line spacing + int fulltxtheight; // total height of all the text +} disp; + +// Pass yy = -1 to find Y co-ord automatically +// allowShrink = 0 for none, 1 for leftwards, 2 for rightwards +// pass blocking=2 to create permanent overlay +int _display_main(int xx, int yy, int wii, const char *text, int disp_type, int usingfont, int asspch, int isThought, int allowShrink, bool overlayPositionFixed) +{ + const bool use_speech_textwindow = (asspch < 0) && (game.options[OPT_SPEECHTYPE] >= 2); + const bool use_thought_gui = (isThought) && (game.options[OPT_THOUGHTGUI] > 0); + + bool alphaChannel = false; + char todis[STD_BUFFER_SIZE]; + snprintf(todis, STD_BUFFER_SIZE - 1, "%s", text); + int usingGui = -1; + if (use_speech_textwindow) + usingGui = play.speech_textwindow_gui; + else if (use_thought_gui) + usingGui = game.options[OPT_THOUGHTGUI]; + + int padding = get_textwindow_padding(usingGui); + int paddingScaled = get_fixed_pixel_size(padding); + int paddingDoubledScaled = get_fixed_pixel_size(padding * 2); // Just in case screen size does is not neatly divisible by 320x200 + + ensure_text_valid_for_font(todis, usingfont); + break_up_text_into_lines(todis, Lines, wii-2*padding, usingfont); + disp.lineheight = getfontheight_outlined(usingfont); + disp.linespacing= getfontspacing_outlined(usingfont); + disp.fulltxtheight = getheightoflines(usingfont, Lines.Count()); + + // AGS 2.x: If the screen is faded out, fade in again when displaying a message box. + if (!asspch && (loaded_game_file_version <= kGameVersion_272)) + play.screen_is_faded_out = 0; + + // if it's a normal message box and the game was being skipped, + // ensure that the screen is up to date before the message box + // is drawn on top of it + // TODO: is this really necessary anymore? + if ((play.skip_until_char_stops >= 0) && (disp_type == DISPLAYTEXT_MESSAGEBOX)) + render_graphics(); + + EndSkippingUntilCharStops(); + + if (topBar.wantIt) { + // ensure that the window is wide enough to display + // any top bar text + int topBarWid = wgettextwidth_compensate(topBar.text, topBar.font); + topBarWid += data_to_game_coord(play.top_bar_borderwidth + 2) * 2; + if (longestline < topBarWid) + longestline = topBarWid; + // the top bar should behave like DisplaySpeech wrt blocking + disp_type = DISPLAYTEXT_SPEECH; + } + + if (asspch > 0) { + // update the all_buttons_disabled variable in advance + // of the adjust_x/y_for_guis calls + play.disabled_user_interface++; + update_gui_disabled_status(); + play.disabled_user_interface--; + } + + const Rect &ui_view = play.GetUIViewport(); + if (xx == OVR_AUTOPLACE) ; + // centre text in middle of screen + else if (yy<0) yy= ui_view.GetHeight()/2-disp.fulltxtheight/2-padding; + // speech, so it wants to be above the character's head + else if (asspch > 0) { + yy-=disp.fulltxtheight; + if (yy < 5) yy=5; + yy = adjust_y_for_guis (yy); + } + + if (longestline < wii - paddingDoubledScaled) { + // shrink the width of the dialog box to fit the text + int oldWid = wii; + //if ((asspch >= 0) || (allowShrink > 0)) + // If it's not speech, or a shrink is allowed, then shrink it + if ((asspch == 0) || (allowShrink > 0)) + wii = longestline + paddingDoubledScaled; + + // shift the dialog box right to align it, if necessary + if ((allowShrink == 2) && (xx >= 0)) + xx += (oldWid - wii); + } + + if (xx<-1) { + xx=(-xx)-wii/2; + if (xx < 0) + xx = 0; + + xx = adjust_x_for_guis (xx, yy); + + if (xx + wii >= ui_view.GetWidth()) + xx = (ui_view.GetWidth() - wii) - 5; + } + else if (xx<0) xx= ui_view.GetWidth()/2-wii/2; + + int extraHeight = paddingDoubledScaled; + color_t text_color = MakeColor(15); + if (disp_type < DISPLAYTEXT_NORMALOVERLAY) + remove_screen_overlay(OVER_TEXTMSG); // remove any previous blocking texts + + Bitmap *text_window_ds = BitmapHelper::CreateTransparentBitmap((wii > 0) ? wii : 2, disp.fulltxtheight + extraHeight, game.GetColorDepth()); + + // inform draw_text_window to free the old bitmap + const bool wantFreeScreenop = true; + + // + // Creating displayed graphic + // + // may later change if usingGUI, needed to avoid changing original coordinates + int adjustedXX = xx; + int adjustedYY = yy; + + if ((strlen (todis) < 1) || (strcmp (todis, " ") == 0) || (wii == 0)) ; + // if it's an empty speech line, don't draw anything + else if (asspch) { //text_color = ds->GetCompatibleColor(12); + int ttxleft = 0, ttxtop = paddingScaled, oriwid = wii - padding * 2; + int drawBackground = 0; + + if (use_speech_textwindow) { + drawBackground = 1; + } + else if (use_thought_gui) { + // make it treat it as drawing inside a window now + if (asspch > 0) + asspch = -asspch; + drawBackground = 1; + } + + if (drawBackground) + { + draw_text_window_and_bar(&text_window_ds, wantFreeScreenop, &ttxleft, &ttxtop, &adjustedXX, &adjustedYY, &wii, &text_color, 0, usingGui); + if (usingGui > 0) + { + alphaChannel = guis[usingGui].HasAlphaChannel(); + } + } + else if ((ShouldAntiAliasText()) && (game.GetColorDepth() >= 24)) + alphaChannel = true; + + for (size_t ee=0;ee= 0) && + ((game.options[OPT_SPEECHTYPE] >= 2) || (isThought))) + text_color = text_window_ds->GetCompatibleColor(guis[usingGui].FgColor); + else + text_color = text_window_ds->GetCompatibleColor(-asspch); + + wouttext_aligned(text_window_ds, ttxleft, ttyp, oriwid, usingfont, text_color, Lines[ee], play.text_align); + } + else { + text_color = text_window_ds->GetCompatibleColor(asspch); + wouttext_aligned(text_window_ds, ttxleft, ttyp, wii, usingfont, text_color, Lines[ee], play.speech_text_align); + } + } + } + else { + int xoffs,yoffs, oriwid = wii - padding * 2; + draw_text_window_and_bar(&text_window_ds, wantFreeScreenop, &xoffs,&yoffs,&xx,&yy,&wii,&text_color); + + if (game.options[OPT_TWCUSTOM] > 0) + { + alphaChannel = guis[game.options[OPT_TWCUSTOM]].HasAlphaChannel(); + } + + adjust_y_coordinate_for_text(&yoffs, usingfont); + + for (size_t ee=0;ee= OVER_CUSTOM) ovrtype = disp_type; + + int nse = add_screen_overlay(xx, yy, ovrtype, text_window_ds, adjustedXX - xx, adjustedYY - yy, alphaChannel); + // we should not delete text_window_ds here, because it is now owned by Overlay + + if (disp_type >= DISPLAYTEXT_NORMALOVERLAY) { + return screenover[nse].type; + } + + // + // Wait for the blocking text to timeout or until skipped by another command + // + if (disp_type == DISPLAYTEXT_MESSAGEBOX) { + // If fast-forwarding, then skip immediately + if (play.fast_forward) { + remove_screen_overlay(OVER_TEXTMSG); + play.messagetime=-1; + return 0; + } + + if (!play.mouse_cursor_hidden) + ags_domouse(DOMOUSE_ENABLE); + int countdown = GetTextDisplayTime (todis); + int skip_setting = user_to_internal_skip_speech((SkipSpeechStyle)play.skip_display); + // Loop until skipped + while (true) { + update_audio_system_on_game_loop(); + render_graphics(); + int mbut, mwheelz; + if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0) { + check_skip_cutscene_mclick(mbut); + if (play.fast_forward) + break; + if (skip_setting & SKIP_MOUSECLICK && !play.IsIgnoringInput()) + break; + } + int kp; + if (run_service_key_controls(kp)) { + check_skip_cutscene_keypress (kp); + if (play.fast_forward) + break; + if ((skip_setting & SKIP_KEYPRESS) && !play.IsIgnoringInput()) + break; + } + + update_polled_stuff_if_runtime(); + + if (play.fast_forward == 0) + { + WaitForNextFrame(); + } + + countdown--; + + // Special behavior when coupled with a voice-over + if (play.speech_has_voice) { + // extend life of text if the voice hasn't finished yet + if (channel_is_playing(SCHAN_SPEECH) && (play.fast_forward == 0)) { + if (countdown <= 1) + countdown = 1; + } + else // if the voice has finished, remove the speech + countdown = 0; + } + // Test for the timed auto-skip + if ((countdown < 1) && (skip_setting & SKIP_AUTOTIMER)) + { + play.SetIgnoreInput(play.ignore_user_input_after_text_timeout_ms); + break; + } + // if skipping cutscene, don't get stuck on No Auto Remove text boxes + if ((countdown < 1) && (play.fast_forward)) + break; + } + if (!play.mouse_cursor_hidden) + ags_domouse(DOMOUSE_DISABLE); + remove_screen_overlay(OVER_TEXTMSG); + invalidate_screen(); + } + else { + // if the speech does not time out, but we are skipping a cutscene, + // allow it to time out + if ((play.messagetime < 0) && (play.fast_forward)) + play.messagetime = 2; + + if (!overlayPositionFixed) + { + screenover[nse].positionRelativeToScreen = false; + VpPoint vpt = play.GetRoomViewport(0)->ScreenToRoom(screenover[nse].x, screenover[nse].y, false); + screenover[nse].x = vpt.first.X; + screenover[nse].y = vpt.first.Y; + } + + GameLoopUntilNoOverlay(); + } + + play.messagetime=-1; + return 0; +} + +void _display_at(int xx, int yy, int wii, const char *text, int disp_type, int asspch, int isThought, int allowShrink, bool overlayPositionFixed) { + int usingfont=FONT_NORMAL; + if (asspch) usingfont=FONT_SPEECH; + // TODO: _display_at may be called from _displayspeech, which can start + // and finalize voice speech on its own. Find out if we really need to + // keep track of this and not just stop voice regardless. + bool need_stop_speech = false; + + EndSkippingUntilCharStops(); + + if (try_auto_play_speech(text, text, play.narrator_speech, true)) + {// TODO: is there any need for this flag? + need_stop_speech = true; + } + _display_main(xx, yy, wii, text, disp_type, usingfont, asspch, isThought, allowShrink, overlayPositionFixed); + + if (need_stop_speech) + stop_voice_speech(); +} + +bool try_auto_play_speech(const char *text, const char *&replace_text, int charid, bool blocking) +{ + const char *src = text; + if (src[0] != '&') + return false; + + int sndid = atoi(&src[1]); + while ((src[0] != ' ') & (src[0] != 0)) src++; + if (src[0] == ' ') src++; + if (sndid <= 0) + quit("DisplaySpeech: auto-voice symbol '&' not followed by valid integer"); + + replace_text = src; // skip voice tag + if (play_voice_speech(charid, sndid)) + { + // if Voice Only, then blank out the text + if (play.want_speech == 2) + replace_text = " "; + return true; + } + return false; +} + +// TODO: refactor this global variable out; currently it is set at the every get_translation call. +// Be careful: a number of Say/Display functions expect it to be set beforehand. +int source_text_length = -1; + +int GetTextDisplayLength(const char *text) +{ + int len = (int)strlen(text); + if ((text[0] == '&') && (play.unfactor_speech_from_textlength != 0)) + { + // if there's an "&12 text" type line, remove "&12 " from the source length + size_t j = 0; + while ((text[j] != ' ') && (text[j] != 0)) + j++; + j++; + len -= j; + } + return len; +} + +int GetTextDisplayTime(const char *text, int canberel) { + int uselen = 0; + auto fpstimer = ::lround(get_current_fps()); + + // if it's background speech, make it stay relative to game speed + if ((canberel == 1) && (play.bgspeech_game_speed == 1)) + fpstimer = 40; + + if (source_text_length >= 0) { + // sync to length of original text, to make sure any animations + // and music sync up correctly + uselen = source_text_length; + source_text_length = -1; + } + else { + uselen = GetTextDisplayLength(text); + } + + if (uselen <= 0) + return 0; + + if (play.text_speed + play.text_speed_modifier <= 0) + quit("!Text speed is zero; unable to display text. Check your game.text_speed settings."); + + // Store how many game loops per character of text + // This is calculated using a hard-coded 15 for the text speed, + // so that it's always the same no matter how fast the user + // can read. + loops_per_character = (((uselen/play.lipsync_speed)+1) * fpstimer) / uselen; + + int textDisplayTimeInMS = ((uselen / (play.text_speed + play.text_speed_modifier)) + 1) * 1000; + if (textDisplayTimeInMS < play.text_min_display_time_ms) + textDisplayTimeInMS = play.text_min_display_time_ms; + + return (textDisplayTimeInMS * fpstimer) / 1000; +} + +bool ShouldAntiAliasText() { + return (game.options[OPT_ANTIALIASFONTS] != 0); +} + +#if defined (AGS_FONTOUTLINE_MOREOPAQUE) +// TODO: was suggested by fernewelten, but it's unclear whether is necessary +// Make semi-transparent bits much more opaque +void wouttextxy_AutoOutline_Semitransparent2Opaque(Bitmap *map) +{ + if (map->GetColorDepth() < 32) + return; // such maps don't feature partial transparency + size_t const width = map->GetWidth(); + size_t const height = map->GetHeight(); + + for (size_t y = 0; y < height; y++) + { + int32 *sc_line = reinterpret_cast(map->GetScanLineForWriting(y)); + for (size_t x = 0; x < width; x++) + { + int32 &px = sc_line[x]; + int const transparency = geta(px); + if (0 < transparency && transparency < 255) + px = makeacol32( + getr32(px), + getg32(px), + getb32(px), + std::min(85 + transparency * 2, 255)); + } + } +} +#endif + +// Draw outline that is calculated from the text font, not derived from an outline font +void wouttextxy_AutoOutline(Bitmap *ds, size_t font, int32_t color, const char *texx, int &xxp, int &yyp) +{ + int const thickness = game.fonts.at(font).AutoOutlineThickness; + auto const style = game.fonts.at(font).AutoOutlineStyle; + if (thickness <= 0) + return; + + // 16-bit games should use 32-bit stencils to keep anti-aliasing working + int const ds_cd = ds->GetColorDepth(); + bool const antialias = ds_cd >= 16 && game.options[OPT_ANTIALIASFONTS] != 0 && !is_bitmap_font(font); + int const stencil_cd = antialias ? 32 : ds_cd; + if (antialias) // This is to make sure TTFs render proper alpha channel in 16-bit games too + color |= makeacol32(0, 0, 0, 0xff); + + size_t const t_width = wgettextwidth(texx, font); + size_t const t_height = wgettextheight(texx, font); + if (t_width == 0 || t_height == 0) + return; + Bitmap texx_stencil, outline_stencil; + texx_stencil.CreateTransparent(t_width, t_height, stencil_cd); + outline_stencil.CreateTransparent(t_width, t_height + 2 * thickness, stencil_cd); + if (outline_stencil.IsNull() || texx_stencil.IsNull()) + return; + wouttextxy(&texx_stencil, 0, 0, font, color, texx); +#if defined (AGS_FONTOUTLINE_MOREOPAQUE) + wouttextxy_AutoOutline_Semitransparent2Opaque(texx_stencil); +#endif + + void(Bitmap::*pfn_drawstencil)(Bitmap *src, int dst_x, int dst_y); + if (antialias) + { // NOTE: we must set out blender AFTER wouttextxy, or it will be overidden + set_argb2any_blender(); + pfn_drawstencil = &Bitmap::TransBlendBlt; + } + else + { + pfn_drawstencil = &Bitmap::MaskedBlit; + } + + // move start of text so that the outline doesn't drop off the bitmap + xxp += thickness; + int const outline_y = yyp; + yyp += thickness; + + int largest_y_diff_reached_so_far = -1; + for (int x_diff = thickness; x_diff >= 0; x_diff--) + { + // Integer arithmetics: In the following, we use terms k*(k + 1) to account for rounding. + // (k + 0.5)^2 == k*k + 2*k*0.5 + 0.5^2 == k*k + k + 0.25 ==approx. k*(k + 1) + int y_term_limit = thickness * (thickness + 1); + if (FontInfo::kRounded == style) + y_term_limit -= x_diff * x_diff; + + // extend the outline stencil to the top and bottom + for (int y_diff = largest_y_diff_reached_so_far + 1; + y_diff <= thickness && y_diff * y_diff <= y_term_limit; + y_diff++) + { + (outline_stencil.*pfn_drawstencil)(&texx_stencil, 0, thickness - y_diff); + if (y_diff > 0) + (outline_stencil.*pfn_drawstencil)(&texx_stencil, 0, thickness + y_diff); + largest_y_diff_reached_so_far = y_diff; + } + + // stamp the outline stencil to the left and right of the text + (ds->*pfn_drawstencil)(&outline_stencil, xxp - x_diff, outline_y); + if (x_diff > 0) + (ds->*pfn_drawstencil)(&outline_stencil, xxp + x_diff, outline_y); + } +} + +// Draw an outline if requested, then draw the text on top +void wouttext_outline(Common::Bitmap *ds, int xxp, int yyp, int font, color_t text_color, const char *texx) +{ + size_t const text_font = static_cast(font); + // Draw outline (a backdrop) if requested + color_t const outline_color = ds->GetCompatibleColor(play.speech_text_shadow); + int const outline_font = get_font_outline(font); + if (outline_font >= 0) + wouttextxy(ds, xxp, yyp, static_cast(outline_font), outline_color, texx); + else if (outline_font == FONT_OUTLINE_AUTO) + wouttextxy_AutoOutline(ds, text_font, outline_color, texx, xxp, yyp); + else + ; // no outline + + // Draw text on top + wouttextxy(ds, xxp, yyp, text_font, text_color, texx); +} + +void wouttext_aligned(Bitmap *ds, int usexp, int yy, int oriwid, int usingfont, color_t text_color, const char *text, HorAlignment align) { + + if (align & kMAlignHCenter) + usexp = usexp + (oriwid / 2) - (wgettextwidth_compensate(text, usingfont) / 2); + else if (align & kMAlignRight) + usexp = usexp + (oriwid - wgettextwidth_compensate(text, usingfont)); + + wouttext_outline(ds, usexp, yy, usingfont, text_color, (char *)text); +} + +// Get outline's thickness addition to the font's width or height +int get_outline_padding(int font) +{ + if (get_font_outline(font) == FONT_OUTLINE_AUTO) { + return get_font_outline_thickness(font) * 2; + } + return 0; +} + +int getfontheight_outlined(int font) +{ + return getfontheight(font) + get_outline_padding(font); +} + +int getfontspacing_outlined(int font) +{ + return use_default_linespacing(font) ? + getfontheight_outlined(font) : + getfontlinespacing(font); +} + +int getfontlinegap(int font) +{ + return getfontspacing_outlined(font) - getfontheight_outlined(font); +} + +int getheightoflines(int font, int numlines) +{ + return getfontspacing_outlined(font) * (numlines - 1) + getfontheight_outlined(font); +} + +int wgettextwidth_compensate(const char *tex, int font) +{ + return wgettextwidth(tex, font) + get_outline_padding(font); +} + +void do_corner(Bitmap *ds, int sprn, int x, int y, int offx, int offy) { + if (sprn<0) return; + if (spriteset[sprn] == nullptr) + { + sprn = 0; + } + + x = x + offx * game.SpriteInfos[sprn].Width; + y = y + offy * game.SpriteInfos[sprn].Height; + draw_gui_sprite_v330(ds, sprn, x, y); +} + +int get_but_pic(GUIMain*guo,int indx) +{ + int butid = guo->GetControlID(indx); + return butid >= 0 ? guibuts[butid].Image : 0; +} + +void draw_button_background(Bitmap *ds, int xx1,int yy1,int xx2,int yy2,GUIMain*iep) { + color_t draw_color; + if (iep==nullptr) { // standard window + draw_color = ds->GetCompatibleColor(15); + ds->FillRect(Rect(xx1,yy1,xx2,yy2), draw_color); + draw_color = ds->GetCompatibleColor(16); + ds->DrawRect(Rect(xx1,yy1,xx2,yy2), draw_color); + /* draw_color = ds->GetCompatibleColor(opts.tws.backcol); ds->FillRect(Rect(xx1,yy1,xx2,yy2); + draw_color = ds->GetCompatibleColor(opts.tws.ds->GetTextColor()); ds->DrawRect(Rect(xx1+1,yy1+1,xx2-1,yy2-1);*/ + } + else { + if (loaded_game_file_version < kGameVersion_262) // < 2.62 + { + // Color 0 wrongly shows as transparent instead of black + // From the changelog of 2.62: + // - Fixed text windows getting a black background if colour 0 was + // specified, rather than being transparent. + if (iep->BgColor == 0) + iep->BgColor = 16; + } + + if (iep->BgColor >= 0) draw_color = ds->GetCompatibleColor(iep->BgColor); + else draw_color = ds->GetCompatibleColor(0); // black backrgnd behind picture + + if (iep->BgColor > 0) + ds->FillRect(Rect(xx1,yy1,xx2,yy2), draw_color); + + int leftRightWidth = game.SpriteInfos[get_but_pic(iep,4)].Width; + int topBottomHeight = game.SpriteInfos[get_but_pic(iep,6)].Height; + if (iep->BgImage>0) { + if ((loaded_game_file_version <= kGameVersion_272) // 2.xx + && (spriteset[iep->BgImage]->GetWidth() == 1) + && (spriteset[iep->BgImage]->GetHeight() == 1) + && (*((unsigned int*)spriteset[iep->BgImage]->GetData()) == 0x00FF00FF)) + { + // Don't draw fully transparent dummy GUI backgrounds + } + else + { + // offset the background image and clip it so that it is drawn + // such that the border graphics can have a transparent outside + // edge + int bgoffsx = xx1 - leftRightWidth / 2; + int bgoffsy = yy1 - topBottomHeight / 2; + ds->SetClip(Rect(bgoffsx, bgoffsy, xx2 + leftRightWidth / 2, yy2 + topBottomHeight / 2)); + int bgfinishx = xx2; + int bgfinishy = yy2; + int bgoffsyStart = bgoffsy; + while (bgoffsx <= bgfinishx) + { + bgoffsy = bgoffsyStart; + while (bgoffsy <= bgfinishy) + { + draw_gui_sprite_v330(ds, iep->BgImage, bgoffsx, bgoffsy); + bgoffsy += game.SpriteInfos[iep->BgImage].Height; + } + bgoffsx += game.SpriteInfos[iep->BgImage].Width; + } + // return to normal clipping rectangle + ds->SetClip(Rect(0, 0, ds->GetWidth() - 1, ds->GetHeight() - 1)); + } + } + int uu; + for (uu=yy1;uu <= yy2;uu+= game.SpriteInfos[get_but_pic(iep,4)].Height) { + do_corner(ds, get_but_pic(iep,4),xx1,uu,-1,0); // left side + do_corner(ds, get_but_pic(iep,5),xx2+1,uu,0,0); // right side + } + for (uu=xx1;uu <= xx2;uu+=game.SpriteInfos[get_but_pic(iep,6)].Width) { + do_corner(ds, get_but_pic(iep,6),uu,yy1,0,-1); // top side + do_corner(ds, get_but_pic(iep,7),uu,yy2+1,0,0); // bottom side + } + do_corner(ds, get_but_pic(iep,0),xx1,yy1,-1,-1); // top left + do_corner(ds, get_but_pic(iep,1),xx1,yy2+1,-1,0); // bottom left + do_corner(ds, get_but_pic(iep,2),xx2+1,yy1,0,-1); // top right + do_corner(ds, get_but_pic(iep,3),xx2+1,yy2+1,0,0); // bottom right + } +} + +// Calculate the width that the left and right border of the textwindow +// GUI take up +int get_textwindow_border_width (int twgui) { + if (twgui < 0) + return 0; + + if (!guis[twgui].IsTextWindow()) + quit("!GUI set as text window but is not actually a text window GUI"); + + int borwid = game.SpriteInfos[get_but_pic(&guis[twgui], 4)].Width + + game.SpriteInfos[get_but_pic(&guis[twgui], 5)].Width; + + return borwid; +} + +// get the hegiht of the text window's top border +int get_textwindow_top_border_height (int twgui) { + if (twgui < 0) + return 0; + + if (!guis[twgui].IsTextWindow()) + quit("!GUI set as text window but is not actually a text window GUI"); + + return game.SpriteInfos[get_but_pic(&guis[twgui], 6)].Height; +} + +// Get the padding for a text window +// -1 for the game's custom text window +int get_textwindow_padding(int ifnum) { + int result; + + if (ifnum < 0) + ifnum = game.options[OPT_TWCUSTOM]; + if (ifnum > 0 && ifnum < game.numgui) + result = guis[ifnum].Padding; + else + result = TEXTWINDOW_PADDING_DEFAULT; + + return result; +} + +void draw_text_window(Bitmap **text_window_ds, bool should_free_ds, + int*xins,int*yins,int*xx,int*yy,int*wii, color_t *set_text_color, int ovrheight, int ifnum) { + + Bitmap *ds = *text_window_ds; + if (ifnum < 0) + ifnum = game.options[OPT_TWCUSTOM]; + + if (ifnum <= 0) { + if (ovrheight) + quit("!Cannot use QFG4 style options without custom text window"); + draw_button_background(ds, 0,0,ds->GetWidth() - 1,ds->GetHeight() - 1,nullptr); + if (set_text_color) + *set_text_color = ds->GetCompatibleColor(16); + xins[0]=3; + yins[0]=3; + } + else { + if (ifnum >= game.numgui) + quitprintf("!Invalid GUI %d specified as text window (total GUIs: %d)", ifnum, game.numgui); + if (!guis[ifnum].IsTextWindow()) + quit("!GUI set as text window but is not actually a text window GUI"); + + int tbnum = get_but_pic(&guis[ifnum], 0); + + wii[0] += get_textwindow_border_width (ifnum); + xx[0]-=game.SpriteInfos[tbnum].Width; + yy[0]-=game.SpriteInfos[tbnum].Height; + if (ovrheight == 0) + ovrheight = disp.fulltxtheight; + + if (should_free_ds) + delete *text_window_ds; + int padding = get_textwindow_padding(ifnum); + *text_window_ds = BitmapHelper::CreateTransparentBitmap(wii[0],ovrheight+(padding*2)+ game.SpriteInfos[tbnum].Height*2,game.GetColorDepth()); + ds = *text_window_ds; + int xoffs=game.SpriteInfos[tbnum].Width,yoffs= game.SpriteInfos[tbnum].Height; + draw_button_background(ds, xoffs,yoffs,(ds->GetWidth() - xoffs) - 1,(ds->GetHeight() - yoffs) - 1,&guis[ifnum]); + if (set_text_color) + *set_text_color = ds->GetCompatibleColor(guis[ifnum].FgColor); + xins[0]=xoffs+padding; + yins[0]=yoffs+padding; + } +} + +void draw_text_window_and_bar(Bitmap **text_window_ds, bool should_free_ds, + int*xins,int*yins,int*xx,int*yy,int*wii,color_t *set_text_color,int ovrheight, int ifnum) { + + draw_text_window(text_window_ds, should_free_ds, xins, yins, xx, yy, wii, set_text_color, ovrheight, ifnum); + + if ((topBar.wantIt) && (text_window_ds && *text_window_ds)) { + // top bar on the dialog window with character's name + // create an enlarged window, then free the old one + Bitmap *ds = *text_window_ds; + Bitmap *newScreenop = BitmapHelper::CreateBitmap(ds->GetWidth(), ds->GetHeight() + topBar.height, game.GetColorDepth()); + newScreenop->Blit(ds, 0, 0, 0, topBar.height, ds->GetWidth(), ds->GetHeight()); + delete *text_window_ds; + *text_window_ds = newScreenop; + ds = *text_window_ds; + + // draw the top bar + color_t draw_color = ds->GetCompatibleColor(play.top_bar_backcolor); + ds->FillRect(Rect(0, 0, ds->GetWidth() - 1, topBar.height - 1), draw_color); + if (play.top_bar_backcolor != play.top_bar_bordercolor) { + // draw the border + draw_color = ds->GetCompatibleColor(play.top_bar_bordercolor); + for (int j = 0; j < data_to_game_coord(play.top_bar_borderwidth); j++) + ds->DrawRect(Rect(j, j, ds->GetWidth() - (j + 1), topBar.height - (j + 1)), draw_color); + } + + // draw the text + int textx = (ds->GetWidth() / 2) - wgettextwidth_compensate(topBar.text, topBar.font) / 2; + color_t text_color = ds->GetCompatibleColor(play.top_bar_textcolor); + wouttext_outline(ds, textx, play.top_bar_borderwidth + get_fixed_pixel_size(1), topBar.font, text_color, topBar.text); + + // don't draw it next time + topBar.wantIt = 0; + // adjust the text Y position + yins[0] += topBar.height; + } + else if (topBar.wantIt) + topBar.wantIt = 0; +} diff --git a/engines/ags/engine/ac/display.h b/engines/ags/engine/ac/display.h new file mode 100644 index 00000000000..68b00bfdad2 --- /dev/null +++ b/engines/ags/engine/ac/display.h @@ -0,0 +1,80 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__DISPLAY_H +#define __AGS_EE_AC__DISPLAY_H + +#include "gui/guimain.h" + +using AGS::Common::GUIMain; + +// options for 'disp_type' parameter +#define DISPLAYTEXT_SPEECH 0 +#define DISPLAYTEXT_MESSAGEBOX 1 +#define DISPLAYTEXT_NORMALOVERLAY 2 +// also accepts explicit overlay ID >= OVER_CUSTOM + +int _display_main(int xx, int yy, int wii, const char *text, int disp_type, int usingfont, int asspch, int isThought, int allowShrink, bool overlayPositionFixed); +void _display_at(int xx, int yy, int wii, const char *text, int disp_type, int asspch, int isThought, int allowShrink, bool overlayPositionFixed); +// Tests the given string for the voice-over tags and plays cue clip for the given character; +// will assign replacement string, which will be blank string if game is in "voice-only" mode +// and clip was started, or string cleaned from voice-over tags which is safe to display on screen. +// Returns whether voice-over clip was started successfully. +bool try_auto_play_speech(const char *text, const char *&replace_text, int charid, bool blocking); +bool ShouldAntiAliasText(); +// Calculates meaningful length of the displayed text +int GetTextDisplayLength(const char *text); +// Calculates number of game loops for displaying a text on screen +int GetTextDisplayTime(const char *text, int canberel = 0); +// Draw an outline if requested, then draw the text on top +void wouttext_outline(Common::Bitmap *ds, int xxp, int yyp, int usingfont, color_t text_color, const char *texx); +void wouttext_aligned (Common::Bitmap *ds, int usexp, int yy, int oriwid, int usingfont, color_t text_color, const char *text, HorAlignment align); +// TODO: GUI classes located in Common library do not make use of outlining, +// need to find a way to make all code use same functions. +// Get the maximal height of the given font, with corresponding outlining +int getfontheight_outlined(int font); +// Get line spacing for the given font, with possible outlining in mind +int getfontspacing_outlined(int font); +// Get the distance between bottom one one line and top of the next line (may be negative!) +int getfontlinegap(int font); +// Gets the total maximal height of the given number of lines printed with the given font +int getheightoflines(int font, int numlines); +// Get the maximal width of the given font, with corresponding outlining +int wgettextwidth_compensate(const char *tex, int font); +void do_corner(Common::Bitmap *ds, int sprn,int xx1,int yy1,int typx,int typy); +// Returns the image of a button control on the GUI under given child index +int get_but_pic(GUIMain*guo,int indx); +void draw_button_background(Common::Bitmap *ds, int xx1,int yy1,int xx2,int yy2,GUIMain*iep); +// Calculate the width that the left and right border of the textwindow +// GUI take up +int get_textwindow_border_width (int twgui); +// get the hegiht of the text window's top border +int get_textwindow_top_border_height (int twgui); +// draw_text_window: draws the normal or custom text window +// create a new bitmap the size of the window before calling, and +// point text_window_ds to it +// returns text start x & y pos in parameters +// Warning!: draw_text_window() and draw_text_window_and_bar() can create new text_window_ds +void draw_text_window(Common::Bitmap **text_window_ds, bool should_free_ds, int*xins,int*yins,int*xx,int*yy,int*wii,color_t *set_text_color,int ovrheight, int ifnum); +void draw_text_window_and_bar(Common::Bitmap **text_window_ds, bool should_free_ds, + int*xins,int*yins,int*xx,int*yy,int*wii,color_t *set_text_color,int ovrheight=0, int ifnum=-1); +int get_textwindow_padding(int ifnum); + +// The efficient length of the last source text prepared for display +extern int source_text_length; + +#endif // __AGS_EE_AC__DISPLAY_H diff --git a/engines/ags/engine/ac/draw.cpp b/engines/ags/engine/ac/draw.cpp new file mode 100644 index 00000000000..fe24f1c4534 --- /dev/null +++ b/engines/ags/engine/ac/draw.cpp @@ -0,0 +1,2563 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "aastr.h" +#include "core/platform.h" +#include "ac/common.h" +#include "util/compress.h" +#include "ac/view.h" +#include "ac/charactercache.h" +#include "ac/characterextras.h" +#include "ac/characterinfo.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/draw_software.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_game.h" +#include "ac/global_gui.h" +#include "ac/global_region.h" +#include "ac/gui.h" +#include "ac/mouse.h" +#include "ac/objectcache.h" +#include "ac/overlay.h" +#include "ac/sys_events.h" +#include "ac/roomobject.h" +#include "ac/roomstatus.h" +#include "ac/runtime_defines.h" +#include "ac/screenoverlay.h" +#include "ac/sprite.h" +#include "ac/spritelistentry.h" +#include "ac/string.h" +#include "ac/system.h" +#include "ac/viewframe.h" +#include "ac/walkablearea.h" +#include "ac/walkbehind.h" +#include "ac/dynobj/scriptsystem.h" +#include "debug/debugger.h" +#include "debug/debug_log.h" +#include "font/fonts.h" +#include "gui/guimain.h" +#include "platform/base/agsplatformdriver.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "ac/spritecache.h" +#include "gfx/gfx_util.h" +#include "gfx/graphicsdriver.h" +#include "gfx/ali3dexception.h" +#include "gfx/blender.h" +#include "media/audio/audio_system.h" +#include "ac/game.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +#if AGS_PLATFORM_OS_ANDROID +#include +#include + +extern "C" void android_render(); +#endif + +#if AGS_PLATFORM_OS_IOS +extern "C" void ios_render(); +#endif + +extern GameSetup usetup; +extern GameSetupStruct game; +extern GameState play; +extern int convert_16bit_bgr; +extern ScriptSystem scsystem; +extern AGSPlatformDriver *platform; +extern RoomStruct thisroom; +extern char noWalkBehindsAtAll; +extern unsigned int loopcounter; +extern char *walkBehindExists; // whether a WB area is in this column +extern int *walkBehindStartY, *walkBehindEndY; +extern int walkBehindLeft[MAX_WALK_BEHINDS], walkBehindTop[MAX_WALK_BEHINDS]; +extern int walkBehindRight[MAX_WALK_BEHINDS], walkBehindBottom[MAX_WALK_BEHINDS]; +extern IDriverDependantBitmap *walkBehindBitmap[MAX_WALK_BEHINDS]; +extern int walkBehindsCachedForBgNum; +extern WalkBehindMethodEnum walkBehindMethod; +extern int walk_behind_baselines_changed; +extern SpriteCache spriteset; +extern RoomStatus*croom; +extern int our_eip; +extern int in_new_room; +extern RoomObject*objs; +extern ViewStruct*views; +extern CharacterCache *charcache; +extern ObjectCache objcache[MAX_ROOM_OBJECTS]; +extern int displayed_room; +extern CharacterExtras *charextra; +extern CharacterInfo*playerchar; +extern int eip_guinum; +extern int is_complete_overlay; +extern int cur_mode,cur_cursor; +extern int mouse_frame,mouse_delay; +extern int lastmx,lastmy; +extern IDriverDependantBitmap *mouseCursor; +extern int hotx,hoty; +extern int bg_just_changed; + +color palette[256]; + +COLOR_MAP maincoltable; + +IGraphicsDriver *gfxDriver; +IDriverDependantBitmap *blankImage = nullptr; +IDriverDependantBitmap *blankSidebarImage = nullptr; +IDriverDependantBitmap *debugConsole = nullptr; + +// actsps is used for temporary storage of the bitamp image +// of the latest version of the sprite +int actSpsCount = 0; +Bitmap **actsps; +IDriverDependantBitmap* *actspsbmp; +// temporary cache of walk-behind for this actsps image +Bitmap **actspswb; +IDriverDependantBitmap* *actspswbbmp; +CachedActSpsData* actspswbcache; + +bool current_background_is_dirty = false; + +// Room background sprite +IDriverDependantBitmap* roomBackgroundBmp = nullptr; +// Buffer and info flags for viewport/camera pairs rendering in software mode +struct RoomCameraDrawData +{ + // Intermediate bitmap for the software drawing method. + // We use this bitmap in case room camera has scaling enabled, we draw dirty room rects on it, + // and then pass to software renderer which draws sprite on top and then either blits or stretch-blits + // to the virtual screen. + // For more details see comment in ALSoftwareGraphicsDriver::RenderToBackBuffer(). + PBitmap Buffer; // this is the actual bitmap + PBitmap Frame; // this is either same bitmap reference or sub-bitmap of virtual screen + bool IsOffscreen; // whether room viewport was offscreen (cannot use sub-bitmap) + bool IsOverlap; // whether room viewport overlaps any others (marking dirty rects is complicated) +}; +std::vector CameraDrawData; + +std::vector sprlist; +std::vector thingsToDrawList; + +Bitmap **guibg = nullptr; +IDriverDependantBitmap **guibgbmp = nullptr; + + +Bitmap *debugConsoleBuffer = nullptr; + +// whether there are currently remnants of a DisplaySpeech +bool screen_is_dirty = false; + +Bitmap *raw_saved_screen = nullptr; +Bitmap *dynamicallyCreatedSurfaces[MAX_DYNAMIC_SURFACES]; + + +SpriteListEntry::SpriteListEntry() + : bmp(nullptr) + , pic(nullptr) + , baseline(0), x(0), y(0) + , transparent(0) + , takesPriorityIfEqual(false), hasAlphaChannel(false) +{ +} + +void setpal() { + set_palette_range(palette, 0, 255, 0); +} + +int _places_r = 3, _places_g = 2, _places_b = 3; + +// convert RGB to BGR for strange graphics cards +Bitmap *convert_16_to_16bgr(Bitmap *tempbl) { + + int x,y; + unsigned short c,r,ds,b; + + for (y=0; y < tempbl->GetHeight(); y++) { + unsigned short*p16 = (unsigned short *)tempbl->GetScanLine(y); + + for (x=0; x < tempbl->GetWidth(); x++) { + c = p16[x]; + if (c != MASK_COLOR_16) { + b = _rgb_scale_5[c & 0x1F]; + ds = _rgb_scale_6[(c >> 5) & 0x3F]; + r = _rgb_scale_5[(c >> 11) & 0x1F]; + // allegro assumes 5-6-5 for 16-bit + p16[x] = (((r >> _places_r) << _rgb_r_shift_16) | + ((ds >> _places_g) << _rgb_g_shift_16) | + ((b >> _places_b) << _rgb_b_shift_16)); + + } + } + } + + return tempbl; +} + +// PSP: convert 32 bit RGB to BGR. +Bitmap *convert_32_to_32bgr(Bitmap *tempbl) { + + int i = 0; + int j = 0; + unsigned char* current; + while (i < tempbl->GetHeight()) + { + current = tempbl->GetScanLineForWriting(i); + while (j < tempbl->GetWidth()) + { + current[0] ^= current[2]; + current[2] ^= current[0]; + current[0] ^= current[2]; + current += 4; + j++; + } + i++; + j = 0; + } + + return tempbl; +} + +// NOTE: Some of these conversions are required even when using +// D3D and OpenGL rendering, for two reasons: +// 1) certain raw drawing operations are still performed by software +// Allegro methods, hence bitmaps should be kept compatible to any native +// software operations, such as blitting two bitmaps of different formats. +// 2) mobile ports feature an OpenGL renderer built in Allegro library, +// that assumes native bitmaps are in OpenGL-compatible format, so that it +// could copy them to texture without additional changes. +// AGS own OpenGL renderer tries to sync its behavior with the former one. +// +// TODO: make gfxDriver->GetCompatibleBitmapFormat describe all necessary +// conversions, so that we did not have to guess. +// +Bitmap *AdjustBitmapForUseWithDisplayMode(Bitmap* bitmap, bool has_alpha) +{ + const int bmp_col_depth = bitmap->GetColorDepth(); + const int sys_col_depth = System_GetColorDepth(); + const int game_col_depth = game.GetColorDepth(); + Bitmap *new_bitmap = bitmap; + + // + // The only special case when bitmap needs to be prepared for graphics driver + // + // In 32-bit display mode, 32-bit bitmaps may require component conversion + // to match graphics driver expectation about pixel format. + // TODO: make GetCompatibleBitmapFormat tell this somehow +#if defined (AGS_INVERTED_COLOR_ORDER) + if (sys_col_depth > 16 && bmp_col_depth == 32) + { + // Convert RGB to BGR. + new_bitmap = convert_32_to_32bgr(bitmap); + } +#endif + + // + // The rest is about bringing bitmaps to the native game's format + // (has no dependency on display mode). + // + // In 32-bit game 32-bit bitmaps should have transparent pixels marked + // (this adjustment is probably needed for DrawingSurface ops) + if (game_col_depth == 32 && bmp_col_depth == 32) + { + if (has_alpha) + set_rgb_mask_using_alpha_channel(new_bitmap); + } + // In 32-bit game hicolor bitmaps must be converted to the true color + else if (game_col_depth == 32 && (bmp_col_depth > 8 && bmp_col_depth <= 16)) + { + new_bitmap = BitmapHelper::CreateBitmapCopy(bitmap, game_col_depth); + } + // In non-32-bit game truecolor bitmaps must be downgraded + else if (game_col_depth <= 16 && bmp_col_depth > 16) + { + if (has_alpha) // if has valid alpha channel, convert it to regular transparency mask + new_bitmap = remove_alpha_channel(bitmap); + else // else simply convert bitmap + new_bitmap = BitmapHelper::CreateBitmapCopy(bitmap, game_col_depth); + } + // Special case when we must convert 16-bit RGB to BGR + else if (convert_16bit_bgr == 1 && bmp_col_depth == 16) + { + new_bitmap = convert_16_to_16bgr(bitmap); + } + return new_bitmap; +} + +Bitmap *ReplaceBitmapWithSupportedFormat(Bitmap *bitmap) +{ + Bitmap *new_bitmap = GfxUtil::ConvertBitmap(bitmap, gfxDriver->GetCompatibleBitmapFormat(bitmap->GetColorDepth())); + if (new_bitmap != bitmap) + delete bitmap; + return new_bitmap; +} + +Bitmap *PrepareSpriteForUse(Bitmap* bitmap, bool has_alpha) +{ + bool must_switch_palette = bitmap->GetColorDepth() == 8 && game.GetColorDepth() > 8; + if (must_switch_palette) + select_palette(palette); + + Bitmap *new_bitmap = AdjustBitmapForUseWithDisplayMode(bitmap, has_alpha); + if (new_bitmap != bitmap) + delete bitmap; + new_bitmap = ReplaceBitmapWithSupportedFormat(new_bitmap); + + if (must_switch_palette) + unselect_palette(); + return new_bitmap; +} + +PBitmap PrepareSpriteForUse(PBitmap bitmap, bool has_alpha) +{ + bool must_switch_palette = bitmap->GetColorDepth() == 8 && System_GetColorDepth() > 8; + if (must_switch_palette) + select_palette(palette); + + Bitmap *new_bitmap = AdjustBitmapForUseWithDisplayMode(bitmap.get(), has_alpha); + new_bitmap = ReplaceBitmapWithSupportedFormat(new_bitmap); + + if (must_switch_palette) + unselect_palette(); + return new_bitmap == bitmap.get() ? bitmap : PBitmap(new_bitmap); // if bitmap is same, don't create new smart ptr! +} + +Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res) +{ + Bitmap *dst = new Bitmap(width, height, game.GetColorDepth()); + GraphicResolution want_fmt; + // If the size and color depth are supported we may copy right into our bitmap + if (gfxDriver->GetCopyOfScreenIntoBitmap(dst, at_native_res, &want_fmt)) + return dst; + // Otherwise we might need to copy between few bitmaps... + Bitmap *buf_screenfmt = new Bitmap(want_fmt.Width, want_fmt.Height, want_fmt.ColorDepth); + gfxDriver->GetCopyOfScreenIntoBitmap(buf_screenfmt, at_native_res); + // If at least size matches then we may blit + if (dst->GetSize() == buf_screenfmt->GetSize()) + { + dst->Blit(buf_screenfmt); + } + // Otherwise we need to go through another bitmap of the matching format + else + { + Bitmap *buf_dstfmt = new Bitmap(buf_screenfmt->GetWidth(), buf_screenfmt->GetHeight(), dst->GetColorDepth()); + buf_dstfmt->Blit(buf_screenfmt); + dst->StretchBlt(buf_dstfmt, RectWH(dst->GetSize())); + delete buf_dstfmt; + } + delete buf_screenfmt; + return dst; +} + + +// Begin resolution system functions + +// Multiplies up the number of pixels depending on the current +// resolution, to give a relatively fixed size at any game res +AGS_INLINE int get_fixed_pixel_size(int pixels) +{ + return pixels * game.GetRelativeUIMult(); +} + +AGS_INLINE int data_to_game_coord(int coord) +{ + return coord * game.GetDataUpscaleMult(); +} + +AGS_INLINE void data_to_game_coords(int *x, int *y) +{ + const int mul = game.GetDataUpscaleMult(); + x[0] *= mul; + y[0] *= mul; +} + +AGS_INLINE void data_to_game_round_up(int *x, int *y) +{ + const int mul = game.GetDataUpscaleMult(); + x[0] = x[0] * mul + (mul - 1); + y[0] = y[0] * mul + (mul - 1); +} + +AGS_INLINE int game_to_data_coord(int coord) +{ + return coord / game.GetDataUpscaleMult(); +} + +AGS_INLINE void game_to_data_coords(int &x, int &y) +{ + const int mul = game.GetDataUpscaleMult(); + x /= mul; + y /= mul; +} + +AGS_INLINE int game_to_data_round_up(int coord) +{ + const int mul = game.GetDataUpscaleMult(); + return (coord / mul) + (mul - 1); +} + +AGS_INLINE void ctx_data_to_game_coord(int &x, int &y, bool hires_ctx) +{ + if (hires_ctx && !game.IsLegacyHiRes()) + { + x /= HIRES_COORD_MULTIPLIER; + y /= HIRES_COORD_MULTIPLIER; + } + else if (!hires_ctx && game.IsLegacyHiRes()) + { + x *= HIRES_COORD_MULTIPLIER; + y *= HIRES_COORD_MULTIPLIER; + } +} + +AGS_INLINE void ctx_data_to_game_size(int &w, int &h, bool hires_ctx) +{ + if (hires_ctx && !game.IsLegacyHiRes()) + { + w = Math::Max(1, (w / HIRES_COORD_MULTIPLIER)); + h = Math::Max(1, (h / HIRES_COORD_MULTIPLIER)); + } + else if (!hires_ctx && game.IsLegacyHiRes()) + { + w *= HIRES_COORD_MULTIPLIER; + h *= HIRES_COORD_MULTIPLIER; + } +} + +AGS_INLINE int ctx_data_to_game_size(int size, bool hires_ctx) +{ + if (hires_ctx && !game.IsLegacyHiRes()) + return Math::Max(1, (size / HIRES_COORD_MULTIPLIER)); + if (!hires_ctx && game.IsLegacyHiRes()) + return size * HIRES_COORD_MULTIPLIER; + return size; +} + +AGS_INLINE int game_to_ctx_data_size(int size, bool hires_ctx) +{ + if (hires_ctx && !game.IsLegacyHiRes()) + return size * HIRES_COORD_MULTIPLIER; + else if (!hires_ctx && game.IsLegacyHiRes()) + return Math::Max(1, (size / HIRES_COORD_MULTIPLIER)); + return size; +} + +AGS_INLINE void defgame_to_finalgame_coords(int &x, int &y) +{ // Note we support only upscale now + x *= game.GetScreenUpscaleMult(); + y *= game.GetScreenUpscaleMult(); +} + +// End resolution system functions + +// Create blank (black) images used to repaint borders around game frame +void create_blank_image(int coldepth) +{ + // this is the first time that we try to use the graphics driver, + // so it's the most likey place for a crash + try + { + Bitmap *blank = BitmapHelper::CreateBitmap(16, 16, coldepth); + blank = ReplaceBitmapWithSupportedFormat(blank); + blank->Clear(); + blankImage = gfxDriver->CreateDDBFromBitmap(blank, false, true); + blankSidebarImage = gfxDriver->CreateDDBFromBitmap(blank, false, true); + delete blank; + } + catch (Ali3DException gfxException) + { + quit((char*)gfxException._message); + } +} + +void destroy_blank_image() +{ + if (blankImage) + gfxDriver->DestroyDDB(blankImage); + if (blankSidebarImage) + gfxDriver->DestroyDDB(blankSidebarImage); + blankImage = nullptr; + blankSidebarImage = nullptr; +} + +int MakeColor(int color_index) +{ + color_t real_color = 0; + __my_setcolor(&real_color, color_index, game.GetColorDepth()); + return real_color; +} + +void init_draw_method() +{ + if (gfxDriver->HasAcceleratedTransform()) + { + walkBehindMethod = DrawAsSeparateSprite; + create_blank_image(game.GetColorDepth()); + } + else + { + walkBehindMethod = DrawOverCharSprite; + } + + on_mainviewport_changed(); + init_room_drawdata(); + if (gfxDriver->UsesMemoryBackBuffer()) + gfxDriver->GetMemoryBackBuffer()->Clear(); +} + +void dispose_draw_method() +{ + dispose_room_drawdata(); + dispose_invalid_regions(false); + destroy_blank_image(); +} + +void dispose_room_drawdata() +{ + CameraDrawData.clear(); + dispose_invalid_regions(true); +} + +void on_mainviewport_changed() +{ + if (!gfxDriver->RequiresFullRedrawEachFrame()) + { + init_invalid_regions(-1, play.GetMainViewport().GetSize(), RectWH(play.GetMainViewport().GetSize())); + if (game.GetGameRes().ExceedsByAny(play.GetMainViewport().GetSize())) + clear_letterbox_borders(); + } +} + +// Allocates a bitmap for rendering camera/viewport pair (software render mode) +void prepare_roomview_frame(Viewport *view) +{ + const int view_index = view->GetID(); + const Size view_sz = view->GetRect().GetSize(); + const Size cam_sz = view->GetCamera()->GetRect().GetSize(); + RoomCameraDrawData &draw_dat = CameraDrawData[view_index]; + // We use intermediate bitmap to render camera/viewport pair in software mode under these conditions: + // * camera size and viewport size are different (this may be suboptimal to paint dirty rects stretched, + // and also Allegro backend cannot stretch background of different colour depth). + // * viewport is located outside of the virtual screen (even if partially): subbitmaps cannot contain + // regions outside of master bitmap, and we must not clamp surface size to virtual screen because + // plugins may want to also use viewport bitmap, therefore it should retain full size. + if (cam_sz == view_sz && !draw_dat.IsOffscreen) + { // note we keep the buffer allocated in case it will become useful later + draw_dat.Frame.reset(); + } + else + { + PBitmap &camera_frame = draw_dat.Frame; + PBitmap &camera_buffer = draw_dat.Buffer; + if (!camera_buffer || camera_buffer->GetWidth() < cam_sz.Width || camera_buffer->GetHeight() < cam_sz.Height) + { + // Allocate new buffer bitmap with an extra size in case they will want to zoom out + int room_width = data_to_game_coord(thisroom.Width); + int room_height = data_to_game_coord(thisroom.Height); + Size alloc_sz = Size::Clamp(cam_sz * 2, Size(1, 1), Size(room_width, room_height)); + camera_buffer.reset(new Bitmap(alloc_sz.Width, alloc_sz.Height, gfxDriver->GetMemoryBackBuffer()->GetColorDepth())); + } + + if (!camera_frame || camera_frame->GetSize() != cam_sz) + { + camera_frame.reset(BitmapHelper::CreateSubBitmap(camera_buffer.get(), RectWH(cam_sz))); + } + } +} + +// Syncs room viewport and camera in case either size has changed +void sync_roomview(Viewport *view) +{ + if (view->GetCamera() == nullptr) + return; + init_invalid_regions(view->GetID(), view->GetCamera()->GetRect().GetSize(), play.GetRoomViewportAbs(view->GetID())); + prepare_roomview_frame(view); +} + +void init_room_drawdata() +{ + if (gfxDriver->RequiresFullRedrawEachFrame()) + return; + // Make sure all frame buffers are created for software drawing + int view_count = play.GetRoomViewportCount(); + CameraDrawData.resize(view_count); + for (int i = 0; i < play.GetRoomViewportCount(); ++i) + sync_roomview(play.GetRoomViewport(i).get()); +} + +void on_roomviewport_created(int index) +{ + if (!gfxDriver || gfxDriver->RequiresFullRedrawEachFrame()) + return; + if ((size_t)index < CameraDrawData.size()) + return; + CameraDrawData.resize(index + 1); +} + +void on_roomviewport_deleted(int index) +{ + if (gfxDriver->RequiresFullRedrawEachFrame()) + return; + CameraDrawData.erase(CameraDrawData.begin() + index); + delete_invalid_regions(index); +} + +void on_roomviewport_changed(Viewport *view) +{ + if (gfxDriver->RequiresFullRedrawEachFrame()) + return; + if (!view->IsVisible() || view->GetCamera() == nullptr) + return; + const bool off = !IsRectInsideRect(RectWH(gfxDriver->GetMemoryBackBuffer()->GetSize()), view->GetRect()); + const bool off_changed = off != CameraDrawData[view->GetID()].IsOffscreen; + CameraDrawData[view->GetID()].IsOffscreen = off; + if (view->HasChangedSize()) + sync_roomview(view); + else if (off_changed) + prepare_roomview_frame(view); + // TODO: don't have to do this all the time, perhaps do "dirty rect" method + // and only clear previous viewport location? + invalidate_screen(); + gfxDriver->GetMemoryBackBuffer()->Clear(); +} + +void detect_roomviewport_overlaps(size_t z_index) +{ + if (gfxDriver->RequiresFullRedrawEachFrame()) + return; + // Find out if we overlap or are overlapped by anything; + const auto &viewports = play.GetRoomViewportsZOrdered(); + for (; z_index < viewports.size(); ++z_index) + { + auto this_view = viewports[z_index]; + const int this_id = this_view->GetID(); + bool is_overlap = false; + if (!this_view->IsVisible()) continue; + for (size_t z_index2 = 0; z_index2 < z_index; ++z_index) + { + if (!viewports[z_index2]->IsVisible()) continue; + if (AreRectsIntersecting(this_view->GetRect(), viewports[z_index2]->GetRect())) + { + is_overlap = true; + break; + } + } + if (CameraDrawData[this_id].IsOverlap != is_overlap) + { + CameraDrawData[this_id].IsOverlap = is_overlap; + prepare_roomview_frame(this_view.get()); + } + } +} + +void on_roomcamera_changed(Camera *cam) +{ + if (gfxDriver->RequiresFullRedrawEachFrame()) + return; + if (cam->HasChangedSize()) + { + auto viewrefs = cam->GetLinkedViewports(); + for (auto vr : viewrefs) + { + PViewport vp = vr.lock(); + if (vp) + sync_roomview(vp.get()); + } + } + // TODO: only invalidate what this particular camera sees + invalidate_screen(); +} + +void mark_screen_dirty() +{ + screen_is_dirty = true; +} + +bool is_screen_dirty() +{ + return screen_is_dirty; +} + +void invalidate_screen() +{ + invalidate_all_rects(); +} + +void invalidate_camera_frame(int index) +{ + invalidate_all_camera_rects(index); +} + +void invalidate_rect(int x1, int y1, int x2, int y2, bool in_room) +{ + //if (!in_room) + invalidate_rect_ds(x1, y1, x2, y2, in_room); +} + +void invalidate_sprite(int x1, int y1, IDriverDependantBitmap *pic, bool in_room) +{ + //if (!in_room) + invalidate_rect_ds(x1, y1, x1 + pic->GetWidth(), y1 + pic->GetHeight(), in_room); +} + +void mark_current_background_dirty() +{ + current_background_is_dirty = true; +} + + +void draw_and_invalidate_text(Bitmap *ds, int x1, int y1, int font, color_t text_color, const char *text) +{ + wouttext_outline(ds, x1, y1, font, text_color, (char*)text); + invalidate_rect(x1, y1, x1 + wgettextwidth_compensate(text, font), y1 + getfontheight_outlined(font) + get_fixed_pixel_size(1), false); +} + +// Renders black borders for the legacy boxed game mode, +// where whole game screen changes size between large and small rooms +void render_black_borders() +{ + if (gfxDriver->UsesMemoryBackBuffer()) + return; + { + gfxDriver->BeginSpriteBatch(RectWH(game.GetGameRes()), SpriteTransform()); + const Rect &viewport = play.GetMainViewport(); + if (viewport.Top > 0) + { + // letterbox borders + blankImage->SetStretch(game.GetGameRes().Width, viewport.Top, false); + gfxDriver->DrawSprite(0, 0, blankImage); + gfxDriver->DrawSprite(0, viewport.Bottom + 1, blankImage); + } + if (viewport.Left > 0) + { + // sidebar borders for widescreen + blankSidebarImage->SetStretch(viewport.Left, viewport.GetHeight(), false); + gfxDriver->DrawSprite(0, 0, blankSidebarImage); + gfxDriver->DrawSprite(viewport.Right + 1, 0, blankSidebarImage); + } + } +} + + +void render_to_screen() +{ + // Stage: final plugin callback (still drawn on game screen + if (pl_any_want_hook(AGSE_FINALSCREENDRAW)) + { + gfxDriver->BeginSpriteBatch(play.GetMainViewport(), SpriteTransform(), Point(0, play.shake_screen_yoff), (GlobalFlipType)play.screen_flipped); + gfxDriver->DrawSprite(AGSE_FINALSCREENDRAW, 0, nullptr); + } + // Stage: engine overlay + construct_engine_overlay(); + + // only vsync in full screen mode, it makes things worse in a window + gfxDriver->EnableVsyncBeforeRender((scsystem.vsync > 0) && (!scsystem.windowed)); + + bool succeeded = false; + while (!succeeded) + { + try + { + // For software renderer, need to blacken upper part of the game frame when shaking screen moves image down + const Rect &viewport = play.GetMainViewport(); + if (play.shake_screen_yoff > 0 && !gfxDriver->RequiresFullRedrawEachFrame()) + gfxDriver->ClearRectangle(viewport.Left, viewport.Top, viewport.GetWidth() - 1, play.shake_screen_yoff, nullptr); + gfxDriver->Render(0, play.shake_screen_yoff, (GlobalFlipType)play.screen_flipped); + +#if AGS_PLATFORM_OS_ANDROID + if (game.color_depth == 1) + android_render(); +#elif AGS_PLATFORM_OS_IOS + if (game.color_depth == 1) + ios_render(); +#endif + + succeeded = true; + } + catch (Ali3DFullscreenLostException) + { + platform->Delay(500); + } + } +} + +// Blanks out borders around main viewport in case it became smaller (e.g. after loading another room) +void clear_letterbox_borders() +{ + const Rect &viewport = play.GetMainViewport(); + gfxDriver->ClearRectangle(0, 0, game.GetGameRes().Width - 1, viewport.Top - 1, nullptr); + gfxDriver->ClearRectangle(0, viewport.Bottom + 1, game.GetGameRes().Width - 1, game.GetGameRes().Height - 1, nullptr); +} + +void draw_game_screen_callback() +{ + construct_game_scene(true); + construct_game_screen_overlay(false); +} + + + +void putpixel_compensate (Bitmap *ds, int xx,int yy, int col) { + if ((ds->GetColorDepth() == 32) && (col != 0)) { + // ensure the alpha channel is preserved if it has one + int alphaval = geta32(ds->GetPixel(xx, yy)); + col = makeacol32(getr32(col), getg32(col), getb32(col), alphaval); + } + ds->FillRect(Rect(xx, yy, xx + get_fixed_pixel_size(1) - 1, yy + get_fixed_pixel_size(1) - 1), col); +} + + + + +void draw_sprite_support_alpha(Bitmap *ds, bool ds_has_alpha, int xpos, int ypos, Bitmap *image, bool src_has_alpha, + BlendMode blend_mode, int alpha) +{ + if (alpha <= 0) + return; + + if (game.options[OPT_SPRITEALPHA] == kSpriteAlphaRender_Proper) + { + GfxUtil::DrawSpriteBlend(ds, Point(xpos, ypos), image, blend_mode, ds_has_alpha, src_has_alpha, alpha); + } + // Backwards-compatible drawing + else if (src_has_alpha && alpha == 0xFF) + { + set_alpha_blender(); + ds->TransBlendBlt(image, xpos, ypos); + } + else + { + GfxUtil::DrawSpriteWithTransparency(ds, image, xpos, ypos, alpha); + } +} + +void draw_sprite_slot_support_alpha(Bitmap *ds, bool ds_has_alpha, int xpos, int ypos, int src_slot, + BlendMode blend_mode, int alpha) +{ + draw_sprite_support_alpha(ds, ds_has_alpha, xpos, ypos, spriteset[src_slot], (game.SpriteInfos[src_slot].Flags & SPF_ALPHACHANNEL) != 0, + blend_mode, alpha); +} + + +IDriverDependantBitmap* recycle_ddb_bitmap(IDriverDependantBitmap *bimp, Bitmap *source, bool hasAlpha, bool opaque) { + if (bimp != nullptr) { + // same colour depth, width and height -> reuse + if (((bimp->GetColorDepth() + 1) / 8 == source->GetBPP()) && + (bimp->GetWidth() == source->GetWidth()) && (bimp->GetHeight() == source->GetHeight())) + { + gfxDriver->UpdateDDBFromBitmap(bimp, source, hasAlpha); + return bimp; + } + + gfxDriver->DestroyDDB(bimp); + } + bimp = gfxDriver->CreateDDBFromBitmap(source, hasAlpha, opaque); + return bimp; +} + +void invalidate_cached_walkbehinds() +{ + memset(&actspswbcache[0], 0, sizeof(CachedActSpsData) * actSpsCount); +} + +// sort_out_walk_behinds: modifies the supplied sprite by overwriting parts +// of it with transparent pixels where there are walk-behind areas +// Returns whether any pixels were updated +int sort_out_walk_behinds(Bitmap *sprit,int xx,int yy,int basel, Bitmap *copyPixelsFrom = nullptr, Bitmap *checkPixelsFrom = nullptr, int zoom=100) { + if (noWalkBehindsAtAll) + return 0; + + if ((!thisroom.WalkBehindMask->IsMemoryBitmap()) || + (!sprit->IsMemoryBitmap())) + quit("!sort_out_walk_behinds: wb bitmap not linear"); + + int rr,tmm, toheight;//,tcol; + // precalculate this to try and shave some time off + int maskcol = sprit->GetMaskColor(); + int spcoldep = sprit->GetColorDepth(); + int screenhit = thisroom.WalkBehindMask->GetHeight(); + short *shptr, *shptr2; + int *loptr, *loptr2; + int pixelsChanged = 0; + int ee = 0; + if (xx < 0) + ee = 0 - xx; + + if ((checkPixelsFrom != nullptr) && (checkPixelsFrom->GetColorDepth() != spcoldep)) + quit("sprite colour depth does not match background colour depth"); + + for ( ; ee < sprit->GetWidth(); ee++) { + if (ee + xx >= thisroom.WalkBehindMask->GetWidth()) + break; + + if ((!walkBehindExists[ee+xx]) || + (walkBehindEndY[ee+xx] <= yy) || + (walkBehindStartY[ee+xx] > yy+sprit->GetHeight())) + continue; + + toheight = sprit->GetHeight(); + + if (walkBehindStartY[ee+xx] < yy) + rr = 0; + else + rr = (walkBehindStartY[ee+xx] - yy); + + // Since we will use _getpixel, ensure we only check within the screen + if (rr + yy < 0) + rr = 0 - yy; + if (toheight + yy > screenhit) + toheight = screenhit - yy; + if (toheight + yy > walkBehindEndY[ee+xx]) + toheight = walkBehindEndY[ee+xx] - yy; + if (rr < 0) + rr = 0; + + for ( ; rr < toheight;rr++) { + + // we're ok with _getpixel because we've checked the screen edges + //tmm = _getpixel(thisroom.WalkBehindMask,ee+xx,rr+yy); + // actually, _getpixel is well inefficient, do it ourselves + // since we know it's 8-bit bitmap + tmm = thisroom.WalkBehindMask->GetScanLine(rr+yy)[ee+xx]; + if (tmm<1) continue; + if (croom->walkbehind_base[tmm] <= basel) continue; + + if (copyPixelsFrom != nullptr) + { + if (spcoldep <= 8) + { + if (checkPixelsFrom->GetScanLine((rr * 100) / zoom)[(ee * 100) / zoom] != maskcol) { + sprit->GetScanLineForWriting(rr)[ee] = copyPixelsFrom->GetScanLine(rr + yy)[ee + xx]; + pixelsChanged = 1; + } + } + else if (spcoldep <= 16) { + shptr = (short*)&sprit->GetScanLine(rr)[0]; + shptr2 = (short*)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0]; + if (shptr2[(ee * 100) / zoom] != maskcol) { + shptr[ee] = ((short*)(©PixelsFrom->GetScanLine(rr + yy)[0]))[ee + xx]; + pixelsChanged = 1; + } + } + else if (spcoldep == 24) { + char *chptr = (char*)&sprit->GetScanLine(rr)[0]; + char *chptr2 = (char*)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0]; + if (memcmp(&chptr2[((ee * 100) / zoom) * 3], &maskcol, 3) != 0) { + memcpy(&chptr[ee * 3], ©PixelsFrom->GetScanLine(rr + yy)[(ee + xx) * 3], 3); + pixelsChanged = 1; + } + } + else if (spcoldep <= 32) { + loptr = (int*)&sprit->GetScanLine(rr)[0]; + loptr2 = (int*)&checkPixelsFrom->GetScanLine((rr * 100) / zoom)[0]; + if (loptr2[(ee * 100) / zoom] != maskcol) { + loptr[ee] = ((int*)(©PixelsFrom->GetScanLine(rr + yy)[0]))[ee + xx]; + pixelsChanged = 1; + } + } + } + else + { + pixelsChanged = 1; + if (spcoldep <= 8) + sprit->GetScanLineForWriting(rr)[ee] = maskcol; + else if (spcoldep <= 16) { + shptr = (short*)&sprit->GetScanLine(rr)[0]; + shptr[ee] = maskcol; + } + else if (spcoldep == 24) { + char *chptr = (char*)&sprit->GetScanLine(rr)[0]; + memcpy(&chptr[ee * 3], &maskcol, 3); + } + else if (spcoldep <= 32) { + loptr = (int*)&sprit->GetScanLine(rr)[0]; + loptr[ee] = maskcol; + } + else + quit("!Sprite colour depth >32 ??"); + } + } + } + return pixelsChanged; +} + +void sort_out_char_sprite_walk_behind(int actspsIndex, int xx, int yy, int basel, int zoom, int width, int height) +{ + if (noWalkBehindsAtAll) + return; + + if ((!actspswbcache[actspsIndex].valid) || + (actspswbcache[actspsIndex].xWas != xx) || + (actspswbcache[actspsIndex].yWas != yy) || + (actspswbcache[actspsIndex].baselineWas != basel)) + { + actspswb[actspsIndex] = recycle_bitmap(actspswb[actspsIndex], thisroom.BgFrames[play.bg_frame].Graphic->GetColorDepth(), width, height, true); + Bitmap *wbSprite = actspswb[actspsIndex]; + + actspswbcache[actspsIndex].isWalkBehindHere = sort_out_walk_behinds(wbSprite, xx, yy, basel, thisroom.BgFrames[play.bg_frame].Graphic.get(), actsps[actspsIndex], zoom); + actspswbcache[actspsIndex].xWas = xx; + actspswbcache[actspsIndex].yWas = yy; + actspswbcache[actspsIndex].baselineWas = basel; + actspswbcache[actspsIndex].valid = 1; + + if (actspswbcache[actspsIndex].isWalkBehindHere) + { + actspswbbmp[actspsIndex] = recycle_ddb_bitmap(actspswbbmp[actspsIndex], actspswb[actspsIndex], false); + } + } + + if (actspswbcache[actspsIndex].isWalkBehindHere) + { + add_to_sprite_list(actspswbbmp[actspsIndex], xx, yy, basel, 0, -1, true); + } +} + +void clear_draw_list() { + thingsToDrawList.clear(); +} +void add_thing_to_draw(IDriverDependantBitmap* bmp, int x, int y, int trans, bool alphaChannel) { + SpriteListEntry sprite; + sprite.pic = nullptr; + sprite.bmp = bmp; + sprite.x = x; + sprite.y = y; + sprite.transparent = trans; + sprite.hasAlphaChannel = alphaChannel; + thingsToDrawList.push_back(sprite); +} + +// the sprite list is an intermediate list used to order +// objects and characters by their baselines before everything +// is added to the Thing To Draw List +void clear_sprite_list() { + sprlist.clear(); +} +void add_to_sprite_list(IDriverDependantBitmap* spp, int xx, int yy, int baseline, int trans, int sprNum, bool isWalkBehind) { + + if (spp == nullptr) + quit("add_to_sprite_list: attempted to draw NULL sprite"); + // completely invisible, so don't draw it at all + if (trans == 255) + return; + + SpriteListEntry sprite; + if ((sprNum >= 0) && ((game.SpriteInfos[sprNum].Flags & SPF_ALPHACHANNEL) != 0)) + sprite.hasAlphaChannel = true; + else + sprite.hasAlphaChannel = false; + + sprite.bmp = spp; + sprite.baseline = baseline; + sprite.x=xx; + sprite.y=yy; + sprite.transparent=trans; + + if (walkBehindMethod == DrawAsSeparateSprite) + sprite.takesPriorityIfEqual = !isWalkBehind; + else + sprite.takesPriorityIfEqual = isWalkBehind; + + sprlist.push_back(sprite); +} + +void repair_alpha_channel(Bitmap *dest, Bitmap *bgpic) +{ + // Repair the alpha channel, because sprites may have been drawn + // over it by the buttons, etc + int theWid = (dest->GetWidth() < bgpic->GetWidth()) ? dest->GetWidth() : bgpic->GetWidth(); + int theHit = (dest->GetHeight() < bgpic->GetHeight()) ? dest->GetHeight() : bgpic->GetHeight(); + for (int y = 0; y < theHit; y++) + { + unsigned int *destination = ((unsigned int*)dest->GetScanLineForWriting(y)); + unsigned int *source = ((unsigned int*)bgpic->GetScanLineForWriting(y)); + for (int x = 0; x < theWid; x++) + { + destination[x] |= (source[x] & 0xff000000); + } + } +} + + +// used by GUI renderer to draw images +void draw_gui_sprite(Bitmap *ds, int pic, int x, int y, bool use_alpha, BlendMode blend_mode) +{ + Bitmap *sprite = spriteset[pic]; + const bool ds_has_alpha = ds->GetColorDepth() == 32; + const bool src_has_alpha = (game.SpriteInfos[pic].Flags & SPF_ALPHACHANNEL) != 0; + + if (use_alpha && game.options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Proper) + { + GfxUtil::DrawSpriteBlend(ds, Point(x, y), sprite, blend_mode, ds_has_alpha, src_has_alpha); + } + // Backwards-compatible drawing + else if (use_alpha && ds_has_alpha && game.options[OPT_NEWGUIALPHA] == kGuiAlphaRender_AdditiveAlpha) + { + if (src_has_alpha) + set_additive_alpha_blender(); + else + set_opaque_alpha_blender(); + ds->TransBlendBlt(sprite, x, y); + } + else + { + GfxUtil::DrawSpriteWithTransparency(ds, sprite, x, y); + } +} + +void draw_gui_sprite_v330(Bitmap *ds, int pic, int x, int y, bool use_alpha, BlendMode blend_mode) +{ + draw_gui_sprite(ds, pic, x, y, use_alpha && (loaded_game_file_version >= kGameVersion_330), blend_mode); +} + +// function to sort the sprites into baseline order +bool spritelistentry_less(const SpriteListEntry &e1, const SpriteListEntry &e2) +{ + if (e1.baseline == e2.baseline) + { + if (e1.takesPriorityIfEqual) + return false; + if (e2.takesPriorityIfEqual) + return true; + } + return e1.baseline < e2.baseline; +} + + + + +void draw_sprite_list() { + + if (walkBehindMethod == DrawAsSeparateSprite) + { + for (int ee = 1; ee < MAX_WALK_BEHINDS; ee++) + { + if (walkBehindBitmap[ee] != nullptr) + { + add_to_sprite_list(walkBehindBitmap[ee], walkBehindLeft[ee], walkBehindTop[ee], + croom->walkbehind_base[ee], 0, -1, true); + } + } + } + + std::sort(sprlist.begin(), sprlist.end(), spritelistentry_less); + + if(pl_any_want_hook(AGSE_PRESCREENDRAW)) + add_thing_to_draw(nullptr, AGSE_PRESCREENDRAW, 0, TRANS_RUN_PLUGIN, false); + + // copy the sorted sprites into the Things To Draw list + thingsToDrawList.insert(thingsToDrawList.end(), sprlist.begin(), sprlist.end()); +} + +// Avoid freeing and reallocating the memory if possible +Bitmap *recycle_bitmap(Bitmap *bimp, int coldep, int wid, int hit, bool make_transparent) { + if (bimp != nullptr) { + // same colour depth, width and height -> reuse + if ((bimp->GetColorDepth() == coldep) && (bimp->GetWidth() == wid) + && (bimp->GetHeight() == hit)) + { + if (make_transparent) + { + bimp->ClearTransparent(); + } + return bimp; + } + + delete bimp; + } + bimp = make_transparent ? BitmapHelper::CreateTransparentBitmap(wid, hit,coldep) : + BitmapHelper::CreateBitmap(wid, hit,coldep); + return bimp; +} + + +// Get the local tint at the specified X & Y co-ordinates, based on +// room regions and SetAmbientTint +// tint_amnt will be set to 0 if there is no tint enabled +// if this is the case, then light_lev holds the light level (0=none) +void get_local_tint(int xpp, int ypp, int nolight, + int *tint_amnt, int *tint_r, int *tint_g, + int *tint_b, int *tint_lit, + int *light_lev) { + + int tint_level = 0, light_level = 0; + int tint_amount = 0; + int tint_red = 0; + int tint_green = 0; + int tint_blue = 0; + int tint_light = 255; + + if (nolight == 0) { + + int onRegion = 0; + + if ((play.ground_level_areas_disabled & GLED_EFFECTS) == 0) { + // check if the player is on a region, to find its + // light/tint level + onRegion = GetRegionIDAtRoom(xpp, ypp); + if (onRegion == 0) { + // when walking, he might just be off a walkable area + onRegion = GetRegionIDAtRoom(xpp - 3, ypp); + if (onRegion == 0) + onRegion = GetRegionIDAtRoom(xpp + 3, ypp); + if (onRegion == 0) + onRegion = GetRegionIDAtRoom(xpp, ypp - 3); + if (onRegion == 0) + onRegion = GetRegionIDAtRoom(xpp, ypp + 3); + } + } + + if ((onRegion > 0) && (onRegion < MAX_ROOM_REGIONS)) { + light_level = thisroom.Regions[onRegion].Light; + tint_level = thisroom.Regions[onRegion].Tint; + } + else if (onRegion <= 0) { + light_level = thisroom.Regions[0].Light; + tint_level = thisroom.Regions[0].Tint; + } + + int tint_sat = (tint_level >> 24) & 0xFF; + if ((game.color_depth == 1) || ((tint_level & 0x00ffffff) == 0) || + (tint_sat == 0)) + tint_level = 0; + + if (tint_level) { + tint_red = (unsigned char)(tint_level & 0x000ff); + tint_green = (unsigned char)((tint_level >> 8) & 0x000ff); + tint_blue = (unsigned char)((tint_level >> 16) & 0x000ff); + tint_amount = tint_sat; + tint_light = light_level; + } + + if (play.rtint_enabled) + { + if (play.rtint_level > 0) + { + // override with room tint + tint_red = play.rtint_red; + tint_green = play.rtint_green; + tint_blue = play.rtint_blue; + tint_amount = play.rtint_level; + tint_light = play.rtint_light; + } + else + { + // override with room light level + tint_amount = 0; + light_level = play.rtint_light; + } + } + } + + // copy to output parameters + *tint_amnt = tint_amount; + *tint_r = tint_red; + *tint_g = tint_green; + *tint_b = tint_blue; + *tint_lit = tint_light; + if (light_lev) + *light_lev = light_level; +} + + + + +// Applies the specified RGB Tint or Light Level to the actsps +// sprite indexed with actspsindex +void apply_tint_or_light(int actspsindex, int light_level, + int tint_amount, int tint_red, int tint_green, + int tint_blue, int tint_light, int coldept, + Bitmap *blitFrom) { + + // In a 256-colour game, we cannot do tinting or lightening + // (but we can do darkening, if light_level < 0) + if (game.color_depth == 1) { + if ((light_level > 0) || (tint_amount != 0)) + return; + } + + // we can only do tint/light if the colour depths match + if (game.GetColorDepth() == actsps[actspsindex]->GetColorDepth()) { + Bitmap *oldwas; + // if the caller supplied a source bitmap, ->Blit from it + // (used as a speed optimisation where possible) + if (blitFrom) + oldwas = blitFrom; + // otherwise, make a new target bmp + else { + oldwas = actsps[actspsindex]; + actsps[actspsindex] = BitmapHelper::CreateBitmap(oldwas->GetWidth(), oldwas->GetHeight(), coldept); + } + Bitmap *active_spr = actsps[actspsindex]; + + if (tint_amount) { + // It is an RGB tint + tint_image (active_spr, oldwas, tint_red, tint_green, tint_blue, tint_amount, tint_light); + } + else { + // the RGB values passed to set_trans_blender decide whether it will darken + // or lighten sprites ( <128=darken, >128=lighten). The parameter passed + // to LitBlendBlt defines how much it will be darkened/lightened by. + + int lit_amnt; + active_spr->FillTransparent(); + // It's a light level, not a tint + if (game.color_depth == 1) { + // 256-col + lit_amnt = (250 - ((-light_level) * 5)/2); + } + else { + // hi-color + if (light_level < 0) + set_my_trans_blender(8,8,8,0); + else + set_my_trans_blender(248,248,248,0); + lit_amnt = abs(light_level) * 2; + } + + active_spr->LitBlendBlt(oldwas, 0, 0, lit_amnt); + } + + if (oldwas != blitFrom) + delete oldwas; + + } + else if (blitFrom) { + // sprite colour depth != game colour depth, so don't try and tint + // but we do need to do something, so copy the source + Bitmap *active_spr = actsps[actspsindex]; + active_spr->Blit(blitFrom, 0, 0, 0, 0, active_spr->GetWidth(), active_spr->GetHeight()); + } + +} + +// Draws the specified 'sppic' sprite onto actsps[useindx] at the +// specified width and height, and flips the sprite if necessary. +// Returns 1 if something was drawn to actsps; returns 0 if no +// scaling or stretching was required, in which case nothing was done +int scale_and_flip_sprite(int useindx, int coldept, int zoom_level, + int sppic, int newwidth, int newheight, + int isMirrored) { + + int actsps_used = 1; + + // create and blank out the new sprite + actsps[useindx] = recycle_bitmap(actsps[useindx], coldept, newwidth, newheight, true); + Bitmap *active_spr = actsps[useindx]; + + if (zoom_level != 100) { + // Scaled character + + our_eip = 334; + + // Ensure that anti-aliasing routines have a palette to + // use for mapping while faded out + if (in_new_room) + select_palette (palette); + + + if (isMirrored) { + Bitmap *tempspr = BitmapHelper::CreateBitmap(newwidth, newheight,coldept); + tempspr->Fill (actsps[useindx]->GetMaskColor()); + if ((IS_ANTIALIAS_SPRITES) && ((game.SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) == 0)) + tempspr->AAStretchBlt (spriteset[sppic], RectWH(0, 0, newwidth, newheight), Common::kBitmap_Transparency); + else + tempspr->StretchBlt (spriteset[sppic], RectWH(0, 0, newwidth, newheight), Common::kBitmap_Transparency); + active_spr->FlipBlt(tempspr, 0, 0, Common::kBitmap_HFlip); + delete tempspr; + } + else if ((IS_ANTIALIAS_SPRITES) && ((game.SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) == 0)) + active_spr->AAStretchBlt(spriteset[sppic],RectWH(0,0,newwidth,newheight), Common::kBitmap_Transparency); + else + active_spr->StretchBlt(spriteset[sppic],RectWH(0,0,newwidth,newheight), Common::kBitmap_Transparency); + + /* AASTR2 version of code (doesn't work properly, gives black borders) + if (IS_ANTIALIAS_SPRITES) { + int aa_mode = AA_MASKED; + if (game.spriteflags[sppic] & SPF_ALPHACHANNEL) + aa_mode |= AA_ALPHA | AA_RAW_ALPHA; + if (isMirrored) + aa_mode |= AA_HFLIP; + + aa_set_mode(aa_mode); + ->AAStretchBlt(actsps[useindx],spriteset[sppic],0,0,newwidth,newheight); + } + else if (isMirrored) { + Bitmap *tempspr = BitmapHelper::CreateBitmap_ (coldept, newwidth, newheight); + ->Clear (tempspr, ->GetMaskColor(actsps[useindx])); + ->StretchBlt (tempspr, spriteset[sppic], 0, 0, newwidth, newheight); + ->FlipBlt(Common::kBitmap_HFlip, (actsps[useindx], tempspr, 0, 0); + wfreeblock (tempspr); + } + else + ->StretchBlt(actsps[useindx],spriteset[sppic],0,0,newwidth,newheight); + */ + if (in_new_room) + unselect_palette(); + + } + else { + // Not a scaled character, draw at normal size + + our_eip = 339; + + if (isMirrored) + active_spr->FlipBlt(spriteset[sppic], 0, 0, Common::kBitmap_HFlip); + else + actsps_used = 0; + //->Blit (spriteset[sppic], actsps[useindx], 0, 0, 0, 0, actsps[useindx]->GetWidth(), actsps[useindx]->GetHeight()); + } + + return actsps_used; +} + + + +// create the actsps[aa] image with the object drawn correctly +// returns 1 if nothing at all has changed and actsps is still +// intact from last time; 0 otherwise +int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware) { + int useindx = aa; + bool hardwareAccelerated = !alwaysUseSoftware && gfxDriver->HasAcceleratedTransform(); + + if (spriteset[objs[aa].num] == nullptr) + quitprintf("There was an error drawing object %d. Its current sprite, %d, is invalid.", aa, objs[aa].num); + + int coldept = spriteset[objs[aa].num]->GetColorDepth(); + int sprwidth = game.SpriteInfos[objs[aa].num].Width; + int sprheight = game.SpriteInfos[objs[aa].num].Height; + + int tint_red, tint_green, tint_blue; + int tint_level, tint_light, light_level; + int zoom_level = 100; + + // calculate the zoom level + if ((objs[aa].flags & OBJF_USEROOMSCALING) == 0) + { + zoom_level = objs[aa].zoom; + } + else + { + int onarea = get_walkable_area_at_location(objs[aa].x, objs[aa].y); + if ((onarea <= 0) && (thisroom.WalkAreas[0].ScalingFar == 0)) { + // just off the edge of an area -- use the scaling we had + // while on the area + zoom_level = objs[aa].zoom; + } + else + zoom_level = get_area_scaling(onarea, objs[aa].x, objs[aa].y); + } + if (zoom_level != 100) + scale_sprite_size(objs[aa].num, zoom_level, &sprwidth, &sprheight); + objs[aa].zoom = zoom_level; + + // save width/height into parameters if requested + if (drawnWidth) + *drawnWidth = sprwidth; + if (drawnHeight) + *drawnHeight = sprheight; + + objs[aa].last_width = sprwidth; + objs[aa].last_height = sprheight; + + tint_red = tint_green = tint_blue = tint_level = tint_light = light_level = 0; + + if (objs[aa].flags & OBJF_HASTINT) { + // object specific tint, use it + tint_red = objs[aa].tint_r; + tint_green = objs[aa].tint_g; + tint_blue = objs[aa].tint_b; + tint_level = objs[aa].tint_level; + tint_light = objs[aa].tint_light; + light_level = 0; + } + else if (objs[aa].flags & OBJF_HASLIGHT) + { + light_level = objs[aa].tint_light; + } + else { + // get the ambient or region tint + int ignoreRegionTints = 1; + if (objs[aa].flags & OBJF_USEREGIONTINTS) + ignoreRegionTints = 0; + + get_local_tint(objs[aa].x, objs[aa].y, ignoreRegionTints, + &tint_level, &tint_red, &tint_green, &tint_blue, + &tint_light, &light_level); + } + + // check whether the image should be flipped + int isMirrored = 0; + if ( (objs[aa].view >= 0) && + (views[objs[aa].view].loops[objs[aa].loop].frames[objs[aa].frame].pic == objs[aa].num) && + ((views[objs[aa].view].loops[objs[aa].loop].frames[objs[aa].frame].flags & VFLG_FLIPSPRITE) != 0)) { + isMirrored = 1; + } + + if ((hardwareAccelerated) && + (walkBehindMethod != DrawOverCharSprite) && + (objcache[aa].image != nullptr) && + (objcache[aa].sppic == objs[aa].num) && + (actsps[useindx] != nullptr)) + { + // HW acceleration + objcache[aa].tintamntwas = tint_level; + objcache[aa].tintredwas = tint_red; + objcache[aa].tintgrnwas = tint_green; + objcache[aa].tintbluwas = tint_blue; + objcache[aa].tintlightwas = tint_light; + objcache[aa].lightlevwas = light_level; + objcache[aa].zoomWas = zoom_level; + objcache[aa].mirroredWas = isMirrored; + + return 1; + } + + if ((!hardwareAccelerated) && (gfxDriver->HasAcceleratedTransform())) + { + // They want to draw it in software mode with the D3D driver, + // so force a redraw + objcache[aa].sppic = -389538; + } + + // If we have the image cached, use it + if ((objcache[aa].image != nullptr) && + (objcache[aa].sppic == objs[aa].num) && + (objcache[aa].tintamntwas == tint_level) && + (objcache[aa].tintlightwas == tint_light) && + (objcache[aa].tintredwas == tint_red) && + (objcache[aa].tintgrnwas == tint_green) && + (objcache[aa].tintbluwas == tint_blue) && + (objcache[aa].lightlevwas == light_level) && + (objcache[aa].zoomWas == zoom_level) && + (objcache[aa].mirroredWas == isMirrored)) { + // the image is the same, we can use it cached! + if ((walkBehindMethod != DrawOverCharSprite) && + (actsps[useindx] != nullptr)) + return 1; + // Check if the X & Y co-ords are the same, too -- if so, there + // is scope for further optimisations + if ((objcache[aa].xwas == objs[aa].x) && + (objcache[aa].ywas == objs[aa].y) && + (actsps[useindx] != nullptr) && + (walk_behind_baselines_changed == 0)) + return 1; + actsps[useindx] = recycle_bitmap(actsps[useindx], coldept, sprwidth, sprheight); + actsps[useindx]->Blit(objcache[aa].image, 0, 0, 0, 0, objcache[aa].image->GetWidth(), objcache[aa].image->GetHeight()); + return 0; + } + + // Not cached, so draw the image + + int actspsUsed = 0; + if (!hardwareAccelerated) + { + // draw the base sprite, scaled and flipped as appropriate + actspsUsed = scale_and_flip_sprite(useindx, coldept, zoom_level, + objs[aa].num, sprwidth, sprheight, isMirrored); + } + else + { + // ensure actsps exists + actsps[useindx] = recycle_bitmap(actsps[useindx], coldept, game.SpriteInfos[objs[aa].num].Width, game.SpriteInfos[objs[aa].num].Height); + } + + // direct read from source bitmap, where possible + Bitmap *comeFrom = nullptr; + if (!actspsUsed) + comeFrom = spriteset[objs[aa].num]; + + // apply tints or lightenings where appropriate, else just copy + // the source bitmap + if (!hardwareAccelerated && ((tint_level > 0) || (light_level != 0))) + { + apply_tint_or_light(useindx, light_level, tint_level, tint_red, + tint_green, tint_blue, tint_light, coldept, + comeFrom); + } + else if (!actspsUsed) { + actsps[useindx]->Blit(spriteset[objs[aa].num],0,0,0,0,game.SpriteInfos[objs[aa].num].Width, game.SpriteInfos[objs[aa].num].Height); + } + + // Re-use the bitmap if it's the same size + objcache[aa].image = recycle_bitmap(objcache[aa].image, coldept, sprwidth, sprheight); + // Create the cached image and store it + objcache[aa].image->Blit(actsps[useindx], 0, 0, 0, 0, sprwidth, sprheight); + objcache[aa].sppic = objs[aa].num; + objcache[aa].tintamntwas = tint_level; + objcache[aa].tintredwas = tint_red; + objcache[aa].tintgrnwas = tint_green; + objcache[aa].tintbluwas = tint_blue; + objcache[aa].tintlightwas = tint_light; + objcache[aa].lightlevwas = light_level; + objcache[aa].zoomWas = zoom_level; + objcache[aa].mirroredWas = isMirrored; + return 0; +} + + + + +// This is only called from draw_screen_background, but it's seperated +// to help with profiling the program +void prepare_objects_for_drawing() { + our_eip=32; + + for (int aa=0; aanumobj; aa++) { + if (objs[aa].on != 1) continue; + // offscreen, don't draw + if ((objs[aa].x >= thisroom.Width) || (objs[aa].y < 1)) + continue; + + const int useindx = aa; + int tehHeight; + int actspsIntact = construct_object_gfx(aa, nullptr, &tehHeight, false); + + // update the cache for next time + objcache[aa].xwas = objs[aa].x; + objcache[aa].ywas = objs[aa].y; + int atxp = data_to_game_coord(objs[aa].x); + int atyp = data_to_game_coord(objs[aa].y) - tehHeight; + + int usebasel = objs[aa].get_baseline(); + + if (objs[aa].flags & OBJF_NOWALKBEHINDS) { + // ignore walk-behinds, do nothing + if (walkBehindMethod == DrawAsSeparateSprite) + { + usebasel += thisroom.Height; + } + } + else if (walkBehindMethod == DrawAsSeparateCharSprite) + { + sort_out_char_sprite_walk_behind(useindx, atxp, atyp, usebasel, objs[aa].zoom, objs[aa].last_width, objs[aa].last_height); + } + else if ((!actspsIntact) && (walkBehindMethod == DrawOverCharSprite)) + { + sort_out_walk_behinds(actsps[useindx], atxp, atyp, usebasel); + } + + if ((!actspsIntact) || (actspsbmp[useindx] == nullptr)) + { + bool hasAlpha = (game.SpriteInfos[objs[aa].num].Flags & SPF_ALPHACHANNEL) != 0; + + if (actspsbmp[useindx] != nullptr) + gfxDriver->DestroyDDB(actspsbmp[useindx]); + actspsbmp[useindx] = gfxDriver->CreateDDBFromBitmap(actsps[useindx], hasAlpha); + } + + if (gfxDriver->HasAcceleratedTransform()) + { + actspsbmp[useindx]->SetFlippedLeftRight(objcache[aa].mirroredWas != 0); + actspsbmp[useindx]->SetStretch(objs[aa].last_width, objs[aa].last_height); + actspsbmp[useindx]->SetTint(objcache[aa].tintredwas, objcache[aa].tintgrnwas, objcache[aa].tintbluwas, (objcache[aa].tintamntwas * 256) / 100); + + if (objcache[aa].tintamntwas > 0) + { + if (objcache[aa].tintlightwas == 0) // luminance of 0 -- pass 1 to enable + actspsbmp[useindx]->SetLightLevel(1); + else if (objcache[aa].tintlightwas < 250) + actspsbmp[useindx]->SetLightLevel(objcache[aa].tintlightwas); + else + actspsbmp[useindx]->SetLightLevel(0); + } + else if (objcache[aa].lightlevwas != 0) + actspsbmp[useindx]->SetLightLevel((objcache[aa].lightlevwas * 25) / 10 + 256); + else + actspsbmp[useindx]->SetLightLevel(0); + } + + add_to_sprite_list(actspsbmp[useindx], atxp, atyp, usebasel, objs[aa].transparent,objs[aa].num); + } +} + + + +// Draws srcimg onto destimg, tinting to the specified level +// Totally overwrites the contents of the destination image +void tint_image (Bitmap *ds, Bitmap *srcimg, int red, int grn, int blu, int light_level, int luminance) { + + if ((srcimg->GetColorDepth() != ds->GetColorDepth()) || + (srcimg->GetColorDepth() <= 8)) { + debug_script_warn("Image tint failed - images must both be hi-color"); + // the caller expects something to have been copied + ds->Blit(srcimg, 0, 0, 0, 0, srcimg->GetWidth(), srcimg->GetHeight()); + return; + } + + // For performance reasons, we have a seperate blender for + // when light is being adjusted and when it is not. + // If luminance >= 250, then normal brightness, otherwise darken + if (luminance >= 250) + set_blender_mode (_myblender_color15, _myblender_color16, _myblender_color32, red, grn, blu, 0); + else + set_blender_mode (_myblender_color15_light, _myblender_color16_light, _myblender_color32_light, red, grn, blu, 0); + + if (light_level >= 100) { + // fully colourised + ds->FillTransparent(); + ds->LitBlendBlt(srcimg, 0, 0, luminance); + } + else { + // light_level is between -100 and 100 normally; 0-100 in + // this case when it's a RGB tint + light_level = (light_level * 25) / 10; + + // Copy the image to the new bitmap + ds->Blit(srcimg, 0, 0, 0, 0, srcimg->GetWidth(), srcimg->GetHeight()); + // Render the colourised image to a temporary bitmap, + // then transparently draw it over the original image + Bitmap *finaltarget = BitmapHelper::CreateTransparentBitmap(srcimg->GetWidth(), srcimg->GetHeight(), srcimg->GetColorDepth()); + finaltarget->LitBlendBlt(srcimg, 0, 0, luminance); + + // customized trans blender to preserve alpha channel + set_my_trans_blender (0, 0, 0, light_level); + ds->TransBlendBlt (finaltarget, 0, 0); + delete finaltarget; + } +} + + + + +void prepare_characters_for_drawing() { + int zoom_level,newwidth,newheight,onarea,sppic; + int light_level,coldept; + int tint_red, tint_green, tint_blue, tint_amount, tint_light = 255; + + our_eip=33; + + // draw characters + for (int aa=0; aa < game.numcharacters; aa++) { + if (game.chars[aa].on==0) continue; + if (game.chars[aa].room!=displayed_room) continue; + eip_guinum = aa; + const int useindx = aa + MAX_ROOM_OBJECTS; + + CharacterInfo*chin=&game.chars[aa]; + our_eip = 330; + // if it's on but set to view -1, they're being silly + if (chin->view < 0) { + quitprintf("!The character '%s' was turned on in the current room (room %d) but has not been assigned a view number.", + chin->name, displayed_room); + } + + if (chin->frame >= views[chin->view].loops[chin->loop].numFrames) + chin->frame = 0; + + if ((chin->loop >= views[chin->view].numLoops) || + (views[chin->view].loops[chin->loop].numFrames < 1)) { + quitprintf("!The character '%s' could not be displayed because there were no frames in loop %d of view %d.", + chin->name, chin->loop, chin->view + 1); + } + + sppic=views[chin->view].loops[chin->loop].frames[chin->frame].pic; + if (sppic < 0) + sppic = 0; // in case it's screwed up somehow + our_eip = 331; + // sort out the stretching if required + onarea = get_walkable_area_at_character (aa); + our_eip = 332; + + // calculate the zoom level + if (chin->flags & CHF_MANUALSCALING) // character ignores scaling + zoom_level = charextra[aa].zoom; + else if ((onarea <= 0) && (thisroom.WalkAreas[0].ScalingFar == 0)) { + zoom_level = charextra[aa].zoom; + // NOTE: room objects don't have this fix + if (zoom_level == 0) + zoom_level = 100; + } + else + zoom_level = get_area_scaling (onarea, chin->x, chin->y); + + charextra[aa].zoom = zoom_level; + + tint_red = tint_green = tint_blue = tint_amount = tint_light = light_level = 0; + + if (chin->flags & CHF_HASTINT) { + // object specific tint, use it + tint_red = charextra[aa].tint_r; + tint_green = charextra[aa].tint_g; + tint_blue = charextra[aa].tint_b; + tint_amount = charextra[aa].tint_level; + tint_light = charextra[aa].tint_light; + light_level = 0; + } + else if (chin->flags & CHF_HASLIGHT) + { + light_level = charextra[aa].tint_light; + } + else { + get_local_tint(chin->x, chin->y, chin->flags & CHF_NOLIGHTING, + &tint_amount, &tint_red, &tint_green, &tint_blue, + &tint_light, &light_level); + } + + our_eip = 3330; + int isMirrored = 0, specialpic = sppic; + bool usingCachedImage = false; + + coldept = spriteset[sppic]->GetColorDepth(); + + // adjust the sppic if mirrored, so it doesn't accidentally + // cache the mirrored frame as the real one + if (views[chin->view].loops[chin->loop].frames[chin->frame].flags & VFLG_FLIPSPRITE) { + isMirrored = 1; + specialpic = -sppic; + } + + our_eip = 3331; + + // if the character was the same sprite and scaling last time, + // just use the cached image + if ((charcache[aa].inUse) && + (charcache[aa].sppic == specialpic) && + (charcache[aa].scaling == zoom_level) && + (charcache[aa].tintredwas == tint_red) && + (charcache[aa].tintgrnwas == tint_green) && + (charcache[aa].tintbluwas == tint_blue) && + (charcache[aa].tintamntwas == tint_amount) && + (charcache[aa].tintlightwas == tint_light) && + (charcache[aa].lightlevwas == light_level)) + { + if (walkBehindMethod == DrawOverCharSprite) + { + actsps[useindx] = recycle_bitmap(actsps[useindx], charcache[aa].image->GetColorDepth(), charcache[aa].image->GetWidth(), charcache[aa].image->GetHeight()); + actsps[useindx]->Blit (charcache[aa].image, 0, 0, 0, 0, actsps[useindx]->GetWidth(), actsps[useindx]->GetHeight()); + } + else + { + usingCachedImage = true; + } + } + else if ((charcache[aa].inUse) && + (charcache[aa].sppic == specialpic) && + (gfxDriver->HasAcceleratedTransform())) + { + usingCachedImage = true; + } + else if (charcache[aa].inUse) { + //destroy_bitmap (charcache[aa].image); + charcache[aa].inUse = 0; + } + + our_eip = 3332; + + if (zoom_level != 100) { + // it needs to be stretched, so calculate the new dimensions + + scale_sprite_size(sppic, zoom_level, &newwidth, &newheight); + charextra[aa].width=newwidth; + charextra[aa].height=newheight; + } + else { + // draw at original size, so just use the sprite width and height + // TODO: store width and height always, that's much simplier to use for reference! + charextra[aa].width=0; + charextra[aa].height=0; + newwidth = game.SpriteInfos[sppic].Width; + newheight = game.SpriteInfos[sppic].Height; + } + + our_eip = 3336; + + // Calculate the X & Y co-ordinates of where the sprite will be + const int atxp =(data_to_game_coord(chin->x)) - newwidth/2; + const int atyp =(data_to_game_coord(chin->y) - newheight) + // adjust the Y positioning for the character's Z co-ord + - data_to_game_coord(chin->z); + + charcache[aa].scaling = zoom_level; + charcache[aa].sppic = specialpic; + charcache[aa].tintredwas = tint_red; + charcache[aa].tintgrnwas = tint_green; + charcache[aa].tintbluwas = tint_blue; + charcache[aa].tintamntwas = tint_amount; + charcache[aa].tintlightwas = tint_light; + charcache[aa].lightlevwas = light_level; + + // If cache needs to be re-drawn + if (!charcache[aa].inUse) { + + // create the base sprite in actsps[useindx], which will + // be scaled and/or flipped, as appropriate + int actspsUsed = 0; + if (!gfxDriver->HasAcceleratedTransform()) + { + actspsUsed = scale_and_flip_sprite( + useindx, coldept, zoom_level, sppic, + newwidth, newheight, isMirrored); + } + else + { + // ensure actsps exists + actsps[useindx] = recycle_bitmap(actsps[useindx], coldept, game.SpriteInfos[sppic].Width, game.SpriteInfos[sppic].Height); + } + + our_eip = 335; + + if (((light_level != 0) || (tint_amount != 0)) && + (!gfxDriver->HasAcceleratedTransform())) { + // apply the lightening or tinting + Bitmap *comeFrom = nullptr; + // if possible, direct read from the source image + if (!actspsUsed) + comeFrom = spriteset[sppic]; + + apply_tint_or_light(useindx, light_level, tint_amount, tint_red, + tint_green, tint_blue, tint_light, coldept, + comeFrom); + } + else if (!actspsUsed) { + // no scaling, flipping or tinting was done, so just blit it normally + actsps[useindx]->Blit (spriteset[sppic], 0, 0, 0, 0, actsps[useindx]->GetWidth(), actsps[useindx]->GetHeight()); + } + + // update the character cache with the new image + charcache[aa].inUse = 1; + //charcache[aa].image = BitmapHelper::CreateBitmap_ (coldept, actsps[useindx]->GetWidth(), actsps[useindx]->GetHeight()); + charcache[aa].image = recycle_bitmap(charcache[aa].image, coldept, actsps[useindx]->GetWidth(), actsps[useindx]->GetHeight()); + charcache[aa].image->Blit (actsps[useindx], 0, 0, 0, 0, actsps[useindx]->GetWidth(), actsps[useindx]->GetHeight()); + + } // end if !cache.inUse + + int usebasel = chin->get_baseline(); + + our_eip = 336; + + const int bgX = atxp + chin->pic_xoffs; + const int bgY = atyp + chin->pic_yoffs; + + if (chin->flags & CHF_NOWALKBEHINDS) { + // ignore walk-behinds, do nothing + if (walkBehindMethod == DrawAsSeparateSprite) + { + usebasel += thisroom.Height; + } + } + else if (walkBehindMethod == DrawAsSeparateCharSprite) + { + sort_out_char_sprite_walk_behind(useindx, bgX, bgY, usebasel, charextra[aa].zoom, newwidth, newheight); + } + else if (walkBehindMethod == DrawOverCharSprite) + { + sort_out_walk_behinds(actsps[useindx], bgX, bgY, usebasel); + } + + if ((!usingCachedImage) || (actspsbmp[useindx] == nullptr)) + { + bool hasAlpha = (game.SpriteInfos[sppic].Flags & SPF_ALPHACHANNEL) != 0; + + actspsbmp[useindx] = recycle_ddb_bitmap(actspsbmp[useindx], actsps[useindx], hasAlpha); + } + + if (gfxDriver->HasAcceleratedTransform()) + { + actspsbmp[useindx]->SetStretch(newwidth, newheight); + actspsbmp[useindx]->SetFlippedLeftRight(isMirrored != 0); + actspsbmp[useindx]->SetTint(tint_red, tint_green, tint_blue, (tint_amount * 256) / 100); + + if (tint_amount != 0) + { + if (tint_light == 0) // tint with 0 luminance, pass as 1 instead + actspsbmp[useindx]->SetLightLevel(1); + else if (tint_light < 250) + actspsbmp[useindx]->SetLightLevel(tint_light); + else + actspsbmp[useindx]->SetLightLevel(0); + } + else if (light_level != 0) + actspsbmp[useindx]->SetLightLevel((light_level * 25) / 10 + 256); + else + actspsbmp[useindx]->SetLightLevel(0); + + } + + our_eip = 337; + + chin->actx = atxp; + chin->acty = atyp; + + add_to_sprite_list(actspsbmp[useindx], bgX, bgY, usebasel, chin->transparency, sppic); + } +} + + +// Compiles a list of room sprites (characters, objects, background) +void prepare_room_sprites() +{ + // Background sprite is required for the non-software renderers always, + // and for software renderer in case there are overlapping viewports. + // Note that software DDB is just a tiny wrapper around bitmap, so overhead is negligible. + if (roomBackgroundBmp == nullptr) + { + update_polled_stuff_if_runtime(); + roomBackgroundBmp = gfxDriver->CreateDDBFromBitmap(thisroom.BgFrames[play.bg_frame].Graphic.get(), false, true); + } + else if (current_background_is_dirty) + { + update_polled_stuff_if_runtime(); + gfxDriver->UpdateDDBFromBitmap(roomBackgroundBmp, thisroom.BgFrames[play.bg_frame].Graphic.get(), false); + } + if (gfxDriver->RequiresFullRedrawEachFrame()) + { + if (current_background_is_dirty || walkBehindsCachedForBgNum != play.bg_frame) + { + if (walkBehindMethod == DrawAsSeparateSprite) + { + update_walk_behind_images(); + } + } + add_thing_to_draw(roomBackgroundBmp, 0, 0, 0, false); + } + current_background_is_dirty = false; // Note this is only place where this flag is checked + + clear_sprite_list(); + + if ((debug_flags & DBG_NOOBJECTS) == 0) + { + prepare_objects_for_drawing(); + prepare_characters_for_drawing(); + + if ((debug_flags & DBG_NODRAWSPRITES) == 0) + { + our_eip = 34; + draw_sprite_list(); + } + } + our_eip = 36; +} + +// Draws the black surface behind (or rather between) the room viewports +void draw_preroom_background() +{ + if (gfxDriver->RequiresFullRedrawEachFrame()) + return; + update_black_invreg_and_reset(gfxDriver->GetMemoryBackBuffer()); +} + +// Draws the room background on the given surface. +// +// NOTE that this is **strictly** for software rendering. +// ds is a full game screen surface, and roomcam_surface is a surface for drawing room camera content to. +// ds and roomcam_surface may be the same bitmap. +// no_transform flag tells to copy dirty regions on roomcam_surface without any coordinate conversion +// whatsoever. +PBitmap draw_room_background(Viewport *view, const SpriteTransform &room_trans) +{ + our_eip = 31; + + // For the sake of software renderer, if there is any kind of camera transform required + // except screen offset, we tell it to draw on separate bitmap first with zero transformation. + // There are few reasons for this, primary is that Allegro does not support StretchBlt + // between different colour depths (i.e. it won't correctly stretch blit 16-bit rooms to + // 32-bit virtual screen). + // Also see comment to ALSoftwareGraphicsDriver::RenderToBackBuffer(). + const int view_index = view->GetID(); + Bitmap *ds = gfxDriver->GetMemoryBackBuffer(); + // If separate bitmap was prepared for this view/camera pair then use it, draw untransformed + // and blit transformed whole surface later. + const bool draw_to_camsurf = CameraDrawData[view_index].Frame != nullptr; + Bitmap *roomcam_surface = draw_to_camsurf ? CameraDrawData[view_index].Frame.get() : ds; + { + // For software renderer: copy dirty rects onto the virtual screen. + // TODO: that would be SUPER NICE to reorganize the code and move this operation into SoftwareGraphicDriver somehow. + // Because basically we duplicate sprite batch transform here. + + auto camera = view->GetCamera(); + set_invalidrects_cameraoffs(view_index, camera->GetRect().Left, camera->GetRect().Top); + + // TODO: (by CJ) + // the following line takes up to 50% of the game CPU time at + // high resolutions and colour depths - if we can optimise it + // somehow, significant performance gains to be had + update_room_invreg_and_reset(view_index, roomcam_surface, thisroom.BgFrames[play.bg_frame].Graphic.get(), draw_to_camsurf); + } + + return CameraDrawData[view_index].Frame; +} + + +void draw_fps(const Rect &viewport) +{ + // TODO: make allocated "fps struct" instead of using static vars!! + static IDriverDependantBitmap* ddb = nullptr; + static Bitmap *fpsDisplay = nullptr; + const int font = FONT_NORMAL; + if (fpsDisplay == nullptr) + { + fpsDisplay = BitmapHelper::CreateBitmap(viewport.GetWidth(), (getfontheight_outlined(font) + get_fixed_pixel_size(5)), game.GetColorDepth()); + fpsDisplay = ReplaceBitmapWithSupportedFormat(fpsDisplay); + } + fpsDisplay->ClearTransparent(); + + color_t text_color = fpsDisplay->GetCompatibleColor(14); + + char base_buffer[20]; + if (!isTimerFpsMaxed()) { + sprintf(base_buffer, "%d", frames_per_second); + } else { + sprintf(base_buffer, "unlimited"); + } + + char fps_buffer[60]; + // Don't display fps if we don't have enough information (because loop count was just reset) + if (!std::isnan(fps)) { + snprintf(fps_buffer, sizeof(fps_buffer), "FPS: %2.1f / %s", fps, base_buffer); + } else { + snprintf(fps_buffer, sizeof(fps_buffer), "FPS: --.- / %s", base_buffer); + } + wouttext_outline(fpsDisplay, 1, 1, font, text_color, fps_buffer); + + char loop_buffer[60]; + sprintf(loop_buffer, "Loop %u", loopcounter); + wouttext_outline(fpsDisplay, viewport.GetWidth() / 2, 1, font, text_color, loop_buffer); + + if (ddb) + gfxDriver->UpdateDDBFromBitmap(ddb, fpsDisplay, false); + else + ddb = gfxDriver->CreateDDBFromBitmap(fpsDisplay, false); + int yp = viewport.GetHeight() - fpsDisplay->GetHeight(); + gfxDriver->DrawSprite(1, yp, ddb); + invalidate_sprite(1, yp, ddb, false); +} + +// Draw GUI and overlays of all kinds, anything outside the room space +void draw_gui_and_overlays() +{ + if(pl_any_want_hook(AGSE_PREGUIDRAW)) + add_thing_to_draw(nullptr, AGSE_PREGUIDRAW, 0, TRANS_RUN_PLUGIN, false); + + // draw overlays, except text boxes and portraits + for (const auto &over : screenover) { + // complete overlay draw in non-transparent mode + if (over.type == OVER_COMPLETE) + add_thing_to_draw(over.bmp, over.x, over.y, TRANS_OPAQUE, false); + else if (over.type != OVER_TEXTMSG && over.type != OVER_PICTURE) { + int tdxp, tdyp; + get_overlay_position(over, &tdxp, &tdyp); + add_thing_to_draw(over.bmp, tdxp, tdyp, 0, over.hasAlphaChannel); + } + } + + // Draw GUIs - they should always be on top of overlays like + // speech background text + our_eip=35; + if (((debug_flags & DBG_NOIFACE)==0) && (displayed_room >= 0)) { + int aa; + + if (playerchar->activeinv >= MAX_INV) { + quit("!The player.activeinv variable has been corrupted, probably as a result\n" + "of an incorrect assignment in the game script."); + } + if (playerchar->activeinv < 1) gui_inv_pic=-1; + else gui_inv_pic=game.invinfo[playerchar->activeinv].pic; + our_eip = 37; + if (guis_need_update) { + guis_need_update = 0; + for (aa=0;aaClearTransparent(); + our_eip = 372; + guis[aa].DrawAt(guibg[aa], 0,0); + our_eip = 373; + + bool isAlpha = false; + if (guis[aa].HasAlphaChannel()) + { + isAlpha = true; + + if ((game.options[OPT_NEWGUIALPHA] == kGuiAlphaRender_Legacy) && (guis[aa].BgImage > 0)) + { + // old-style (pre-3.0.2) GUI alpha rendering + repair_alpha_channel(guibg[aa], spriteset[guis[aa].BgImage]); + } + } + + if (guibgbmp[aa] != nullptr) + { + gfxDriver->UpdateDDBFromBitmap(guibgbmp[aa], guibg[aa], isAlpha); + } + else + { + guibgbmp[aa] = gfxDriver->CreateDDBFromBitmap(guibg[aa], isAlpha); + } + our_eip = 374; + } + } + our_eip = 38; + // Draw the GUIs + for (int gg = 0; gg < game.numgui; gg++) { + aa = play.gui_draw_order[gg]; + if (!guis[aa].IsDisplayed()) continue; + + // Don't draw GUI if "GUIs Turn Off When Disabled" + if ((game.options[OPT_DISABLEOFF] == 3) && + (all_buttons_disabled > 0) && + (guis[aa].PopupStyle != kGUIPopupNoAutoRemove)) + continue; + + add_thing_to_draw(guibgbmp[aa], guis[aa].X, guis[aa].Y, guis[aa].Transparency, guis[aa].HasAlphaChannel()); + + // only poll if the interface is enabled (mouseovers should not + // work while in Wait state) + if (IsInterfaceEnabled()) + guis[aa].Poll(); + } + } + + // draw speech and portraits (so that they appear over GUIs) + for (const auto &over : screenover) + { + if (over.type == OVER_TEXTMSG || over.type == OVER_PICTURE) + { + int tdxp, tdyp; + get_overlay_position(over, &tdxp, &tdyp); + add_thing_to_draw(over.bmp, tdxp, tdyp, 0, false); + } + } + + our_eip = 1099; +} + +// Push the gathered list of sprites into the active graphic renderer +void put_sprite_list_on_screen(bool in_room) +{ + // *** Draw the Things To Draw List *** + + SpriteListEntry *thisThing; + + for (size_t i = 0; i < thingsToDrawList.size(); ++i) + { + thisThing = &thingsToDrawList[i]; + + if (thisThing->bmp != nullptr) { + // mark the image's region as dirty + invalidate_sprite(thisThing->x, thisThing->y, thisThing->bmp, in_room); + } + else if ((thisThing->transparent != TRANS_RUN_PLUGIN) && + (thisThing->bmp == nullptr)) + { + quit("Null pointer added to draw list"); + } + + if (thisThing->bmp != nullptr) + { + if (thisThing->transparent <= 255) + { + thisThing->bmp->SetTransparency(thisThing->transparent); + } + + gfxDriver->DrawSprite(thisThing->x, thisThing->y, thisThing->bmp); + } + else if (thisThing->transparent == TRANS_RUN_PLUGIN) + { + // meta entry to run the plugin hook + gfxDriver->DrawSprite(thisThing->x, thisThing->y, nullptr); + } + else + quit("Unknown entry in draw list"); + } + + our_eip = 1100; +} + +bool GfxDriverNullSpriteCallback(int x, int y) +{ + if (displayed_room < 0) + { + // if no room loaded, various stuff won't be initialized yet + return 1; + } + return (pl_run_plugin_hooks(x, y) != 0); +} + +void GfxDriverOnInitCallback(void *data) +{ + pl_run_plugin_init_gfx_hooks(gfxDriver->GetDriverID(), data); +} + +// Schedule room rendering: background, objects, characters +static void construct_room_view() +{ + draw_preroom_background(); + prepare_room_sprites(); + // reset the Baselines Changed flag now that we've drawn stuff + walk_behind_baselines_changed = 0; + + for (const auto &viewport : play.GetRoomViewportsZOrdered()) + { + if (!viewport->IsVisible()) + continue; + auto camera = viewport->GetCamera(); + if (!camera) + continue; + const Rect &view_rc = play.GetRoomViewportAbs(viewport->GetID()); + const Rect &cam_rc = camera->GetRect(); + SpriteTransform room_trans(-cam_rc.Left, -cam_rc.Top, + (float)view_rc.GetWidth() / (float)cam_rc.GetWidth(), + (float)view_rc.GetHeight() / (float)cam_rc.GetHeight(), + 0.f); + if (gfxDriver->RequiresFullRedrawEachFrame()) + { // we draw everything as a sprite stack + gfxDriver->BeginSpriteBatch(view_rc, room_trans, Point(0, play.shake_screen_yoff), (GlobalFlipType)play.screen_flipped); + } + else + { + if (CameraDrawData[viewport->GetID()].Frame == nullptr && CameraDrawData[viewport->GetID()].IsOverlap) + { // room background is prepended to the sprite stack + // TODO: here's why we have blit whole piece of background now: + // if we draw directly to the virtual screen overlapping another + // viewport, then we'd have to also mark and repaint every our + // region located directly over their dirty regions. That would + // require to update regions up the stack, converting their + // coordinates (cam1 -> screen -> cam2). + // It's not clear whether this is worth the effort, but if it is, + // then we'd need to optimise view/cam data first. + gfxDriver->BeginSpriteBatch(view_rc, room_trans); + gfxDriver->DrawSprite(0, 0, roomBackgroundBmp); + } + else + { // room background is drawn by dirty rects system + PBitmap bg_surface = draw_room_background(viewport.get(), room_trans); + gfxDriver->BeginSpriteBatch(view_rc, room_trans, Point(), kFlip_None, bg_surface); + } + } + put_sprite_list_on_screen(true); + } + + clear_draw_list(); +} + +// Schedule ui rendering +static void construct_ui_view() +{ + gfxDriver->BeginSpriteBatch(play.GetUIViewportAbs(), SpriteTransform(), Point(0, play.shake_screen_yoff), (GlobalFlipType)play.screen_flipped); + draw_gui_and_overlays(); + put_sprite_list_on_screen(false); + clear_draw_list(); +} + +void construct_game_scene(bool full_redraw) +{ + gfxDriver->ClearDrawLists(); + + if (play.fast_forward) + return; + + our_eip=3; + + // React to changes to viewports and cameras (possibly from script) just before the render + play.UpdateViewports(); + + gfxDriver->UseSmoothScaling(IS_ANTIALIAS_SPRITES); + gfxDriver->RenderSpritesAtScreenResolution(usetup.RenderAtScreenRes, usetup.Supersampling); + + pl_run_plugin_hooks(AGSE_PRERENDER, 0); + + // Possible reasons to invalidate whole screen for the software renderer + if (full_redraw || play.screen_tint > 0 || play.shakesc_length > 0) + invalidate_screen(); + + // TODO: move to game update! don't call update during rendering pass! + // IMPORTANT: keep the order same because sometimes script may depend on it + if (displayed_room >= 0) + play.UpdateRoomCameras(); + + // Stage: room viewports + if (play.screen_is_faded_out == 0 && is_complete_overlay == 0) + { + if (displayed_room >= 0) + { + construct_room_view(); + update_polled_mp3(); + } + else if (!gfxDriver->RequiresFullRedrawEachFrame()) + { + // black it out so we don't get cursor trails + // TODO: this is possible to do with dirty rects system now too (it can paint black rects outside of room viewport) + gfxDriver->GetMemoryBackBuffer()->Fill(0); + } + } + + our_eip=4; + + // Stage: UI overlay + if (play.screen_is_faded_out == 0) + { + construct_ui_view(); + } +} + +void construct_game_screen_overlay(bool draw_mouse) +{ + gfxDriver->BeginSpriteBatch(play.GetMainViewport(), SpriteTransform(), Point(0, play.shake_screen_yoff), (GlobalFlipType)play.screen_flipped); + if (pl_any_want_hook(AGSE_POSTSCREENDRAW)) + gfxDriver->DrawSprite(AGSE_POSTSCREENDRAW, 0, nullptr); + + // TODO: find out if it's okay to move cursor animation and state update + // to the update loop instead of doing it in the drawing routine + // update animating mouse cursor + if (game.mcurs[cur_cursor].view >= 0) { + ags_domouse(DOMOUSE_NOCURSOR); + // only on mousemove, and it's not moving + if (((game.mcurs[cur_cursor].flags & MCF_ANIMMOVE) != 0) && + (mousex == lastmx) && (mousey == lastmy)); + // only on hotspot, and it's not on one + else if (((game.mcurs[cur_cursor].flags & MCF_HOTSPOT) != 0) && + (GetLocationType(game_to_data_coord(mousex), game_to_data_coord(mousey)) == 0)) + set_new_cursor_graphic(game.mcurs[cur_cursor].pic); + else if (mouse_delay>0) mouse_delay--; + else { + int viewnum = game.mcurs[cur_cursor].view; + int loopnum = 0; + if (loopnum >= views[viewnum].numLoops) + quitprintf("An animating mouse cursor is using view %d which has no loops", viewnum + 1); + if (views[viewnum].loops[loopnum].numFrames < 1) + quitprintf("An animating mouse cursor is using view %d which has no frames in loop %d", viewnum + 1, loopnum); + + mouse_frame++; + if (mouse_frame >= views[viewnum].loops[loopnum].numFrames) + mouse_frame = 0; + set_new_cursor_graphic(views[viewnum].loops[loopnum].frames[mouse_frame].pic); + mouse_delay = views[viewnum].loops[loopnum].frames[mouse_frame].speed + 5; + CheckViewFrame(viewnum, loopnum, mouse_frame); + } + lastmx = mousex; lastmy = mousey; + } + + ags_domouse(DOMOUSE_NOCURSOR); + + // Stage: mouse cursor + if (draw_mouse && !play.mouse_cursor_hidden && play.screen_is_faded_out == 0) + { + gfxDriver->DrawSprite(mousex - hotx, mousey - hoty, mouseCursor); + invalidate_sprite(mousex - hotx, mousey - hoty, mouseCursor, false); + } + + if (play.screen_is_faded_out == 0) + { + // Stage: screen fx + if (play.screen_tint >= 1) + gfxDriver->SetScreenTint(play.screen_tint & 0xff, (play.screen_tint >> 8) & 0xff, (play.screen_tint >> 16) & 0xff); + // Stage: legacy letterbox mode borders + render_black_borders(); + } + + if (play.screen_is_faded_out != 0 && gfxDriver->RequiresFullRedrawEachFrame()) + { + const Rect &main_viewport = play.GetMainViewport(); + gfxDriver->BeginSpriteBatch(main_viewport, SpriteTransform()); + gfxDriver->SetScreenFade(play.fade_to_red, play.fade_to_green, play.fade_to_blue); + } +} + +void construct_engine_overlay() +{ + const Rect &viewport = RectWH(game.GetGameRes()); + gfxDriver->BeginSpriteBatch(viewport, SpriteTransform()); + + // draw the debug console, if appropriate + if ((play.debug_mode > 0) && (display_console != 0)) + { + const int font = FONT_NORMAL; + int ypp = 1; + int txtspacing = getfontspacing_outlined(font); + int barheight = getheightoflines(font, DEBUG_CONSOLE_NUMLINES - 1) + 4; + + if (debugConsoleBuffer == nullptr) + { + debugConsoleBuffer = BitmapHelper::CreateBitmap(viewport.GetWidth(), barheight, game.GetColorDepth()); + debugConsoleBuffer = ReplaceBitmapWithSupportedFormat(debugConsoleBuffer); + } + + color_t draw_color = debugConsoleBuffer->GetCompatibleColor(15); + debugConsoleBuffer->FillRect(Rect(0, 0, viewport.GetWidth() - 1, barheight), draw_color); + color_t text_color = debugConsoleBuffer->GetCompatibleColor(16); + for (int jj = first_debug_line; jj != last_debug_line; jj = (jj + 1) % DEBUG_CONSOLE_NUMLINES) { + wouttextxy(debugConsoleBuffer, 1, ypp, font, text_color, debug_line[jj]); + ypp += txtspacing; + } + + if (debugConsole == nullptr) + debugConsole = gfxDriver->CreateDDBFromBitmap(debugConsoleBuffer, false, true); + else + gfxDriver->UpdateDDBFromBitmap(debugConsole, debugConsoleBuffer, false); + + gfxDriver->DrawSprite(0, 0, debugConsole); + invalidate_sprite(0, 0, debugConsole, false); + } + + if (display_fps != kFPS_Hide) + draw_fps(viewport); +} + +static void update_shakescreen() +{ + // TODO: unify blocking and non-blocking shake update + play.shake_screen_yoff = 0; + if (play.shakesc_length > 0) + { + if ((loopcounter % play.shakesc_delay) < (play.shakesc_delay / 2)) + play.shake_screen_yoff = play.shakesc_amount; + } +} + +// Draw everything +void render_graphics(IDriverDependantBitmap *extraBitmap, int extraX, int extraY) +{ + // Don't render if skipping cutscene + if (play.fast_forward) + return; + // Don't render if we've just entered new room and are before fade-in + // TODO: find out why this is not skipped for 8-bit games + if ((in_new_room > 0) & (game.color_depth > 1)) + return; + + // TODO: find out if it's okay to move shake to update function + update_shakescreen(); + + construct_game_scene(false); + our_eip=5; + // NOTE: extraBitmap will always be drawn with the UI render stage + if (extraBitmap != nullptr) + { + invalidate_sprite(extraX, extraY, extraBitmap, false); + gfxDriver->DrawSprite(extraX, extraY, extraBitmap); + } + construct_game_screen_overlay(true); + render_to_screen(); + + if (!play.screen_is_faded_out) { + // always update the palette, regardless of whether the plugin + // vetos the screen update + if (bg_just_changed) { + setpal(); + bg_just_changed = 0; + } + } + + screen_is_dirty = false; +} diff --git a/engines/ags/engine/ac/draw.h b/engines/ags/engine/ac/draw.h new file mode 100644 index 00000000000..af6e8e93549 --- /dev/null +++ b/engines/ags/engine/ac/draw.h @@ -0,0 +1,175 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__DRAW_H +#define __AGS_EE_AC__DRAW_H + +#include +#include "core/types.h" +#include "ac/common_defines.h" +#include "gfx/gfx_def.h" +#include "util/wgt2allg.h" + +namespace AGS +{ + namespace Common + { + class Bitmap; + typedef std::shared_ptr PBitmap; + } + namespace Engine { class IDriverDependantBitmap; } +} +using namespace AGS; // FIXME later + +#define IS_ANTIALIAS_SPRITES usetup.enable_antialiasing && (play.disable_antialiasing == 0) + +// [IKM] WARNING: these definitions has to be made AFTER Allegro headers +// were included, because they override few Allegro function names; +// otherwise Allegro headers should not be included at all to the same +// code unit which uses these defines. +#define getr32(xx) ((xx >> _rgb_r_shift_32) & 0xFF) +#define getg32(xx) ((xx >> _rgb_g_shift_32) & 0xFF) +#define getb32(xx) ((xx >> _rgb_b_shift_32) & 0xFF) +#define geta32(xx) ((xx >> _rgb_a_shift_32) & 0xFF) +#define makeacol32(r,g,b,a) ((r << _rgb_r_shift_32) | (g << _rgb_g_shift_32) | (b << _rgb_b_shift_32) | (a << _rgb_a_shift_32)) + + +struct CachedActSpsData { + int xWas, yWas; + int baselineWas; + int isWalkBehindHere; + int valid; +}; + +// Converts AGS color index to the actual bitmap color using game's color depth +int MakeColor(int color_index); + +class Viewport; +class Camera; + +// Initializes drawing methods and optimisation +void init_draw_method(); +// Initializes drawing resources upon entering new room +void init_room_drawdata(); +// Disposes resources related to the current drawing methods +void dispose_draw_method(); +// Disposes any temporary resources on leaving current room +void dispose_room_drawdata(); +// Updates drawing settings depending on main viewport's size and position on screen +void on_mainviewport_changed(); +// Notifies that a new room viewport was created +void on_roomviewport_created(int index); +// Notifies that a new room viewport was deleted +void on_roomviewport_deleted(int index); +// Updates drawing settings if room viewport's position or size has changed +void on_roomviewport_changed(Viewport *view); +// Detects overlapping viewports, starting from the given index in z-sorted array +void detect_roomviewport_overlaps(size_t z_index); +// Updates drawing settings if room camera's size has changed +void on_roomcamera_changed(Camera *cam); + +// whether there are currently remnants of a DisplaySpeech +void mark_screen_dirty(); +bool is_screen_dirty(); + +// marks whole screen as needing a redraw +void invalidate_screen(); +// marks all the camera frame as needing a redraw +void invalidate_camera_frame(int index); +// marks certain rectangle on screen as needing a redraw +// in_room flag tells how to interpret the coordinates: as in-room coords or screen viewport coordinates. +void invalidate_rect(int x1, int y1, int x2, int y2, bool in_room); + +void mark_current_background_dirty(); +void invalidate_cached_walkbehinds(); +// Avoid freeing and reallocating the memory if possible +Common::Bitmap *recycle_bitmap(Common::Bitmap *bimp, int coldep, int wid, int hit, bool make_transparent = false); +Engine::IDriverDependantBitmap* recycle_ddb_bitmap(Engine::IDriverDependantBitmap *bimp, Common::Bitmap *source, bool hasAlpha = false, bool opaque = false); +// Draw everything +void render_graphics(Engine::IDriverDependantBitmap *extraBitmap = nullptr, int extraX = 0, int extraY = 0); +// Construct game scene, scheduling drawing list for the renderer +void construct_game_scene(bool full_redraw = false); +// Construct final game screen elements; updates and draws mouse cursor +void construct_game_screen_overlay(bool draw_mouse = true); +// Construct engine overlay with debugging tools (fps, console) +void construct_engine_overlay(); +void add_to_sprite_list(Engine::IDriverDependantBitmap* spp, int xx, int yy, int baseline, int trans, int sprNum, bool isWalkBehind = false); +void tint_image (Common::Bitmap *g, Common::Bitmap *source, int red, int grn, int blu, int light_level, int luminance=255); +void draw_sprite_support_alpha(Common::Bitmap *ds, bool ds_has_alpha, int xpos, int ypos, Common::Bitmap *image, bool src_has_alpha, + Common::BlendMode blend_mode = Common::kBlendMode_Alpha, int alpha = 0xFF); +void draw_sprite_slot_support_alpha(Common::Bitmap *ds, bool ds_has_alpha, int xpos, int ypos, int src_slot, + Common::BlendMode blend_mode = Common::kBlendMode_Alpha, int alpha = 0xFF); +void draw_gui_sprite(Common::Bitmap *ds, int pic, int x, int y, bool use_alpha, Common::BlendMode blend_mode); +void draw_gui_sprite_v330(Common::Bitmap *ds, int pic, int x, int y, bool use_alpha = true, Common::BlendMode blend_mode = Common::kBlendMode_Alpha); +// Render game on screen +void render_to_screen(); +// Callbacks for the graphics driver +void draw_game_screen_callback(); +void GfxDriverOnInitCallback(void *data); +bool GfxDriverNullSpriteCallback(int x, int y); +void putpixel_compensate (Common::Bitmap *g, int xx,int yy, int col); +// create the actsps[aa] image with the object drawn correctly +// returns 1 if nothing at all has changed and actsps is still +// intact from last time; 0 otherwise +int construct_object_gfx(int aa, int *drawnWidth, int *drawnHeight, bool alwaysUseSoftware); +void clear_letterbox_borders(); + +void draw_and_invalidate_text(Common::Bitmap *ds, int x1, int y1, int font, color_t text_color, const char *text); + +void setpal(); + +// These functions are converting coordinates between data resolution and +// game resolution units. The first are units used by game data and script, +// and second define the game's screen resolution, sprite and font sizes. +// This conversion is done before anything else (like moving from room to +// viewport on screen, or scaling game further in the window by the graphic +// renderer). +extern AGS_INLINE int get_fixed_pixel_size(int pixels); +// coordinate conversion data,script ---> final game resolution +extern AGS_INLINE int data_to_game_coord(int coord); +extern AGS_INLINE void data_to_game_coords(int *x, int *y); +extern AGS_INLINE void data_to_game_round_up(int *x, int *y); +// coordinate conversion final game resolution ---> data,script +extern AGS_INLINE int game_to_data_coord(int coord); +extern AGS_INLINE void game_to_data_coords(int &x, int &y); +extern AGS_INLINE int game_to_data_round_up(int coord); +// convert contextual data coordinates to final game resolution +extern AGS_INLINE void ctx_data_to_game_coord(int &x, int &y, bool hires_ctx); +extern AGS_INLINE void ctx_data_to_game_size(int &x, int &y, bool hires_ctx); +extern AGS_INLINE int ctx_data_to_game_size(int size, bool hires_ctx); +extern AGS_INLINE int game_to_ctx_data_size(int size, bool hires_ctx); +// This function converts game coordinates coming from script to the actual game resolution. +extern AGS_INLINE void defgame_to_finalgame_coords(int &x, int &y); + +// Checks if the bitmap needs to be converted and **deletes original** if a new bitmap +// had to be created (by default). +// TODO: this helper function was meant to remove bitmap deletion from the GraphicsDriver's +// implementations while keeping code changes to minimum. The proper solution would probably +// be to use shared pointers when storing Bitmaps, or make Bitmap reference-counted object. +Common::Bitmap *ReplaceBitmapWithSupportedFormat(Common::Bitmap *bitmap); +// Checks if the bitmap needs any kind of adjustments before it may be used +// in AGS sprite operations. Also handles number of certain special cases +// (old systems or uncommon gfx modes, and similar stuff). +// Original bitmap **gets deleted** if a new bitmap had to be created. +Common::Bitmap *PrepareSpriteForUse(Common::Bitmap *bitmap, bool has_alpha); +// Same as above, but compatible for std::shared_ptr. +Common::PBitmap PrepareSpriteForUse(Common::PBitmap bitmap, bool has_alpha); +// Makes a screenshot corresponding to the last screen render and returns it as a bitmap +// of the requested width and height and game's native color depth. +Common::Bitmap *CopyScreenIntoBitmap(int width, int height, bool at_native_res = false); + +#endif // __AGS_EE_AC__DRAW_H diff --git a/engines/ags/engine/ac/draw_software.cpp b/engines/ags/engine/ac/draw_software.cpp new file mode 100644 index 00000000000..721b676924f --- /dev/null +++ b/engines/ags/engine/ac/draw_software.cpp @@ -0,0 +1,491 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Software drawing component. Optimizes drawing for software renderer using +// dirty rectangles technique. +// +// TODO: do research/profiling to find out if this dirty rectangles thing +// is still giving ANY notable perfomance boost at all. +// +// TODO: would that give any benefit to reorganize the code and move dirty +// rectangles into SoftwareGraphicDriver? +// Alternatively: we could pass dirty rects struct pointer and room background +// DDB when calling BeginSpriteBatch(). Driver itself could be calling +// update_invalid_region(). That will keep gfx driver's changes to minimum. +// +// NOTE: this code, including structs and functions, has underwent several +// iterations of changes. Originally it was meant to perform full transform +// of dirty rects right away, but later I realized it won't work that way +// because a) Allegro does not support scaling bitmaps over destination with +// different colour depth (which may be a case when running 16-bit game), +// and b) Allegro does not support scaling and rotating of sprites with +// blending and lighting at the same time which means that room objects have +// to be drawn upon non-scaled background first. Possibly some of the code +// below may be therefore simplified. +// +//============================================================================= + +#include +#include +#include "ac/draw_software.h" +#include "gfx/bitmap.h" +#include "util/scaling.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +// TODO: choose these values depending on game resolution? +#define MAXDIRTYREGIONS 25 +#define WHOLESCREENDIRTY (MAXDIRTYREGIONS + 5) +#define MAX_SPANS_PER_ROW 4 + +// Dirty rects store coordinate values in the coordinate system of a camera surface, +// where coords always span from 0,0 to surface width,height. +// Converting from room to dirty rects would require subtracting room camera offsets. +struct IRSpan +{ + int x1, x2; + int mergeSpan(int tx1, int tx2); + + IRSpan(); +}; + +struct IRRow +{ + IRSpan span[MAX_SPANS_PER_ROW]; + int numSpans; + + IRRow(); +}; + +struct DirtyRects +{ + // Size of the surface managed by this dirty rects object + Size SurfaceSize; + // Where the surface is rendered on screen + Rect Viewport; + // Room -> screen coordinate transformation + PlaneScaling Room2Screen; + // Screen -> dirty surface rect + // The dirty rects are saved in coordinates limited to (0,0)->(camera size) rather than room or screen coords + PlaneScaling Screen2DirtySurf; + + std::vector DirtyRows; + Rect DirtyRegions[MAXDIRTYREGIONS]; + size_t NumDirtyRegions; + + DirtyRects(); + bool IsInit() const; + // Initialize dirty rects for the given surface size + void Init(const Size &surf_size, const Rect &viewport); + void SetSurfaceOffsets(int x, int y); + // Delete dirty rects + void Destroy(); + // Mark all surface as tidy + void Reset(); +}; + + +IRSpan::IRSpan() + : x1(0), x2(0) +{ +} + +IRRow::IRRow() + : numSpans(0) +{ +} + +int IRSpan::mergeSpan(int tx1, int tx2) +{ + if ((tx1 > x2) || (tx2 < x1)) + return 0; + // overlapping, increase the span + if (tx1 < x1) + x1 = tx1; + if (tx2 > x2) + x2 = tx2; + return 1; +} + +DirtyRects::DirtyRects() + : NumDirtyRegions(0) +{ +} + +bool DirtyRects::IsInit() const +{ + return DirtyRows.size() > 0; +} + +void DirtyRects::Init(const Size &surf_size, const Rect &viewport) +{ + int height = surf_size.Height; + if (SurfaceSize != surf_size) + { + Destroy(); + SurfaceSize = surf_size; + DirtyRows.resize(height); + + NumDirtyRegions = WHOLESCREENDIRTY; + for (int i = 0; i < height; ++i) + DirtyRows[i].numSpans = 0; + } + + Viewport = viewport; + Room2Screen.Init(surf_size, viewport); + Screen2DirtySurf.Init(viewport, RectWH(0, 0, surf_size.Width, surf_size.Height)); +} + +void DirtyRects::SetSurfaceOffsets(int x, int y) +{ + Room2Screen.SetSrcOffsets(x, y); +} + +void DirtyRects::Destroy() +{ + DirtyRows.clear(); + NumDirtyRegions = 0; +} + +void DirtyRects::Reset() +{ + NumDirtyRegions = 0; + + for (size_t i = 0; i < DirtyRows.size(); ++i) + DirtyRows[i].numSpans = 0; +} + +// Dirty rects for the main viewport background (black screen); +// these are used when the room viewport does not cover whole screen, +// so that we know when to paint black after mouse cursor and gui. +DirtyRects BlackRects; +// Dirty rects object for the single room camera +std::vector RoomCamRects; +// Saved room camera offsets to know if we must invalidate whole surface. +// TODO: if we support rotation then we also need to compare full transform! +std::vector> RoomCamPositions; + + +void dispose_invalid_regions(bool /* room_only */) +{ + RoomCamRects.clear(); + RoomCamPositions.clear(); +} + +void init_invalid_regions(int view_index, const Size &surf_size, const Rect &viewport) +{ + if (view_index < 0) + { + BlackRects.Init(surf_size, viewport); + } + else + { + if (RoomCamRects.size() <= (size_t)view_index) + { + RoomCamRects.resize(view_index + 1); + RoomCamPositions.resize(view_index + 1); + } + RoomCamRects[view_index].Init(surf_size, viewport); + RoomCamPositions[view_index] = std::make_pair(-1000, -1000); + } +} + +void delete_invalid_regions(int view_index) +{ + if (view_index >= 0) + { + RoomCamRects.erase(RoomCamRects.begin() + view_index); + RoomCamPositions.erase(RoomCamPositions.begin() + view_index); + } +} + +void set_invalidrects_cameraoffs(int view_index, int x, int y) +{ + if (view_index < 0) + { + BlackRects.SetSurfaceOffsets(x, y); + return; + } + else + { + RoomCamRects[view_index].SetSurfaceOffsets(x, y); + } + + int &posxwas = RoomCamPositions[view_index].first; + int &posywas = RoomCamPositions[view_index].second; + if ((x != posxwas) || (y != posywas)) + { + invalidate_all_camera_rects(view_index); + posxwas = x; + posywas = y; + } +} + +void invalidate_all_rects() +{ + for (auto &rects : RoomCamRects) + { + if (!IsRectInsideRect(rects.Viewport, BlackRects.Viewport)) + BlackRects.NumDirtyRegions = WHOLESCREENDIRTY; + rects.NumDirtyRegions = WHOLESCREENDIRTY; + } +} + +void invalidate_all_camera_rects(int view_index) +{ + if (view_index < 0) + return; + RoomCamRects[view_index].NumDirtyRegions = WHOLESCREENDIRTY; +} + +void invalidate_rect_on_surf(int x1, int y1, int x2, int y2, DirtyRects &rects) +{ + if (rects.DirtyRows.size() == 0) + return; + if (rects.NumDirtyRegions >= MAXDIRTYREGIONS) { + // too many invalid rectangles, just mark the whole thing dirty + rects.NumDirtyRegions = WHOLESCREENDIRTY; + return; + } + + int a; + + const Size &surfsz = rects.SurfaceSize; + if (x1 >= surfsz.Width) x1 = surfsz.Width - 1; + if (y1 >= surfsz.Height) y1 = surfsz.Height - 1; + if (x2 >= surfsz.Width) x2 = surfsz.Width - 1; + if (y2 >= surfsz.Height) y2 = surfsz.Height - 1; + if (x1 < 0) x1 = 0; + if (y1 < 0) y1 = 0; + if (x2 < 0) x2 = 0; + if (y2 < 0) y2 = 0; + rects.NumDirtyRegions++; + + // ** Span code + std::vector &dirtyRow = rects.DirtyRows; + int s, foundOne; + // add this rect to the list for this row + for (a = y1; a <= y2; a++) { + foundOne = 0; + for (s = 0; s < dirtyRow[a].numSpans; s++) { + if (dirtyRow[a].span[s].mergeSpan(x1, x2)) { + foundOne = 1; + break; + } + } + if (foundOne) { + // we were merged into a span, so we're ok + int t; + // check whether now two of the spans overlap each other + // in which case merge them + for (s = 0; s < dirtyRow[a].numSpans; s++) { + for (t = s + 1; t < dirtyRow[a].numSpans; t++) { + if (dirtyRow[a].span[s].mergeSpan(dirtyRow[a].span[t].x1, dirtyRow[a].span[t].x2)) { + dirtyRow[a].numSpans--; + for (int u = t; u < dirtyRow[a].numSpans; u++) + dirtyRow[a].span[u] = dirtyRow[a].span[u + 1]; + break; + } + } + } + } + else if (dirtyRow[a].numSpans < MAX_SPANS_PER_ROW) { + dirtyRow[a].span[dirtyRow[a].numSpans].x1 = x1; + dirtyRow[a].span[dirtyRow[a].numSpans].x2 = x2; + dirtyRow[a].numSpans++; + } + else { + // didn't fit in an existing span, and there are none spare + int nearestDist = 99999, nearestWas = -1, extendLeft; + int tleft, tright; + // find the nearest span, and enlarge that to include this rect + for (s = 0; s < dirtyRow[a].numSpans; s++) { + tleft = dirtyRow[a].span[s].x1 - x2; + if ((tleft > 0) && (tleft < nearestDist)) { + nearestDist = tleft; + nearestWas = s; + extendLeft = 1; + } + tright = x1 - dirtyRow[a].span[s].x2; + if ((tright > 0) && (tright < nearestDist)) { + nearestDist = tright; + nearestWas = s; + extendLeft = 0; + } + } + if (extendLeft) + dirtyRow[a].span[nearestWas].x1 = x1; + else + dirtyRow[a].span[nearestWas].x2 = x2; + } + } + // ** End span code + //} +} + +void invalidate_rect_ds(DirtyRects &rects, int x1, int y1, int x2, int y2, bool in_room) +{ + if (!in_room) + { + // TODO: for most opimisation (esp. with multiple viewports) should perhaps + // split/cut parts of the original rectangle which overlap room viewport(s). + Rect r(x1, y1, x2, y2); + // If overlay is NOT completely over the room, then invalidate black rect + if (!IsRectInsideRect(rects.Viewport, r)) + invalidate_rect_on_surf(x1, y1, x2, y2, BlackRects); + // If overlay is NOT intersecting room viewport at all, then stop + if (!AreRectsIntersecting(rects.Viewport, r)) + return; + + // Transform from screen to room coordinates through the known viewport + x1 = rects.Screen2DirtySurf.X.ScalePt(x1); + x2 = rects.Screen2DirtySurf.X.ScalePt(x2); + y1 = rects.Screen2DirtySurf.Y.ScalePt(y1); + y2 = rects.Screen2DirtySurf.Y.ScalePt(y2); + } + else + { + x1 -= rects.Room2Screen.X.GetSrcOffset(); + y1 -= rects.Room2Screen.Y.GetSrcOffset(); + x2 -= rects.Room2Screen.X.GetSrcOffset(); + y2 -= rects.Room2Screen.Y.GetSrcOffset(); + } + + invalidate_rect_on_surf(x1, y1, x2, y2, rects); +} + +void invalidate_rect_ds(int x1, int y1, int x2, int y2, bool in_room) +{ + for (auto &rects : RoomCamRects) + invalidate_rect_ds(rects, x1, y1, x2, y2, in_room); +} + +// Note that this function is denied to perform any kind of scaling or other transformation +// other than blitting with offset. This is mainly because destination could be a 32-bit virtual screen +// while room background was 16-bit and Allegro lib does not support stretching between colour depths. +// The no_transform flag here means essentially "no offset", and indicates that the function +// must blit src on ds at 0;0. Otherwise, actual Viewport offset is used. +void update_invalid_region(Bitmap *ds, Bitmap *src, const DirtyRects &rects, bool no_transform) +{ + if (rects.NumDirtyRegions == 0) + return; + + if (!no_transform) + ds->SetClip(rects.Viewport); + + const int src_x = rects.Room2Screen.X.GetSrcOffset(); + const int src_y = rects.Room2Screen.Y.GetSrcOffset(); + const int dst_x = no_transform ? 0 : rects.Viewport.Left; + const int dst_y = no_transform ? 0 : rects.Viewport.Top; + + if (rects.NumDirtyRegions == WHOLESCREENDIRTY) + { + ds->Blit(src, src_x, src_y, dst_x, dst_y, rects.SurfaceSize.Width, rects.SurfaceSize.Height); + } + else + { + const std::vector &dirtyRow = rects.DirtyRows; + const int surf_height = rects.SurfaceSize.Height; + // TODO: is this IsMemoryBitmap check is still relevant? + // If bitmaps properties match and no transform required other than linear offset + if ((src->GetColorDepth() == ds->GetColorDepth()) && (ds->IsMemoryBitmap())) + { + const int bypp = src->GetBPP(); + // do the fast memory copy + for (int i = 0; i < surf_height; i++) + { + const uint8_t *src_scanline = src->GetScanLine(i + src_y); + uint8_t *dst_scanline = ds->GetScanLineForWriting(i + dst_y); + const IRRow &dirty_row = dirtyRow[i]; + for (int k = 0; k < dirty_row.numSpans; k++) + { + int tx1 = dirty_row.span[k].x1; + int tx2 = dirty_row.span[k].x2; + memcpy(&dst_scanline[(tx1 + dst_x) * bypp], &src_scanline[(tx1 + src_x) * bypp], ((tx2 - tx1) + 1) * bypp); + } + } + } + // If has to use Blit, but still must draw with no transform but offset + else + { + // do fast copy without transform + for (int i = 0, rowsInOne = 1; i < surf_height; i += rowsInOne, rowsInOne = 1) + { + // if there are rows with identical masks, do them all in one go + // TODO: what is this for? may this be done at the invalidate_rect merge step? + while ((i + rowsInOne < surf_height) && (memcmp(&dirtyRow[i], &dirtyRow[i + rowsInOne], sizeof(IRRow)) == 0)) + rowsInOne++; + + const IRRow &dirty_row = dirtyRow[i]; + for (int k = 0; k < dirty_row.numSpans; k++) + { + int tx1 = dirty_row.span[k].x1; + int tx2 = dirty_row.span[k].x2; + ds->Blit(src, tx1 + src_x, i + src_y, tx1 + dst_x, i + dst_y, (tx2 - tx1) + 1, rowsInOne); + } + } + } + } +} + +void update_invalid_region(Bitmap *ds, color_t fill_color, const DirtyRects &rects) +{ + ds->SetClip(rects.Viewport); + + if (rects.NumDirtyRegions == WHOLESCREENDIRTY) + { + ds->FillRect(rects.Viewport, fill_color); + } + else + { + const std::vector &dirtyRow = rects.DirtyRows; + const int surf_height = rects.SurfaceSize.Height; + { + const PlaneScaling &tf = rects.Room2Screen; + for (int i = 0, rowsInOne = 1; i < surf_height; i += rowsInOne, rowsInOne = 1) + { + // if there are rows with identical masks, do them all in one go + // TODO: what is this for? may this be done at the invalidate_rect merge step? + while ((i + rowsInOne < surf_height) && (memcmp(&dirtyRow[i], &dirtyRow[i + rowsInOne], sizeof(IRRow)) == 0)) + rowsInOne++; + + const IRRow &dirty_row = dirtyRow[i]; + for (int k = 0; k < dirty_row.numSpans; k++) + { + Rect src_r(dirty_row.span[k].x1, i, dirty_row.span[k].x2, i + rowsInOne - 1); + Rect dst_r = tf.ScaleRange(src_r); + ds->FillRect(dst_r, fill_color); + } + } + } + } +} + +void update_black_invreg_and_reset(Bitmap *ds) +{ + if (!BlackRects.IsInit()) + return; + update_invalid_region(ds, (color_t)0, BlackRects); + BlackRects.Reset(); +} + +void update_room_invreg_and_reset(int view_index, Bitmap *ds, Bitmap *src, bool no_transform) +{ + if (view_index < 0 || RoomCamRects.size() == 0) + return; + + update_invalid_region(ds, src, RoomCamRects[view_index], no_transform); + RoomCamRects[view_index].Reset(); +} diff --git a/engines/ags/engine/ac/draw_software.h b/engines/ags/engine/ac/draw_software.h new file mode 100644 index 00000000000..47db5b227f2 --- /dev/null +++ b/engines/ags/engine/ac/draw_software.h @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Software drawing component. Optimizes drawing for software renderer using +// dirty rectangles technique. +// +//============================================================================= +#ifndef __AGS_EE_AC__DRAWSOFTWARE_H +#define __AGS_EE_AC__DRAWSOFTWARE_H + +#include "gfx/bitmap.h" +#include "gfx/ddb.h" +#include "util/geometry.h" + +// Inits dirty rects array for the given room camera/viewport pair +// View_index indicates the room viewport (>= 0) or the main viewport (-1) +void init_invalid_regions(int view_index, const Size &surf_size, const Rect &viewport); +// Deletes dirty rects for particular index +void delete_invalid_regions(int view_index); +// Disposes dirty rects arrays +void dispose_invalid_regions(bool room_only); +// Update the coordinate transformation for the particular dirty rects object +void set_invalidrects_cameraoffs(int view_index, int x, int y); +// Mark the whole screen dirty +void invalidate_all_rects(); +// Mark the whole camera surface dirty +void invalidate_all_camera_rects(int view_index); +void invalidate_rect_ds(int x1, int y1, int x2, int y2, bool in_room); +// Paints the black screen background in the regions marked as dirty +void update_black_invreg_and_reset(AGS::Common::Bitmap *ds); +// Copies the room regions marked as dirty from source (src) to destination (ds) with the given offset (x, y) +// no_transform flag tells the system that the regions should be plain copied to the ds. +void update_room_invreg_and_reset(int view_index, AGS::Common::Bitmap *ds, AGS::Common::Bitmap *src, bool no_transform); + +#endif // __AGS_EE_AC__DRAWSOFTWARE_H diff --git a/engines/ags/engine/ac/drawingsurface.cpp b/engines/ags/engine/ac/drawingsurface.cpp new file mode 100644 index 00000000000..5dc0ccdf9e2 --- /dev/null +++ b/engines/ags/engine/ac/drawingsurface.cpp @@ -0,0 +1,694 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/draw.h" +#include "ac/drawingsurface.h" +#include "ac/common.h" +#include "ac/charactercache.h" +#include "ac/display.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_translation.h" +#include "ac/objectcache.h" +#include "ac/roomobject.h" +#include "ac/roomstatus.h" +#include "ac/string.h" +#include "ac/walkbehind.h" +#include "debug/debug_log.h" +#include "font/fonts.h" +#include "gui/guimain.h" +#include "ac/spritecache.h" +#include "script/runtimescriptvalue.h" +#include "gfx/gfx_def.h" +#include "gfx/gfx_util.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern GameState play; +extern RoomStatus*croom; +extern RoomObject*objs; +extern CharacterCache *charcache; +extern ObjectCache objcache[MAX_ROOM_OBJECTS]; +extern SpriteCache spriteset; +extern Bitmap *dynamicallyCreatedSurfaces[MAX_DYNAMIC_SURFACES]; + +// ** SCRIPT DRAWINGSURFACE OBJECT + +void DrawingSurface_Release(ScriptDrawingSurface* sds) +{ + if (sds->roomBackgroundNumber >= 0) + { + if (sds->modified) + { + if (sds->roomBackgroundNumber == play.bg_frame) + { + invalidate_screen(); + mark_current_background_dirty(); + } + play.raw_modified[sds->roomBackgroundNumber] = 1; + } + + sds->roomBackgroundNumber = -1; + } + if (sds->roomMaskType > kRoomAreaNone) + { + if (sds->roomMaskType == kRoomAreaWalkBehind) + { + recache_walk_behinds(); + } + sds->roomMaskType = kRoomAreaNone; + } + if (sds->dynamicSpriteNumber >= 0) + { + if (sds->modified) + { + int tt; + // force a refresh of any cached object or character images + if (croom != nullptr) + { + for (tt = 0; tt < croom->numobj; tt++) + { + if (objs[tt].num == sds->dynamicSpriteNumber) + objcache[tt].sppic = -31999; + } + } + for (tt = 0; tt < game.numcharacters; tt++) + { + if (charcache[tt].sppic == sds->dynamicSpriteNumber) + charcache[tt].sppic = -31999; + } + for (tt = 0; tt < game.numgui; tt++) + { + if ((guis[tt].BgImage == sds->dynamicSpriteNumber) && + (guis[tt].IsDisplayed())) + { + guis_need_update = 1; + break; + } + } + } + + sds->dynamicSpriteNumber = -1; + } + if (sds->dynamicSurfaceNumber >= 0) + { + delete dynamicallyCreatedSurfaces[sds->dynamicSurfaceNumber]; + dynamicallyCreatedSurfaces[sds->dynamicSurfaceNumber] = nullptr; + sds->dynamicSurfaceNumber = -1; + } + sds->modified = 0; +} + +void ScriptDrawingSurface::PointToGameResolution(int *xcoord, int *ycoord) +{ + ctx_data_to_game_coord(*xcoord, *ycoord, highResCoordinates != 0); +} + +void ScriptDrawingSurface::SizeToGameResolution(int *width, int *height) +{ + ctx_data_to_game_size(*width, *height, highResCoordinates != 0); +} + +void ScriptDrawingSurface::SizeToGameResolution(int *valueToAdjust) +{ + *valueToAdjust = ctx_data_to_game_size(*valueToAdjust, highResCoordinates != 0); +} + +// convert actual co-ordinate back to what the script is expecting +void ScriptDrawingSurface::SizeToDataResolution(int *valueToAdjust) +{ + *valueToAdjust = game_to_ctx_data_size(*valueToAdjust, highResCoordinates != 0); +} + +ScriptDrawingSurface* DrawingSurface_CreateCopy(ScriptDrawingSurface *sds) +{ + Bitmap *sourceBitmap = sds->GetBitmapSurface(); + + for (int i = 0; i < MAX_DYNAMIC_SURFACES; i++) + { + if (dynamicallyCreatedSurfaces[i] == nullptr) + { + dynamicallyCreatedSurfaces[i] = BitmapHelper::CreateBitmapCopy(sourceBitmap); + ScriptDrawingSurface *newSurface = new ScriptDrawingSurface(); + newSurface->dynamicSurfaceNumber = i; + newSurface->hasAlphaChannel = sds->hasAlphaChannel; + ccRegisterManagedObject(newSurface, newSurface); + return newSurface; + } + } + + quit("!DrawingSurface.CreateCopy: too many copied surfaces created"); + return nullptr; +} + +void DrawingSurface_DrawImageImpl(ScriptDrawingSurface* sds, Bitmap* src, int dst_x, int dst_y, int trans, int dst_width, int dst_height, + int src_x, int src_y, int src_width, int src_height, int sprite_id, bool src_has_alpha) +{ + Bitmap *ds = sds->GetBitmapSurface(); + if (src == ds) + quit("!DrawingSurface.DrawImage: cannot draw onto itself"); + if ((trans < 0) || (trans > 100)) + quit("!DrawingSurface.DrawImage: invalid transparency setting"); + + if (trans == 100) + return; // fully transparent + if (dst_width < 1 || dst_height < 1 || src_width < 1 || src_height < 1) + return; // invalid src or dest rectangles + + // Setup uninitialized arguments; convert coordinates for legacy script mode + if (dst_width == SCR_NO_VALUE) { dst_width = src->GetWidth(); } + else { sds->SizeToGameResolution(&dst_width); } + if (dst_height == SCR_NO_VALUE) { dst_height = src->GetHeight(); } + else { sds->SizeToGameResolution(&dst_height); } + + if (src_x == SCR_NO_VALUE) { src_x = 0; } + if (src_y == SCR_NO_VALUE) { src_y = 0; } + sds->PointToGameResolution(&src_x, &src_y); + if (src_width == SCR_NO_VALUE) { src_width = src->GetWidth(); } + else { sds->SizeToGameResolution(&src_width); } + if (src_height == SCR_NO_VALUE) { src_height = src->GetHeight(); } + else { sds->SizeToGameResolution(&src_height); } + + if (dst_x >= ds->GetWidth() || dst_x + dst_width <= 0 || dst_y >= ds->GetHeight() || dst_y + dst_height <= 0 || + src_x >= src->GetWidth() || src_x + src_width <= 0 || src_y >= src->GetHeight() || src_y + src_height <= 0) + return; // source or destination rects lie completely off surface + // Clamp the source rect to the valid limits to prevent exceptions (ignore dest, bitmap drawing deals with that) + Math::ClampLength(src_x, src_width, 0, src->GetWidth()); + Math::ClampLength(src_y, src_height, 0, src->GetHeight()); + + // TODO: possibly optimize by not making a stretched intermediate bitmap + // if simplier blit/draw_sprite could be called (no translucency with alpha channel). + bool needToFreeBitmap = false; + if (dst_width != src->GetWidth() || dst_height != src->GetHeight() || + src_width != src->GetWidth() || src_height != src->GetHeight()) + { + // Resize and/or partial copy specified + Bitmap *newPic = BitmapHelper::CreateBitmap(dst_width, dst_height, src->GetColorDepth()); + newPic->StretchBlt(src, + RectWH(src_x, src_y, src_width, src_height), + RectWH(0, 0, dst_width, dst_height)); + + src = newPic; + needToFreeBitmap = true; + update_polled_stuff_if_runtime(); + } + + ds = sds->StartDrawing(); + sds->PointToGameResolution(&dst_x, &dst_y); + + if (src->GetColorDepth() != ds->GetColorDepth()) { + if (sprite_id >= 0) + debug_script_warn("DrawImage: Sprite %d colour depth %d-bit not same as background depth %d-bit", sprite_id, src->GetColorDepth(), ds->GetColorDepth()); + else + debug_script_warn("DrawImage: Source image colour depth %d-bit not same as background depth %d-bit", src->GetColorDepth(), ds->GetColorDepth()); + } + + draw_sprite_support_alpha(ds, sds->hasAlphaChannel != 0, dst_x, dst_y, src, src_has_alpha, + kBlendMode_Alpha, GfxDef::Trans100ToAlpha255(trans)); + + sds->FinishedDrawing(); + + if (needToFreeBitmap) + delete src; +} + +void DrawingSurface_DrawImageEx(ScriptDrawingSurface* sds, int dst_x, int dst_y, int slot, int trans, int dst_width, int dst_height, + int src_x, int src_y, int src_width, int src_height) +{ + if ((slot < 0) || (spriteset[slot] == nullptr)) + quit("!DrawingSurface.DrawImage: invalid sprite slot number specified"); + DrawingSurface_DrawImageImpl(sds, spriteset[slot], dst_x, dst_y, trans, dst_width, dst_height, + src_x, src_y, src_width, src_height, slot, (game.SpriteInfos[slot].Flags & SPF_ALPHACHANNEL) != 0); +} + +void DrawingSurface_DrawImage(ScriptDrawingSurface* sds, int xx, int yy, int slot, int trans, int width, int height) +{ + DrawingSurface_DrawImageEx(sds, xx, yy, slot, trans, width, height, 0, 0, SCR_NO_VALUE, SCR_NO_VALUE); +} + +void DrawingSurface_DrawSurfaceEx(ScriptDrawingSurface* target, ScriptDrawingSurface* source, int trans, + int dst_x, int dst_y, int dst_width, int dst_height, + int src_x, int src_y, int src_width, int src_height) +{ + DrawingSurface_DrawImageImpl(target, source->GetBitmapSurface(), dst_x, dst_y, trans, dst_width, dst_height, + src_x, src_y, src_width, src_height, -1, source->hasAlphaChannel); +} + +void DrawingSurface_DrawSurface(ScriptDrawingSurface* target, ScriptDrawingSurface* source, int trans) +{ + DrawingSurface_DrawSurfaceEx(target, source, trans, 0, 0, SCR_NO_VALUE, SCR_NO_VALUE, 0, 0, SCR_NO_VALUE, SCR_NO_VALUE); +} + +void DrawingSurface_SetDrawingColor(ScriptDrawingSurface *sds, int newColour) +{ + sds->currentColourScript = newColour; + // StartDrawing to set up ds to set the colour at the appropriate + // depth for the background + Bitmap *ds = sds->StartDrawing(); + if (newColour == SCR_COLOR_TRANSPARENT) + { + sds->currentColour = ds->GetMaskColor(); + } + else + { + sds->currentColour = ds->GetCompatibleColor(newColour); + } + sds->FinishedDrawingReadOnly(); +} + +int DrawingSurface_GetDrawingColor(ScriptDrawingSurface *sds) +{ + return sds->currentColourScript; +} + +void DrawingSurface_SetUseHighResCoordinates(ScriptDrawingSurface *sds, int highRes) +{ + if (game.AllowRelativeRes()) + sds->highResCoordinates = (highRes) ? 1 : 0; +} + +int DrawingSurface_GetUseHighResCoordinates(ScriptDrawingSurface *sds) +{ + return sds->highResCoordinates; +} + +int DrawingSurface_GetHeight(ScriptDrawingSurface *sds) +{ + Bitmap *ds = sds->GetBitmapSurface(); + int height = ds->GetHeight(); + sds->SizeToGameResolution(&height); + return height; +} + +int DrawingSurface_GetWidth(ScriptDrawingSurface *sds) +{ + Bitmap *ds = sds->GetBitmapSurface(); + int width = ds->GetWidth(); + sds->SizeToGameResolution(&width); + return width; +} + +void DrawingSurface_Clear(ScriptDrawingSurface *sds, int colour) +{ + Bitmap *ds = sds->StartDrawing(); + int allegroColor; + if ((colour == -SCR_NO_VALUE) || (colour == SCR_COLOR_TRANSPARENT)) + { + allegroColor = ds->GetMaskColor(); + } + else + { + allegroColor = ds->GetCompatibleColor(colour); + } + ds->Fill(allegroColor); + sds->FinishedDrawing(); +} + +void DrawingSurface_DrawCircle(ScriptDrawingSurface *sds, int x, int y, int radius) +{ + sds->PointToGameResolution(&x, &y); + sds->SizeToGameResolution(&radius); + + Bitmap *ds = sds->StartDrawing(); + ds->FillCircle(Circle(x, y, radius), sds->currentColour); + sds->FinishedDrawing(); +} + +void DrawingSurface_DrawRectangle(ScriptDrawingSurface *sds, int x1, int y1, int x2, int y2) +{ + sds->PointToGameResolution(&x1, &y1); + sds->PointToGameResolution(&x2, &y2); + + Bitmap *ds = sds->StartDrawing(); + ds->FillRect(Rect(x1,y1,x2,y2), sds->currentColour); + sds->FinishedDrawing(); +} + +void DrawingSurface_DrawTriangle(ScriptDrawingSurface *sds, int x1, int y1, int x2, int y2, int x3, int y3) +{ + sds->PointToGameResolution(&x1, &y1); + sds->PointToGameResolution(&x2, &y2); + sds->PointToGameResolution(&x3, &y3); + + Bitmap *ds = sds->StartDrawing(); + ds->DrawTriangle(Triangle(x1,y1,x2,y2,x3,y3), sds->currentColour); + sds->FinishedDrawing(); +} + +void DrawingSurface_DrawString(ScriptDrawingSurface *sds, int xx, int yy, int font, const char* text) +{ + sds->PointToGameResolution(&xx, &yy); + Bitmap *ds = sds->StartDrawing(); + // don't use wtextcolor because it will do a 16->32 conversion + color_t text_color = sds->currentColour; + if ((ds->GetColorDepth() <= 8) && (play.raw_color > 255)) { + text_color = ds->GetCompatibleColor(1); + debug_script_warn ("RawPrint: Attempted to use hi-color on 256-col background"); + } + wouttext_outline(ds, xx, yy, font, text_color, text); + sds->FinishedDrawing(); +} + +void DrawingSurface_DrawStringWrapped_Old(ScriptDrawingSurface *sds, int xx, int yy, int wid, int font, int alignment, const char *msg) { + DrawingSurface_DrawStringWrapped(sds, xx, yy, wid, font, ConvertLegacyScriptAlignment((LegacyScriptAlignment)alignment), msg); +} + +void DrawingSurface_DrawStringWrapped(ScriptDrawingSurface *sds, int xx, int yy, int wid, int font, int alignment, const char *msg) { + int linespacing = getfontspacing_outlined(font); + sds->PointToGameResolution(&xx, &yy); + sds->SizeToGameResolution(&wid); + + if (break_up_text_into_lines(msg, Lines, wid, font) == 0) + return; + + Bitmap *ds = sds->StartDrawing(); + color_t text_color = sds->currentColour; + + for (size_t i = 0; i < Lines.Count(); i++) + { + int drawAtX = xx; + + if (alignment & kMAlignHCenter) + { + drawAtX = xx + ((wid / 2) - wgettextwidth(Lines[i], font) / 2); + } + else if (alignment & kMAlignRight) + { + drawAtX = (xx + wid) - wgettextwidth(Lines[i], font); + } + + wouttext_outline(ds, drawAtX, yy + linespacing*i, font, text_color, Lines[i]); + } + + sds->FinishedDrawing(); +} + +void DrawingSurface_DrawMessageWrapped(ScriptDrawingSurface *sds, int xx, int yy, int wid, int font, int msgm) +{ + char displbuf[3000]; + get_message_text(msgm, displbuf); + // it's probably too late but check anyway + if (strlen(displbuf) > 2899) + quit("!RawPrintMessageWrapped: message too long"); + + DrawingSurface_DrawStringWrapped_Old(sds, xx, yy, wid, font, kLegacyScAlignLeft, displbuf); +} + +void DrawingSurface_DrawLine(ScriptDrawingSurface *sds, int fromx, int fromy, int tox, int toy, int thickness) { + sds->PointToGameResolution(&fromx, &fromy); + sds->PointToGameResolution(&tox, &toy); + sds->SizeToGameResolution(&thickness); + int ii,jj,xx,yy; + Bitmap *ds = sds->StartDrawing(); + // draw several lines to simulate the thickness + color_t draw_color = sds->currentColour; + for (ii = 0; ii < thickness; ii++) + { + xx = (ii - (thickness / 2)); + for (jj = 0; jj < thickness; jj++) + { + yy = (jj - (thickness / 2)); + ds->DrawLine (Line(fromx + xx, fromy + yy, tox + xx, toy + yy), draw_color); + } + } + sds->FinishedDrawing(); +} + +void DrawingSurface_DrawPixel(ScriptDrawingSurface *sds, int x, int y) { + sds->PointToGameResolution(&x, &y); + int thickness = 1; + sds->SizeToGameResolution(&thickness); + int ii,jj; + Bitmap *ds = sds->StartDrawing(); + // draw several pixels to simulate the thickness + color_t draw_color = sds->currentColour; + for (ii = 0; ii < thickness; ii++) + { + for (jj = 0; jj < thickness; jj++) + { + ds->PutPixel(x + ii, y + jj, draw_color); + } + } + sds->FinishedDrawing(); +} + +int DrawingSurface_GetPixel(ScriptDrawingSurface *sds, int x, int y) { + sds->PointToGameResolution(&x, &y); + Bitmap *ds = sds->StartDrawing(); + unsigned int rawPixel = ds->GetPixel(x, y); + unsigned int maskColor = ds->GetMaskColor(); + int colDepth = ds->GetColorDepth(); + + if (rawPixel == maskColor) + { + rawPixel = SCR_COLOR_TRANSPARENT; + } + else if (colDepth > 8) + { + int r = getr_depth(colDepth, rawPixel); + int ds = getg_depth(colDepth, rawPixel); + int b = getb_depth(colDepth, rawPixel); + + rawPixel = Game_GetColorFromRGB(r, ds, b); + } + + sds->FinishedDrawingReadOnly(); + + return rawPixel; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// void (ScriptDrawingSurface *sds, int colour) +RuntimeScriptValue Sc_DrawingSurface_Clear(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDrawingSurface, DrawingSurface_Clear); +} + +// ScriptDrawingSurface* (ScriptDrawingSurface *sds) +RuntimeScriptValue Sc_DrawingSurface_CreateCopy(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJAUTO(ScriptDrawingSurface, ScriptDrawingSurface, DrawingSurface_CreateCopy); +} + +// void (ScriptDrawingSurface *sds, int x, int y, int radius) +RuntimeScriptValue Sc_DrawingSurface_DrawCircle(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(ScriptDrawingSurface, DrawingSurface_DrawCircle); +} + +// void (ScriptDrawingSurface* sds, int xx, int yy, int slot, int trans, int width, int height) +RuntimeScriptValue Sc_DrawingSurface_DrawImage_6(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT6(ScriptDrawingSurface, DrawingSurface_DrawImage); +} + +RuntimeScriptValue Sc_DrawingSurface_DrawImage(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_OBJ_PARAM_COUNT(METHOD, 10); + DrawingSurface_DrawImageEx((ScriptDrawingSurface*)self, params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, params[5].IValue, + params[6].IValue, params[7].IValue, params[8].IValue, params[9].IValue); + return RuntimeScriptValue((int32_t)0); +} + +// void (ScriptDrawingSurface *sds, int fromx, int fromy, int tox, int toy, int thickness) +RuntimeScriptValue Sc_DrawingSurface_DrawLine(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5(ScriptDrawingSurface, DrawingSurface_DrawLine); +} + +// void (ScriptDrawingSurface *sds, int xx, int yy, int wid, int font, int msgm) +RuntimeScriptValue Sc_DrawingSurface_DrawMessageWrapped(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5(ScriptDrawingSurface, DrawingSurface_DrawMessageWrapped); +} + +// void (ScriptDrawingSurface *sds, int x, int y) +RuntimeScriptValue Sc_DrawingSurface_DrawPixel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(ScriptDrawingSurface, DrawingSurface_DrawPixel); +} + +// void (ScriptDrawingSurface *sds, int x1, int y1, int x2, int y2) +RuntimeScriptValue Sc_DrawingSurface_DrawRectangle(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(ScriptDrawingSurface, DrawingSurface_DrawRectangle); +} + +// void (ScriptDrawingSurface *sds, int xx, int yy, int font, const char* texx, ...) +RuntimeScriptValue Sc_DrawingSurface_DrawString(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_SCRIPT_SPRINTF(DrawingSurface_DrawString, 4); + DrawingSurface_DrawString((ScriptDrawingSurface*)self, params[0].IValue, params[1].IValue, params[2].IValue, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (ScriptDrawingSurface *sds, int xx, int yy, int wid, int font, int alignment, const char *msg) +RuntimeScriptValue Sc_DrawingSurface_DrawStringWrapped_Old(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5_POBJ(ScriptDrawingSurface, DrawingSurface_DrawStringWrapped_Old, const char); +} + +RuntimeScriptValue Sc_DrawingSurface_DrawStringWrapped(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5_POBJ(ScriptDrawingSurface, DrawingSurface_DrawStringWrapped, const char); +} + +// void (ScriptDrawingSurface* target, ScriptDrawingSurface* source, int translev) +RuntimeScriptValue Sc_DrawingSurface_DrawSurface_2(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ_PINT(ScriptDrawingSurface, DrawingSurface_DrawSurface, ScriptDrawingSurface); +} + +RuntimeScriptValue Sc_DrawingSurface_DrawSurface(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_OBJ_PARAM_COUNT(METHOD, 10); + DrawingSurface_DrawSurfaceEx((ScriptDrawingSurface*)self, (ScriptDrawingSurface*)params[0].Ptr, + params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, params[5].IValue, + params[6].IValue, params[7].IValue, params[8].IValue, params[9].IValue); + return RuntimeScriptValue((int32_t)0); +} + +// void (ScriptDrawingSurface *sds, int x1, int y1, int x2, int y2, int x3, int y3) +RuntimeScriptValue Sc_DrawingSurface_DrawTriangle(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT6(ScriptDrawingSurface, DrawingSurface_DrawTriangle); +} + +// int (ScriptDrawingSurface *sds, int x, int y) +RuntimeScriptValue Sc_DrawingSurface_GetPixel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT2(ScriptDrawingSurface, DrawingSurface_GetPixel); +} + +// void (ScriptDrawingSurface* sds) +RuntimeScriptValue Sc_DrawingSurface_Release(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptDrawingSurface, DrawingSurface_Release); +} + +// int (ScriptDrawingSurface *sds) +RuntimeScriptValue Sc_DrawingSurface_GetDrawingColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDrawingSurface, DrawingSurface_GetDrawingColor); +} + +// void (ScriptDrawingSurface *sds, int newColour) +RuntimeScriptValue Sc_DrawingSurface_SetDrawingColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDrawingSurface, DrawingSurface_SetDrawingColor); +} + +// int (ScriptDrawingSurface *sds) +RuntimeScriptValue Sc_DrawingSurface_GetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDrawingSurface, DrawingSurface_GetHeight); +} + +// int (ScriptDrawingSurface *sds) +RuntimeScriptValue Sc_DrawingSurface_GetUseHighResCoordinates(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDrawingSurface, DrawingSurface_GetUseHighResCoordinates); +} + +// void (ScriptDrawingSurface *sds, int highRes) +RuntimeScriptValue Sc_DrawingSurface_SetUseHighResCoordinates(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDrawingSurface, DrawingSurface_SetUseHighResCoordinates); +} + +// int (ScriptDrawingSurface *sds) +RuntimeScriptValue Sc_DrawingSurface_GetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDrawingSurface, DrawingSurface_GetWidth); +} + +//============================================================================= +// +// Exclusive API for Plugins +// +//============================================================================= + +// void (ScriptDrawingSurface *sds, int xx, int yy, int font, const char* texx, ...) +void ScPl_DrawingSurface_DrawString(ScriptDrawingSurface *sds, int xx, int yy, int font, const char* texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + DrawingSurface_DrawString(sds, xx, yy, font, scsf_buffer); +} + +void RegisterDrawingSurfaceAPI(ScriptAPIVersion base_api, ScriptAPIVersion compat_api) +{ + ccAddExternalObjectFunction("DrawingSurface::Clear^1", Sc_DrawingSurface_Clear); + ccAddExternalObjectFunction("DrawingSurface::CreateCopy^0", Sc_DrawingSurface_CreateCopy); + ccAddExternalObjectFunction("DrawingSurface::DrawCircle^3", Sc_DrawingSurface_DrawCircle); + ccAddExternalObjectFunction("DrawingSurface::DrawImage^6", Sc_DrawingSurface_DrawImage_6); + ccAddExternalObjectFunction("DrawingSurface::DrawImage^10", Sc_DrawingSurface_DrawImage); + ccAddExternalObjectFunction("DrawingSurface::DrawLine^5", Sc_DrawingSurface_DrawLine); + ccAddExternalObjectFunction("DrawingSurface::DrawMessageWrapped^5", Sc_DrawingSurface_DrawMessageWrapped); + ccAddExternalObjectFunction("DrawingSurface::DrawPixel^2", Sc_DrawingSurface_DrawPixel); + ccAddExternalObjectFunction("DrawingSurface::DrawRectangle^4", Sc_DrawingSurface_DrawRectangle); + ccAddExternalObjectFunction("DrawingSurface::DrawString^104", Sc_DrawingSurface_DrawString); + if (base_api < kScriptAPI_v350) + ccAddExternalObjectFunction("DrawingSurface::DrawStringWrapped^6", Sc_DrawingSurface_DrawStringWrapped_Old); + else + ccAddExternalObjectFunction("DrawingSurface::DrawStringWrapped^6", Sc_DrawingSurface_DrawStringWrapped); + ccAddExternalObjectFunction("DrawingSurface::DrawSurface^2", Sc_DrawingSurface_DrawSurface_2); + ccAddExternalObjectFunction("DrawingSurface::DrawSurface^10", Sc_DrawingSurface_DrawSurface); + ccAddExternalObjectFunction("DrawingSurface::DrawTriangle^6", Sc_DrawingSurface_DrawTriangle); + ccAddExternalObjectFunction("DrawingSurface::GetPixel^2", Sc_DrawingSurface_GetPixel); + ccAddExternalObjectFunction("DrawingSurface::Release^0", Sc_DrawingSurface_Release); + ccAddExternalObjectFunction("DrawingSurface::get_DrawingColor", Sc_DrawingSurface_GetDrawingColor); + ccAddExternalObjectFunction("DrawingSurface::set_DrawingColor", Sc_DrawingSurface_SetDrawingColor); + ccAddExternalObjectFunction("DrawingSurface::get_Height", Sc_DrawingSurface_GetHeight); + ccAddExternalObjectFunction("DrawingSurface::get_UseHighResCoordinates", Sc_DrawingSurface_GetUseHighResCoordinates); + ccAddExternalObjectFunction("DrawingSurface::set_UseHighResCoordinates", Sc_DrawingSurface_SetUseHighResCoordinates); + ccAddExternalObjectFunction("DrawingSurface::get_Width", Sc_DrawingSurface_GetWidth); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("DrawingSurface::Clear^1", (void*)DrawingSurface_Clear); + ccAddExternalFunctionForPlugin("DrawingSurface::CreateCopy^0", (void*)DrawingSurface_CreateCopy); + ccAddExternalFunctionForPlugin("DrawingSurface::DrawCircle^3", (void*)DrawingSurface_DrawCircle); + ccAddExternalFunctionForPlugin("DrawingSurface::DrawImage^6", (void*)DrawingSurface_DrawImage); + ccAddExternalFunctionForPlugin("DrawingSurface::DrawLine^5", (void*)DrawingSurface_DrawLine); + ccAddExternalFunctionForPlugin("DrawingSurface::DrawMessageWrapped^5", (void*)DrawingSurface_DrawMessageWrapped); + ccAddExternalFunctionForPlugin("DrawingSurface::DrawPixel^2", (void*)DrawingSurface_DrawPixel); + ccAddExternalFunctionForPlugin("DrawingSurface::DrawRectangle^4", (void*)DrawingSurface_DrawRectangle); + ccAddExternalFunctionForPlugin("DrawingSurface::DrawString^104", (void*)ScPl_DrawingSurface_DrawString); + if (base_api < kScriptAPI_v350) + ccAddExternalFunctionForPlugin("DrawingSurface::DrawStringWrapped^6", (void*)DrawingSurface_DrawStringWrapped_Old); + else + ccAddExternalFunctionForPlugin("DrawingSurface::DrawStringWrapped^6", (void*)DrawingSurface_DrawStringWrapped); + ccAddExternalFunctionForPlugin("DrawingSurface::DrawSurface^2", (void*)DrawingSurface_DrawSurface); + ccAddExternalFunctionForPlugin("DrawingSurface::DrawTriangle^6", (void*)DrawingSurface_DrawTriangle); + ccAddExternalFunctionForPlugin("DrawingSurface::GetPixel^2", (void*)DrawingSurface_GetPixel); + ccAddExternalFunctionForPlugin("DrawingSurface::Release^0", (void*)DrawingSurface_Release); + ccAddExternalFunctionForPlugin("DrawingSurface::get_DrawingColor", (void*)DrawingSurface_GetDrawingColor); + ccAddExternalFunctionForPlugin("DrawingSurface::set_DrawingColor", (void*)DrawingSurface_SetDrawingColor); + ccAddExternalFunctionForPlugin("DrawingSurface::get_Height", (void*)DrawingSurface_GetHeight); + ccAddExternalFunctionForPlugin("DrawingSurface::get_UseHighResCoordinates", (void*)DrawingSurface_GetUseHighResCoordinates); + ccAddExternalFunctionForPlugin("DrawingSurface::set_UseHighResCoordinates", (void*)DrawingSurface_SetUseHighResCoordinates); + ccAddExternalFunctionForPlugin("DrawingSurface::get_Width", (void*)DrawingSurface_GetWidth); +} diff --git a/engines/ags/engine/ac/drawingsurface.h b/engines/ags/engine/ac/drawingsurface.h new file mode 100644 index 00000000000..5bd6b7aac71 --- /dev/null +++ b/engines/ags/engine/ac/drawingsurface.h @@ -0,0 +1,45 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__DRAWINGSURFACE_H +#define __AGS_EE_AC__DRAWINGSURFACE_H + +#include "ac/dynobj/scriptdrawingsurface.h" + +void DrawingSurface_Release(ScriptDrawingSurface* sds); +// convert actual co-ordinate back to what the script is expecting +ScriptDrawingSurface* DrawingSurface_CreateCopy(ScriptDrawingSurface *sds); +void DrawingSurface_DrawSurface(ScriptDrawingSurface* target, ScriptDrawingSurface* source, int translev); +void DrawingSurface_DrawImage_FullSrc(ScriptDrawingSurface* sds, int xx, int yy, int slot, int trans, int width, int height); +void DrawingSurface_SetDrawingColor(ScriptDrawingSurface *sds, int newColour); +int DrawingSurface_GetDrawingColor(ScriptDrawingSurface *sds); +void DrawingSurface_SetUseHighResCoordinates(ScriptDrawingSurface *sds, int highRes); +int DrawingSurface_GetUseHighResCoordinates(ScriptDrawingSurface *sds); +int DrawingSurface_GetHeight(ScriptDrawingSurface *sds); +int DrawingSurface_GetWidth(ScriptDrawingSurface *sds); +void DrawingSurface_Clear(ScriptDrawingSurface *sds, int colour); +void DrawingSurface_DrawCircle(ScriptDrawingSurface *sds, int x, int y, int radius); +void DrawingSurface_DrawRectangle(ScriptDrawingSurface *sds, int x1, int y1, int x2, int y2); +void DrawingSurface_DrawTriangle(ScriptDrawingSurface *sds, int x1, int y1, int x2, int y2, int x3, int y3); +void DrawingSurface_DrawString(ScriptDrawingSurface *sds, int xx, int yy, int font, const char* text); +void DrawingSurface_DrawStringWrapped(ScriptDrawingSurface *sds, int xx, int yy, int wid, int font, int alignment, const char *msg); +void DrawingSurface_DrawMessageWrapped(ScriptDrawingSurface *sds, int xx, int yy, int wid, int font, int msgm); +void DrawingSurface_DrawLine(ScriptDrawingSurface *sds, int fromx, int fromy, int tox, int toy, int thickness); +void DrawingSurface_DrawPixel(ScriptDrawingSurface *sds, int x, int y); +int DrawingSurface_GetPixel(ScriptDrawingSurface *sds, int x, int y); + +#endif // __AGS_EE_AC__DRAWINGSURFACE_H diff --git a/engines/ags/engine/ac/dynamicsprite.cpp b/engines/ags/engine/ac/dynamicsprite.cpp new file mode 100644 index 00000000000..480a233e1ae --- /dev/null +++ b/engines/ags/engine/ac/dynamicsprite.cpp @@ -0,0 +1,700 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/dynamicsprite.h" +#include "ac/common.h" +#include "ac/charactercache.h" +#include "ac/draw.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_dynamicsprite.h" +#include "ac/global_game.h" +#include "ac/math.h" // M_PI +#include "ac/objectcache.h" +#include "ac/path_helper.h" +#include "ac/roomobject.h" +#include "ac/roomstatus.h" +#include "ac/system.h" +#include "debug/debug_log.h" +#include "game/roomstruct.h" +#include "gui/guibutton.h" +#include "ac/spritecache.h" +#include "gfx/graphicsdriver.h" +#include "script/runtimescriptvalue.h" + +using namespace Common; +using namespace Engine; + +extern GameSetupStruct game; +extern SpriteCache spriteset; +extern RoomStruct thisroom; +extern RoomObject*objs; +extern RoomStatus*croom; +extern CharacterCache *charcache; +extern ObjectCache objcache[MAX_ROOM_OBJECTS]; + +extern color palette[256]; +extern AGS::Engine::IGraphicsDriver *gfxDriver; + +char check_dynamic_sprites_at_exit = 1; + +// ** SCRIPT DYNAMIC SPRITE + +void DynamicSprite_Delete(ScriptDynamicSprite *sds) { + if (sds->slot) { + free_dynamic_sprite(sds->slot); + sds->slot = 0; + } +} + +ScriptDrawingSurface* DynamicSprite_GetDrawingSurface(ScriptDynamicSprite *dss) +{ + ScriptDrawingSurface *surface = new ScriptDrawingSurface(); + surface->dynamicSpriteNumber = dss->slot; + + if ((game.SpriteInfos[dss->slot].Flags & SPF_ALPHACHANNEL) != 0) + surface->hasAlphaChannel = true; + + ccRegisterManagedObject(surface, surface); + return surface; +} + +int DynamicSprite_GetGraphic(ScriptDynamicSprite *sds) { + if (sds->slot == 0) + quit("!DynamicSprite.Graphic: Cannot get graphic, sprite has been deleted"); + return sds->slot; +} + +int DynamicSprite_GetWidth(ScriptDynamicSprite *sds) { + return game_to_data_coord(game.SpriteInfos[sds->slot].Width); +} + +int DynamicSprite_GetHeight(ScriptDynamicSprite *sds) { + return game_to_data_coord(game.SpriteInfos[sds->slot].Height); +} + +int DynamicSprite_GetColorDepth(ScriptDynamicSprite *sds) { + int depth = spriteset[sds->slot]->GetColorDepth(); + if (depth == 15) + depth = 16; + if (depth == 24) + depth = 32; + return depth; +} + +void DynamicSprite_Resize(ScriptDynamicSprite *sds, int width, int height) { + if ((width < 1) || (height < 1)) + quit("!DynamicSprite.Resize: width and height must be greater than zero"); + if (sds->slot == 0) + quit("!DynamicSprite.Resize: sprite has been deleted"); + + data_to_game_coords(&width, &height); + + if (width * height >= 25000000) + quitprintf("!DynamicSprite.Resize: new size is too large: %d x %d", width, height); + + // resize the sprite to the requested size + Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, spriteset[sds->slot]->GetColorDepth()); + newPic->StretchBlt(spriteset[sds->slot], + RectWH(0, 0, game.SpriteInfos[sds->slot].Width, game.SpriteInfos[sds->slot].Height), + RectWH(0, 0, width, height)); + + delete spriteset[sds->slot]; + + // replace the bitmap in the sprite set + add_dynamic_sprite(sds->slot, newPic, (game.SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0); +} + +void DynamicSprite_Flip(ScriptDynamicSprite *sds, int direction) { + if ((direction < 1) || (direction > 3)) + quit("!DynamicSprite.Flip: invalid direction"); + if (sds->slot == 0) + quit("!DynamicSprite.Flip: sprite has been deleted"); + + // resize the sprite to the requested size + Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(game.SpriteInfos[sds->slot].Width, game.SpriteInfos[sds->slot].Height, spriteset[sds->slot]->GetColorDepth()); + + if (direction == 1) + newPic->FlipBlt(spriteset[sds->slot], 0, 0, Common::kBitmap_HFlip); + else if (direction == 2) + newPic->FlipBlt(spriteset[sds->slot], 0, 0, Common::kBitmap_VFlip); + else if (direction == 3) + newPic->FlipBlt(spriteset[sds->slot], 0, 0, Common::kBitmap_HVFlip); + + delete spriteset[sds->slot]; + + // replace the bitmap in the sprite set + add_dynamic_sprite(sds->slot, newPic, (game.SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0); +} + +void DynamicSprite_CopyTransparencyMask(ScriptDynamicSprite *sds, int sourceSprite) { + if (sds->slot == 0) + quit("!DynamicSprite.CopyTransparencyMask: sprite has been deleted"); + + if ((game.SpriteInfos[sds->slot].Width != game.SpriteInfos[sourceSprite].Width) || + (game.SpriteInfos[sds->slot].Height != game.SpriteInfos[sourceSprite].Height)) + { + quit("!DynamicSprite.CopyTransparencyMask: sprites are not the same size"); + } + + Bitmap *target = spriteset[sds->slot]; + Bitmap *source = spriteset[sourceSprite]; + + if (target->GetColorDepth() != source->GetColorDepth()) + { + quit("!DynamicSprite.CopyTransparencyMask: sprites are not the same colour depth"); + } + + // set the target's alpha channel depending on the source + bool dst_has_alpha = (game.SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0; + bool src_has_alpha = (game.SpriteInfos[sourceSprite].Flags & SPF_ALPHACHANNEL) != 0; + game.SpriteInfos[sds->slot].Flags &= ~SPF_ALPHACHANNEL; + if (src_has_alpha) + { + game.SpriteInfos[sds->slot].Flags |= SPF_ALPHACHANNEL; + } + + BitmapHelper::CopyTransparency(target, source, dst_has_alpha, src_has_alpha); +} + +void DynamicSprite_ChangeCanvasSize(ScriptDynamicSprite *sds, int width, int height, int x, int y) +{ + if (sds->slot == 0) + quit("!DynamicSprite.ChangeCanvasSize: sprite has been deleted"); + if ((width < 1) || (height < 1)) + quit("!DynamicSprite.ChangeCanvasSize: new size is too small"); + + data_to_game_coords(&x, &y); + data_to_game_coords(&width, &height); + + Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, spriteset[sds->slot]->GetColorDepth()); + // blit it into the enlarged image + newPic->Blit(spriteset[sds->slot], 0, 0, x, y, game.SpriteInfos[sds->slot].Width, game.SpriteInfos[sds->slot].Height); + + delete spriteset[sds->slot]; + + // replace the bitmap in the sprite set + add_dynamic_sprite(sds->slot, newPic, (game.SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0); +} + +void DynamicSprite_Crop(ScriptDynamicSprite *sds, int x1, int y1, int width, int height) { + if ((width < 1) || (height < 1)) + quit("!DynamicSprite.Crop: co-ordinates do not make sense"); + if (sds->slot == 0) + quit("!DynamicSprite.Crop: sprite has been deleted"); + + data_to_game_coords(&x1, &y1); + data_to_game_coords(&width, &height); + + if ((width > game.SpriteInfos[sds->slot].Width) || (height > game.SpriteInfos[sds->slot].Height)) + quit("!DynamicSprite.Crop: requested to crop an area larger than the source"); + + Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, spriteset[sds->slot]->GetColorDepth()); + // blit it cropped + newPic->Blit(spriteset[sds->slot], x1, y1, 0, 0, newPic->GetWidth(), newPic->GetHeight()); + + delete spriteset[sds->slot]; + + // replace the bitmap in the sprite set + add_dynamic_sprite(sds->slot, newPic, (game.SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0); +} + +void DynamicSprite_Rotate(ScriptDynamicSprite *sds, int angle, int width, int height) { + if ((angle < 1) || (angle > 359)) + quit("!DynamicSprite.Rotate: invalid angle (must be 1-359)"); + if (sds->slot == 0) + quit("!DynamicSprite.Rotate: sprite has been deleted"); + + if ((width == SCR_NO_VALUE) || (height == SCR_NO_VALUE)) { + // calculate the new image size automatically + // 1 degree = 181 degrees in terms of x/y size, so % 180 + int useAngle = angle % 180; + // and 0..90 is the same as 180..90 + if (useAngle > 90) + useAngle = 180 - useAngle; + // useAngle is now between 0 and 90 (otherwise the sin/cos stuff doesn't work) + double angleInRadians = (double)useAngle * (M_PI / 180.0); + double sinVal = sin(angleInRadians); + double cosVal = cos(angleInRadians); + + width = (cosVal * (double)game.SpriteInfos[sds->slot].Width + sinVal * (double)game.SpriteInfos[sds->slot].Height); + height = (sinVal * (double)game.SpriteInfos[sds->slot].Width + cosVal * (double)game.SpriteInfos[sds->slot].Height); + } + else { + data_to_game_coords(&width, &height); + } + + // convert to allegro angle + angle = (angle * 256) / 360; + + // resize the sprite to the requested size + Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, spriteset[sds->slot]->GetColorDepth()); + + // rotate the sprite about its centre + // (+ width%2 fixes one pixel offset problem) + newPic->RotateBlt(spriteset[sds->slot], width / 2 + width % 2, height / 2, + game.SpriteInfos[sds->slot].Width / 2, game.SpriteInfos[sds->slot].Height / 2, itofix(angle)); + + delete spriteset[sds->slot]; + + // replace the bitmap in the sprite set + add_dynamic_sprite(sds->slot, newPic, (game.SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0); +} + +void DynamicSprite_Tint(ScriptDynamicSprite *sds, int red, int green, int blue, int saturation, int luminance) +{ + Bitmap *source = spriteset[sds->slot]; + Bitmap *newPic = BitmapHelper::CreateBitmap(source->GetWidth(), source->GetHeight(), source->GetColorDepth()); + + tint_image(newPic, source, red, green, blue, saturation, (luminance * 25) / 10); + + delete source; + // replace the bitmap in the sprite set + add_dynamic_sprite(sds->slot, newPic, (game.SpriteInfos[sds->slot].Flags & SPF_ALPHACHANNEL) != 0); +} + +int DynamicSprite_SaveToFile(ScriptDynamicSprite *sds, const char* namm) +{ + if (sds->slot == 0) + quit("!DynamicSprite.SaveToFile: sprite has been deleted"); + + auto filename = String(namm); + if (filename.FindChar('.') == -1) + filename.Append(".bmp"); + + ResolvedPath rp; + if (!ResolveWritePathAndCreateDirs(filename, rp)) + return 0; + return spriteset[sds->slot]->SaveToFile(rp.FullPath, palette) ? 1 : 0; +} + +ScriptDynamicSprite* DynamicSprite_CreateFromSaveGame(int sgslot, int width, int height) { + int slotnum = LoadSaveSlotScreenshot(sgslot, width, height); + if (slotnum) { + ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(slotnum); + return new_spr; + } + return nullptr; +} + +ScriptDynamicSprite* DynamicSprite_CreateFromFile(const char *filename) { + int slotnum = LoadImageFile(filename); + if (slotnum) { + ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(slotnum); + return new_spr; + } + return nullptr; +} + +ScriptDynamicSprite* DynamicSprite_CreateFromScreenShot(int width, int height) { + + // TODO: refactor and merge with create_savegame_screenshot() + + int gotSlot = spriteset.GetFreeIndex(); + if (gotSlot <= 0) + return nullptr; + + const Rect &viewport = play.GetMainViewport(); + if (width <= 0) + width = viewport.GetWidth(); + else + width = data_to_game_coord(width); + + if (height <= 0) + height = viewport.GetHeight(); + else + height = data_to_game_coord(height); + + Bitmap *newPic = CopyScreenIntoBitmap(width, height); + + update_polled_stuff_if_runtime(); + + // replace the bitmap in the sprite set + add_dynamic_sprite(gotSlot, ReplaceBitmapWithSupportedFormat(newPic)); + ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot); + return new_spr; +} + +ScriptDynamicSprite* DynamicSprite_CreateFromExistingSprite(int slot, int preserveAlphaChannel) { + + int gotSlot = spriteset.GetFreeIndex(); + if (gotSlot <= 0) + return nullptr; + + if (!spriteset.DoesSpriteExist(slot)) + quitprintf("DynamicSprite.CreateFromExistingSprite: sprite %d does not exist", slot); + + // create a new sprite as a copy of the existing one + Bitmap *newPic = BitmapHelper::CreateBitmapCopy(spriteset[slot]); + if (newPic == nullptr) + return nullptr; + + bool hasAlpha = (preserveAlphaChannel) && ((game.SpriteInfos[slot].Flags & SPF_ALPHACHANNEL) != 0); + + // replace the bitmap in the sprite set + add_dynamic_sprite(gotSlot, newPic, hasAlpha); + ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot); + return new_spr; +} + +ScriptDynamicSprite* DynamicSprite_CreateFromDrawingSurface(ScriptDrawingSurface *sds, int x, int y, int width, int height) +{ + int gotSlot = spriteset.GetFreeIndex(); + if (gotSlot <= 0) + return nullptr; + + // use DrawingSurface resolution + sds->PointToGameResolution(&x, &y); + sds->SizeToGameResolution(&width, &height); + + Bitmap *ds = sds->StartDrawing(); + + if ((x < 0) || (y < 0) || (x + width > ds->GetWidth()) || (y + height > ds->GetHeight())) + quit("!DynamicSprite.CreateFromDrawingSurface: requested area is outside the surface"); + + int colDepth = ds->GetColorDepth(); + + Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, colDepth); + if (newPic == nullptr) + return nullptr; + + newPic->Blit(ds, x, y, 0, 0, width, height); + + sds->FinishedDrawingReadOnly(); + + add_dynamic_sprite(gotSlot, newPic, (sds->hasAlphaChannel != 0)); + ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot); + return new_spr; +} + +ScriptDynamicSprite* DynamicSprite_Create(int width, int height, int alphaChannel) +{ + data_to_game_coords(&width, &height); + + int gotSlot = spriteset.GetFreeIndex(); + if (gotSlot <= 0) + return nullptr; + + Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, game.GetColorDepth()); + if (newPic == nullptr) + return nullptr; + + if ((alphaChannel) && (game.GetColorDepth() < 32)) + alphaChannel = false; + + add_dynamic_sprite(gotSlot, ReplaceBitmapWithSupportedFormat(newPic), alphaChannel != 0); + ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot); + return new_spr; +} + +ScriptDynamicSprite* DynamicSprite_CreateFromExistingSprite_Old(int slot) +{ + return DynamicSprite_CreateFromExistingSprite(slot, 0); +} + +ScriptDynamicSprite* DynamicSprite_CreateFromBackground(int frame, int x1, int y1, int width, int height) { + + if (frame == SCR_NO_VALUE) { + frame = play.bg_frame; + } + else if ((frame < 0) || ((size_t)frame >= thisroom.BgFrameCount)) + quit("!DynamicSprite.CreateFromBackground: invalid frame specified"); + + if (x1 == SCR_NO_VALUE) { + x1 = 0; + y1 = 0; + width = play.room_width; + height = play.room_height; + } + else if ((x1 < 0) || (y1 < 0) || (width < 1) || (height < 1) || + (x1 + width > play.room_width) || (y1 + height > play.room_height)) + quit("!DynamicSprite.CreateFromBackground: invalid co-ordinates specified"); + + data_to_game_coords(&x1, &y1); + data_to_game_coords(&width, &height); + + int gotSlot = spriteset.GetFreeIndex(); + if (gotSlot <= 0) + return nullptr; + + // create a new sprite as a copy of the existing one + Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, thisroom.BgFrames[frame].Graphic->GetColorDepth()); + if (newPic == nullptr) + return nullptr; + + newPic->Blit(thisroom.BgFrames[frame].Graphic.get(), x1, y1, 0, 0, width, height); + + // replace the bitmap in the sprite set + add_dynamic_sprite(gotSlot, newPic); + ScriptDynamicSprite *new_spr = new ScriptDynamicSprite(gotSlot); + return new_spr; +} + +//============================================================================= + +void add_dynamic_sprite(int gotSlot, Bitmap *redin, bool hasAlpha) { + + spriteset.SetSprite(gotSlot, redin); + + game.SpriteInfos[gotSlot].Flags = SPF_DYNAMICALLOC; + + if (redin->GetColorDepth() > 8) + game.SpriteInfos[gotSlot].Flags |= SPF_HICOLOR; + if (redin->GetColorDepth() > 16) + game.SpriteInfos[gotSlot].Flags |= SPF_TRUECOLOR; + if (hasAlpha) + game.SpriteInfos[gotSlot].Flags |= SPF_ALPHACHANNEL; + + game.SpriteInfos[gotSlot].Width = redin->GetWidth(); + game.SpriteInfos[gotSlot].Height = redin->GetHeight(); +} + +void free_dynamic_sprite (int gotSlot) { + int tt; + + if ((gotSlot < 0) || (gotSlot >= spriteset.GetSpriteSlotCount())) + quit("!FreeDynamicSprite: invalid slot number"); + + if ((game.SpriteInfos[gotSlot].Flags & SPF_DYNAMICALLOC) == 0) + quitprintf("!DeleteSprite: Attempted to free static sprite %d that was not loaded by the script", gotSlot); + + spriteset.RemoveSprite(gotSlot, true); + + game.SpriteInfos[gotSlot].Flags = 0; + game.SpriteInfos[gotSlot].Width = 0; + game.SpriteInfos[gotSlot].Height = 0; + + // ensure it isn't still on any GUI buttons + for (tt = 0; tt < numguibuts; tt++) { + if (guibuts[tt].IsDeleted()) + continue; + if (guibuts[tt].Image == gotSlot) + guibuts[tt].Image = 0; + if (guibuts[tt].CurrentImage == gotSlot) + guibuts[tt].CurrentImage = 0; + if (guibuts[tt].MouseOverImage == gotSlot) + guibuts[tt].MouseOverImage = 0; + if (guibuts[tt].PushedImage == gotSlot) + guibuts[tt].PushedImage = 0; + } + + // force refresh of any object caches using the sprite + if (croom != nullptr) + { + for (tt = 0; tt < croom->numobj; tt++) + { + if (objs[tt].num == gotSlot) + { + objs[tt].num = 0; + objcache[tt].sppic = -1; + } + else if (objcache[tt].sppic == gotSlot) + objcache[tt].sppic = -1; + } + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// void (ScriptDynamicSprite *sds, int width, int height, int x, int y) +RuntimeScriptValue Sc_DynamicSprite_ChangeCanvasSize(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(ScriptDynamicSprite, DynamicSprite_ChangeCanvasSize); +} + +// void (ScriptDynamicSprite *sds, int sourceSprite) +RuntimeScriptValue Sc_DynamicSprite_CopyTransparencyMask(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDynamicSprite, DynamicSprite_CopyTransparencyMask); +} + +// void (ScriptDynamicSprite *sds, int x1, int y1, int width, int height) +RuntimeScriptValue Sc_DynamicSprite_Crop(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(ScriptDynamicSprite, DynamicSprite_Crop); +} + +// void (ScriptDynamicSprite *sds) +RuntimeScriptValue Sc_DynamicSprite_Delete(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptDynamicSprite, DynamicSprite_Delete); +} + +// void (ScriptDynamicSprite *sds, int direction) +RuntimeScriptValue Sc_DynamicSprite_Flip(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptDynamicSprite, DynamicSprite_Flip); +} + +// ScriptDrawingSurface* (ScriptDynamicSprite *dss) +RuntimeScriptValue Sc_DynamicSprite_GetDrawingSurface(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJAUTO(ScriptDynamicSprite, ScriptDrawingSurface, DynamicSprite_GetDrawingSurface); +} + +// void (ScriptDynamicSprite *sds, int width, int height) +RuntimeScriptValue Sc_DynamicSprite_Resize(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(ScriptDynamicSprite, DynamicSprite_Resize); +} + +// void (ScriptDynamicSprite *sds, int angle, int width, int height) +RuntimeScriptValue Sc_DynamicSprite_Rotate(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(ScriptDynamicSprite, DynamicSprite_Rotate); +} + +// int (ScriptDynamicSprite *sds, const char* namm) +RuntimeScriptValue Sc_DynamicSprite_SaveToFile(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(ScriptDynamicSprite, DynamicSprite_SaveToFile, const char); +} + +// void (ScriptDynamicSprite *sds, int red, int green, int blue, int saturation, int luminance) +RuntimeScriptValue Sc_DynamicSprite_Tint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5(ScriptDynamicSprite, DynamicSprite_Tint); +} + +// int (ScriptDynamicSprite *sds) +RuntimeScriptValue Sc_DynamicSprite_GetColorDepth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDynamicSprite, DynamicSprite_GetColorDepth); +} + +// int (ScriptDynamicSprite *sds) +RuntimeScriptValue Sc_DynamicSprite_GetGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDynamicSprite, DynamicSprite_GetGraphic); +} + +// int (ScriptDynamicSprite *sds) +RuntimeScriptValue Sc_DynamicSprite_GetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDynamicSprite, DynamicSprite_GetHeight); +} + +// int (ScriptDynamicSprite *sds) +RuntimeScriptValue Sc_DynamicSprite_GetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDynamicSprite, DynamicSprite_GetWidth); +} + +// ScriptDynamicSprite* (int width, int height, int alphaChannel) +RuntimeScriptValue Sc_DynamicSprite_Create(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT3(ScriptDynamicSprite, DynamicSprite_Create); +} + +// ScriptDynamicSprite* (int frame, int x1, int y1, int width, int height) +RuntimeScriptValue Sc_DynamicSprite_CreateFromBackground(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT5(ScriptDynamicSprite, DynamicSprite_CreateFromBackground); +} + +// ScriptDynamicSprite* (ScriptDrawingSurface *sds, int x, int y, int width, int height) +RuntimeScriptValue Sc_DynamicSprite_CreateFromDrawingSurface(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_POBJ_PINT4(ScriptDynamicSprite, DynamicSprite_CreateFromDrawingSurface, ScriptDrawingSurface); +} + +// ScriptDynamicSprite* (int slot) +RuntimeScriptValue Sc_DynamicSprite_CreateFromExistingSprite_Old(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT(ScriptDynamicSprite, DynamicSprite_CreateFromExistingSprite_Old); +} + +// ScriptDynamicSprite* (int slot, int preserveAlphaChannel) +RuntimeScriptValue Sc_DynamicSprite_CreateFromExistingSprite(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT2(ScriptDynamicSprite, DynamicSprite_CreateFromExistingSprite); +} + +// ScriptDynamicSprite* (const char *filename) +RuntimeScriptValue Sc_DynamicSprite_CreateFromFile(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_POBJ(ScriptDynamicSprite, DynamicSprite_CreateFromFile, const char); +} + +// ScriptDynamicSprite* (int sgslot, int width, int height) +RuntimeScriptValue Sc_DynamicSprite_CreateFromSaveGame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT3(ScriptDynamicSprite, DynamicSprite_CreateFromSaveGame); +} + +// ScriptDynamicSprite* (int width, int height) +RuntimeScriptValue Sc_DynamicSprite_CreateFromScreenShot(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT2(ScriptDynamicSprite, DynamicSprite_CreateFromScreenShot); +} + + +void RegisterDynamicSpriteAPI() +{ + ccAddExternalObjectFunction("DynamicSprite::ChangeCanvasSize^4", Sc_DynamicSprite_ChangeCanvasSize); + ccAddExternalObjectFunction("DynamicSprite::CopyTransparencyMask^1", Sc_DynamicSprite_CopyTransparencyMask); + ccAddExternalObjectFunction("DynamicSprite::Crop^4", Sc_DynamicSprite_Crop); + ccAddExternalObjectFunction("DynamicSprite::Delete", Sc_DynamicSprite_Delete); + ccAddExternalObjectFunction("DynamicSprite::Flip^1", Sc_DynamicSprite_Flip); + ccAddExternalObjectFunction("DynamicSprite::GetDrawingSurface^0", Sc_DynamicSprite_GetDrawingSurface); + ccAddExternalObjectFunction("DynamicSprite::Resize^2", Sc_DynamicSprite_Resize); + ccAddExternalObjectFunction("DynamicSprite::Rotate^3", Sc_DynamicSprite_Rotate); + ccAddExternalObjectFunction("DynamicSprite::SaveToFile^1", Sc_DynamicSprite_SaveToFile); + ccAddExternalObjectFunction("DynamicSprite::Tint^5", Sc_DynamicSprite_Tint); + ccAddExternalObjectFunction("DynamicSprite::get_ColorDepth", Sc_DynamicSprite_GetColorDepth); + ccAddExternalObjectFunction("DynamicSprite::get_Graphic", Sc_DynamicSprite_GetGraphic); + ccAddExternalObjectFunction("DynamicSprite::get_Height", Sc_DynamicSprite_GetHeight); + ccAddExternalObjectFunction("DynamicSprite::get_Width", Sc_DynamicSprite_GetWidth); + ccAddExternalStaticFunction("DynamicSprite::Create^3", Sc_DynamicSprite_Create); + ccAddExternalStaticFunction("DynamicSprite::CreateFromBackground", Sc_DynamicSprite_CreateFromBackground); + ccAddExternalStaticFunction("DynamicSprite::CreateFromDrawingSurface^5", Sc_DynamicSprite_CreateFromDrawingSurface); + ccAddExternalStaticFunction("DynamicSprite::CreateFromExistingSprite^1", Sc_DynamicSprite_CreateFromExistingSprite_Old); + ccAddExternalStaticFunction("DynamicSprite::CreateFromExistingSprite^2", Sc_DynamicSprite_CreateFromExistingSprite); + ccAddExternalStaticFunction("DynamicSprite::CreateFromFile", Sc_DynamicSprite_CreateFromFile); + ccAddExternalStaticFunction("DynamicSprite::CreateFromSaveGame", Sc_DynamicSprite_CreateFromSaveGame); + ccAddExternalStaticFunction("DynamicSprite::CreateFromScreenShot", Sc_DynamicSprite_CreateFromScreenShot); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("DynamicSprite::ChangeCanvasSize^4", (void*)DynamicSprite_ChangeCanvasSize); + ccAddExternalFunctionForPlugin("DynamicSprite::CopyTransparencyMask^1", (void*)DynamicSprite_CopyTransparencyMask); + ccAddExternalFunctionForPlugin("DynamicSprite::Crop^4", (void*)DynamicSprite_Crop); + ccAddExternalFunctionForPlugin("DynamicSprite::Delete", (void*)DynamicSprite_Delete); + ccAddExternalFunctionForPlugin("DynamicSprite::Flip^1", (void*)DynamicSprite_Flip); + ccAddExternalFunctionForPlugin("DynamicSprite::GetDrawingSurface^0", (void*)DynamicSprite_GetDrawingSurface); + ccAddExternalFunctionForPlugin("DynamicSprite::Resize^2", (void*)DynamicSprite_Resize); + ccAddExternalFunctionForPlugin("DynamicSprite::Rotate^3", (void*)DynamicSprite_Rotate); + ccAddExternalFunctionForPlugin("DynamicSprite::SaveToFile^1", (void*)DynamicSprite_SaveToFile); + ccAddExternalFunctionForPlugin("DynamicSprite::Tint^5", (void*)DynamicSprite_Tint); + ccAddExternalFunctionForPlugin("DynamicSprite::get_ColorDepth", (void*)DynamicSprite_GetColorDepth); + ccAddExternalFunctionForPlugin("DynamicSprite::get_Graphic", (void*)DynamicSprite_GetGraphic); + ccAddExternalFunctionForPlugin("DynamicSprite::get_Height", (void*)DynamicSprite_GetHeight); + ccAddExternalFunctionForPlugin("DynamicSprite::get_Width", (void*)DynamicSprite_GetWidth); + ccAddExternalFunctionForPlugin("DynamicSprite::Create^3", (void*)DynamicSprite_Create); + ccAddExternalFunctionForPlugin("DynamicSprite::CreateFromBackground", (void*)DynamicSprite_CreateFromBackground); + ccAddExternalFunctionForPlugin("DynamicSprite::CreateFromDrawingSurface^5", (void*)DynamicSprite_CreateFromDrawingSurface); + ccAddExternalFunctionForPlugin("DynamicSprite::CreateFromExistingSprite^1", (void*)DynamicSprite_CreateFromExistingSprite_Old); + ccAddExternalFunctionForPlugin("DynamicSprite::CreateFromExistingSprite^2", (void*)DynamicSprite_CreateFromExistingSprite); + ccAddExternalFunctionForPlugin("DynamicSprite::CreateFromFile", (void*)DynamicSprite_CreateFromFile); + ccAddExternalFunctionForPlugin("DynamicSprite::CreateFromSaveGame", (void*)DynamicSprite_CreateFromSaveGame); + ccAddExternalFunctionForPlugin("DynamicSprite::CreateFromScreenShot", (void*)DynamicSprite_CreateFromScreenShot); +} diff --git a/engines/ags/engine/ac/dynamicsprite.h b/engines/ags/engine/ac/dynamicsprite.h new file mode 100644 index 00000000000..6602c854028 --- /dev/null +++ b/engines/ags/engine/ac/dynamicsprite.h @@ -0,0 +1,51 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__DYNAMICSPRITE_H +#define __AGS_EE_AC__DYNAMICSPRITE_H + +#include "ac/dynobj/scriptdynamicsprite.h" +#include "ac/dynobj/scriptdrawingsurface.h" + +void DynamicSprite_Delete(ScriptDynamicSprite *sds); +ScriptDrawingSurface* DynamicSprite_GetDrawingSurface(ScriptDynamicSprite *dss); +int DynamicSprite_GetGraphic(ScriptDynamicSprite *sds); +int DynamicSprite_GetWidth(ScriptDynamicSprite *sds); +int DynamicSprite_GetHeight(ScriptDynamicSprite *sds); +int DynamicSprite_GetColorDepth(ScriptDynamicSprite *sds); +void DynamicSprite_Resize(ScriptDynamicSprite *sds, int width, int height); +void DynamicSprite_Flip(ScriptDynamicSprite *sds, int direction); +void DynamicSprite_CopyTransparencyMask(ScriptDynamicSprite *sds, int sourceSprite); +void DynamicSprite_ChangeCanvasSize(ScriptDynamicSprite *sds, int width, int height, int x, int y); +void DynamicSprite_Crop(ScriptDynamicSprite *sds, int x1, int y1, int width, int height); +void DynamicSprite_Rotate(ScriptDynamicSprite *sds, int angle, int width, int height); +void DynamicSprite_Tint(ScriptDynamicSprite *sds, int red, int green, int blue, int saturation, int luminance); +int DynamicSprite_SaveToFile(ScriptDynamicSprite *sds, const char* namm); +ScriptDynamicSprite* DynamicSprite_CreateFromSaveGame(int sgslot, int width, int height); +ScriptDynamicSprite* DynamicSprite_CreateFromFile(const char *filename); +ScriptDynamicSprite* DynamicSprite_CreateFromScreenShot(int width, int height); +ScriptDynamicSprite* DynamicSprite_CreateFromExistingSprite(int slot, int preserveAlphaChannel); +ScriptDynamicSprite* DynamicSprite_CreateFromDrawingSurface(ScriptDrawingSurface *sds, int x, int y, int width, int height); +ScriptDynamicSprite* DynamicSprite_Create(int width, int height, int alphaChannel); +ScriptDynamicSprite* DynamicSprite_CreateFromExistingSprite_Old(int slot); +ScriptDynamicSprite* DynamicSprite_CreateFromBackground(int frame, int x1, int y1, int width, int height); + + +void add_dynamic_sprite(int gotSlot, Common::Bitmap *redin, bool hasAlpha = false); +void free_dynamic_sprite (int gotSlot); + +#endif // __AGS_EE_AC__DYNAMICSPRITE_H diff --git a/engines/ags/engine/ac/dynobj/all_dynamicclasses.h b/engines/ags/engine/ac/dynobj/all_dynamicclasses.h new file mode 100644 index 00000000000..cc6cb71c13b --- /dev/null +++ b/engines/ags/engine/ac/dynobj/all_dynamicclasses.h @@ -0,0 +1,35 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__ALLDYNAMICCLASSES_H +#define __AGS_EE_DYNOBJ__ALLDYNAMICCLASSES_H + +#include "ac/dynobj/cc_agsdynamicobject.h" +#include "ac/dynobj/cc_audiochannel.h" +#include "ac/dynobj/cc_audioclip.h" +#include "ac/dynobj/cc_character.h" +#include "ac/dynobj/cc_dialog.h" +#include "ac/dynobj/cc_gui.h" +#include "ac/dynobj/cc_guiobject.h" +#include "ac/dynobj/cc_hotspot.h" +#include "ac/dynobj/cc_inventory.h" +#include "ac/dynobj/cc_object.h" +#include "ac/dynobj/cc_region.h" + +#include "ac/dynobj/cc_serializer.h" + +#endif // __AGS_EE_DYNOBJ__ALLDYNAMICCLASSES_H diff --git a/engines/ags/engine/ac/dynobj/all_scriptclasses.h b/engines/ags/engine/ac/dynobj/all_scriptclasses.h new file mode 100644 index 00000000000..bcaf7a1d473 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/all_scriptclasses.h @@ -0,0 +1,37 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__ALLSCRIPTCLASSES_H +#define __AGS_EE_DYNOBJ__ALLSCRIPTCLASSES_H + +#include "ac/dynobj/scriptdatetime.h" +#include "ac/dynobj/scriptdialog.h" +#include "ac/dynobj/scriptdialogoptionsrendering.h" +#include "ac/dynobj/scriptdrawingsurface.h" +#include "ac/dynobj/scriptdynamicsprite.h" +#include "ac/dynobj/scriptgui.h" +#include "ac/dynobj/scripthotspot.h" +#include "ac/dynobj/scriptinvitem.h" +#include "ac/dynobj/scriptmouse.h" +#include "ac/dynobj/scriptobject.h" +#include "ac/dynobj/scriptoverlay.h" +#include "ac/dynobj/scriptregion.h" +#include "ac/dynobj/scriptstring.h" +#include "ac/dynobj/scriptsystem.h" +#include "ac/dynobj/scriptviewframe.h" + +#endif // __AGS_EE_DYNOBJ__ALLSCRIPTOBJECTS_H diff --git a/engines/ags/engine/ac/dynobj/cc_agsdynamicobject.cpp b/engines/ags/engine/ac/dynobj/cc_agsdynamicobject.cpp new file mode 100644 index 00000000000..e1d0e6e6ee6 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_agsdynamicobject.cpp @@ -0,0 +1,130 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "core/types.h" +#include "ac/dynobj/cc_agsdynamicobject.h" +#include "ac/common.h" // quit() +#include "util/bbop.h" + +using namespace AGS::Common; + +// *** The script serialization routines for built-in types + +int AGSCCDynamicObject::Dispose(const char *address, bool force) { + // cannot be removed from memory + return 0; +} + +void AGSCCDynamicObject::StartSerialize(char *sbuffer) { + bytesSoFar = 0; + serbuffer = sbuffer; +} + +void AGSCCDynamicObject::SerializeInt(int val) { + char *chptr = &serbuffer[bytesSoFar]; + int *iptr = (int*)chptr; + *iptr = BBOp::Int32FromLE(val); + bytesSoFar += 4; +} + +void AGSCCDynamicObject::SerializeFloat(float val) { + char *chptr = &serbuffer[bytesSoFar]; + float *fptr = (float*)chptr; + *fptr = BBOp::FloatFromLE(val); + bytesSoFar += 4; +} + +int AGSCCDynamicObject::EndSerialize() { + return bytesSoFar; +} + +void AGSCCDynamicObject::StartUnserialize(const char *sbuffer, int pTotalBytes) { + bytesSoFar = 0; + totalBytes = pTotalBytes; + serbuffer = (char*)sbuffer; +} + +int AGSCCDynamicObject::UnserializeInt() { + if (bytesSoFar >= totalBytes) + quit("Unserialise: internal error: read past EOF"); + + char *chptr = &serbuffer[bytesSoFar]; + bytesSoFar += 4; + return BBOp::Int32FromLE(*((int*)chptr)); +} + +float AGSCCDynamicObject::UnserializeFloat() { + if (bytesSoFar >= totalBytes) + quit("Unserialise: internal error: read past EOF"); + + char *chptr = &serbuffer[bytesSoFar]; + bytesSoFar += 4; + return BBOp::FloatFromLE(*((float*)chptr)); +} + +const char* AGSCCDynamicObject::GetFieldPtr(const char *address, intptr_t offset) +{ + return address + offset; +} + +void AGSCCDynamicObject::Read(const char *address, intptr_t offset, void *dest, int size) +{ + memcpy(dest, address + offset, size); +} + +uint8_t AGSCCDynamicObject::ReadInt8(const char *address, intptr_t offset) +{ + return *(uint8_t*)(address + offset); +} + +int16_t AGSCCDynamicObject::ReadInt16(const char *address, intptr_t offset) +{ + return *(int16_t*)(address + offset); +} + +int32_t AGSCCDynamicObject::ReadInt32(const char *address, intptr_t offset) +{ + return *(int32_t*)(address + offset); +} + +float AGSCCDynamicObject::ReadFloat(const char *address, intptr_t offset) +{ + return *(float*)(address + offset); +} + +void AGSCCDynamicObject::Write(const char *address, intptr_t offset, void *src, int size) +{ + memcpy((void*)(address + offset), src, size); +} + +void AGSCCDynamicObject::WriteInt8(const char *address, intptr_t offset, uint8_t val) +{ + *(uint8_t*)(address + offset) = val; +} + +void AGSCCDynamicObject::WriteInt16(const char *address, intptr_t offset, int16_t val) +{ + *(int16_t*)(address + offset) = val; +} + +void AGSCCDynamicObject::WriteInt32(const char *address, intptr_t offset, int32_t val) +{ + *(int32_t*)(address + offset) = val; +} + +void AGSCCDynamicObject::WriteFloat(const char *address, intptr_t offset, float val) +{ + *(float*)(address + offset) = val; +} diff --git a/engines/ags/engine/ac/dynobj/cc_agsdynamicobject.h b/engines/ags/engine/ac/dynobj/cc_agsdynamicobject.h new file mode 100644 index 00000000000..3c2d795e1b9 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_agsdynamicobject.h @@ -0,0 +1,60 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CCDYNAMICOBJECT_H +#define __AC_CCDYNAMICOBJECT_H + +#include "ac/dynobj/cc_dynamicobject.h" + +struct AGSCCDynamicObject : ICCDynamicObject { +protected: + virtual ~AGSCCDynamicObject() = default; +public: + // default implementation + int Dispose(const char *address, bool force) override; + + // TODO: pass savegame format version + virtual void Unserialize(int index, const char *serializedData, int dataSize) = 0; + + // Legacy support for reading and writing object values by their relative offset + const char* GetFieldPtr(const char *address, intptr_t offset) override; + void Read(const char *address, intptr_t offset, void *dest, int size) override; + uint8_t ReadInt8(const char *address, intptr_t offset) override; + int16_t ReadInt16(const char *address, intptr_t offset) override; + int32_t ReadInt32(const char *address, intptr_t offset) override; + float ReadFloat(const char *address, intptr_t offset) override; + void Write(const char *address, intptr_t offset, void *src, int size) override; + void WriteInt8(const char *address, intptr_t offset, uint8_t val) override; + void WriteInt16(const char *address, intptr_t offset, int16_t val) override; + void WriteInt32(const char *address, intptr_t offset, int32_t val) override; + void WriteFloat(const char *address, intptr_t offset, float val) override; + +protected: + // Savegame serialization + // TODO: reimplement with the proper memory stream?! + int bytesSoFar; + int totalBytes; + char *serbuffer; + + void StartSerialize(char *sbuffer); + void SerializeInt(int val); + void SerializeFloat(float val); + int EndSerialize(); + void StartUnserialize(const char *sbuffer, int pTotalBytes); + int UnserializeInt(); + float UnserializeFloat(); + +}; + +#endif // __AC_CCDYNAMICOBJECT_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_audiochannel.cpp b/engines/ags/engine/ac/dynobj/cc_audiochannel.cpp new file mode 100644 index 00000000000..40f96af1701 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_audiochannel.cpp @@ -0,0 +1,36 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_audiochannel.h" +#include "ac/dynobj/scriptaudiochannel.h" +#include "media/audio/audio_system.h" + +extern ScriptAudioChannel scrAudioChannel[MAX_SOUND_CHANNELS + 1]; + +const char *CCAudioChannel::GetType() { + return "AudioChannel"; +} + +int CCAudioChannel::Serialize(const char *address, char *buffer, int bufsize) { + ScriptAudioChannel *ach = (ScriptAudioChannel*)address; + StartSerialize(buffer); + SerializeInt(ach->id); + return EndSerialize(); +} + +void CCAudioChannel::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int id = UnserializeInt(); + ccRegisterUnserializedObject(index, &scrAudioChannel[id], this); +} diff --git a/engines/ags/engine/ac/dynobj/cc_audiochannel.h b/engines/ags/engine/ac/dynobj/cc_audiochannel.h new file mode 100644 index 00000000000..d13dd4cd091 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_audiochannel.h @@ -0,0 +1,29 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__CCAUDIOCHANNEL_H +#define __AGS_EE_DYNOBJ__CCAUDIOCHANNEL_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCAudioChannel final : AGSCCDynamicObject { + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; +}; + +#endif // __AGS_EE_DYNOBJ__CCAUDIOCHANNEL_H diff --git a/engines/ags/engine/ac/dynobj/cc_audioclip.cpp b/engines/ags/engine/ac/dynobj/cc_audioclip.cpp new file mode 100644 index 00000000000..31e4a0105bc --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_audioclip.cpp @@ -0,0 +1,36 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_audioclip.h" +#include "ac/dynobj/scriptaudioclip.h" +#include "ac/gamesetupstruct.h" + +extern GameSetupStruct game; + +const char *CCAudioClip::GetType() { + return "AudioClip"; +} + +int CCAudioClip::Serialize(const char *address, char *buffer, int bufsize) { + ScriptAudioClip *ach = (ScriptAudioClip*)address; + StartSerialize(buffer); + SerializeInt(ach->id); + return EndSerialize(); +} + +void CCAudioClip::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int id = UnserializeInt(); + ccRegisterUnserializedObject(index, &game.audioClips[id], this); +} diff --git a/engines/ags/engine/ac/dynobj/cc_audioclip.h b/engines/ags/engine/ac/dynobj/cc_audioclip.h new file mode 100644 index 00000000000..b8b633a63c2 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_audioclip.h @@ -0,0 +1,29 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__CCAUDIOCLIP_H +#define __AGS_EE_DYNOBJ__CCAUDIOCLIP_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCAudioClip final : AGSCCDynamicObject { + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; +}; + +#endif // __AGS_EE_DYNOBJ__CCAUDIOCLIP_H diff --git a/engines/ags/engine/ac/dynobj/cc_character.cpp b/engines/ags/engine/ac/dynobj/cc_character.cpp new file mode 100644 index 00000000000..00478f2b08b --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_character.cpp @@ -0,0 +1,58 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_character.h" +#include "ac/characterinfo.h" +#include "ac/global_character.h" +#include "ac/gamesetupstruct.h" +#include "ac/game_version.h" + +extern GameSetupStruct game; + +// return the type name of the object +const char *CCCharacter::GetType() { + return "Character"; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int CCCharacter::Serialize(const char *address, char *buffer, int bufsize) { + CharacterInfo *chaa = (CharacterInfo*)address; + StartSerialize(buffer); + SerializeInt(chaa->index_id); + return EndSerialize(); +} + +void CCCharacter::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int num = UnserializeInt(); + ccRegisterUnserializedObject(index, &game.chars[num], this); +} + +void CCCharacter::WriteInt16(const char *address, intptr_t offset, int16_t val) +{ + *(int16_t*)(address + offset) = val; + + // Detect when a game directly modifies the inventory, which causes the displayed + // and actual inventory to diverge since 2.70. Force an update of the displayed + // inventory for older games that reply on the previous behaviour. + if (loaded_game_file_version < kGameVersion_270) + { + const int invoffset = 112; + if (offset >= invoffset && offset < (invoffset + MAX_INV * sizeof(short))) + { + update_invorder(); + } + } +} diff --git a/engines/ags/engine/ac/dynobj/cc_character.h b/engines/ags/engine/ac/dynobj/cc_character.h new file mode 100644 index 00000000000..d610cb914f2 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_character.h @@ -0,0 +1,34 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CCCHARACTER_H +#define __AC_CCCHARACTER_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCCharacter final : AGSCCDynamicObject { + + // return the type name of the object + const char *GetType() override; + + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + + void Unserialize(int index, const char *serializedData, int dataSize) override; + + void WriteInt16(const char *address, intptr_t offset, int16_t val) override; +}; + +#endif // __AC_CCCHARACTER_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.cpp b/engines/ags/engine/ac/dynobj/cc_dialog.cpp new file mode 100644 index 00000000000..5c52802cae3 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_dialog.cpp @@ -0,0 +1,38 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_dialog.h" +#include "ac/dialog.h" +#include "ac/dialogtopic.h" +#include "ac/gamestructdefines.h" + +// return the type name of the object +const char *CCDialog::GetType() { + return "Dialog"; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int CCDialog::Serialize(const char *address, char *buffer, int bufsize) { + ScriptDialog *shh = (ScriptDialog*)address; + StartSerialize(buffer); + SerializeInt(shh->id); + return EndSerialize(); +} + +void CCDialog::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int num = UnserializeInt(); + ccRegisterUnserializedObject(index, &scrDialog[num], this); +} diff --git a/engines/ags/engine/ac/dynobj/cc_dialog.h b/engines/ags/engine/ac/dynobj/cc_dialog.h new file mode 100644 index 00000000000..bf1a95e122a --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_dialog.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CCDIALOG_H +#define __AC_CCDIALOG_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCDialog final : AGSCCDynamicObject { + + // return the type name of the object + const char *GetType() override; + + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + + void Unserialize(int index, const char *serializedData, int dataSize) override; + +}; + +#endif // __AC_CCDIALOG_H diff --git a/engines/ags/engine/ac/dynobj/cc_dynamicarray.cpp b/engines/ags/engine/ac/dynobj/cc_dynamicarray.cpp new file mode 100644 index 00000000000..694ef902132 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_dynamicarray.cpp @@ -0,0 +1,161 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "cc_dynamicarray.h" + +// return the type name of the object +const char *CCDynamicArray::GetType() { + return CC_DYNAMIC_ARRAY_TYPE_NAME; +} + +int CCDynamicArray::Dispose(const char *address, bool force) { + address -= 8; + + // If it's an array of managed objects, release their ref counts; + // except if this array is forcefully removed from the managed pool, + // in which case just ignore these. + if (!force) + { + int *elementCount = (int*)address; + if (elementCount[0] & ARRAY_MANAGED_TYPE_FLAG) + { + elementCount[0] &= ~ARRAY_MANAGED_TYPE_FLAG; + for (int i = 0; i < elementCount[0]; i++) + { + if (elementCount[2 + i] != 0) + { + ccReleaseObjectReference(elementCount[2 + i]); + } + } + } + } + + delete address; + return 1; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int CCDynamicArray::Serialize(const char *address, char *buffer, int bufsize) { + int *sizeInBytes = &((int*)address)[-1]; + int sizeToWrite = *sizeInBytes + 8; + if (sizeToWrite > bufsize) + { + // buffer not big enough, ask for a bigger one + return -sizeToWrite; + } + memcpy(buffer, address - 8, sizeToWrite); + return sizeToWrite; +} + +void CCDynamicArray::Unserialize(int index, const char *serializedData, int dataSize) { + char *newArray = new char[dataSize]; + memcpy(newArray, serializedData, dataSize); + ccRegisterUnserializedObject(index, &newArray[8], this); +} + +DynObjectRef CCDynamicArray::Create(int numElements, int elementSize, bool isManagedType) +{ + char *newArray = new char[numElements * elementSize + 8]; + memset(newArray, 0, numElements * elementSize + 8); + int *sizePtr = (int*)newArray; + sizePtr[0] = numElements; + sizePtr[1] = numElements * elementSize; + if (isManagedType) + sizePtr[0] |= ARRAY_MANAGED_TYPE_FLAG; + void *obj_ptr = &newArray[8]; + int32_t handle = ccRegisterManagedObject(obj_ptr, this); + if (handle == 0) + { + delete[] newArray; + return DynObjectRef(0, nullptr); + } + return DynObjectRef(handle, obj_ptr); +} + + +const char* CCDynamicArray::GetFieldPtr(const char *address, intptr_t offset) +{ + return address + offset; +} + +void CCDynamicArray::Read(const char *address, intptr_t offset, void *dest, int size) +{ + memcpy(dest, address + offset, size); +} + +uint8_t CCDynamicArray::ReadInt8(const char *address, intptr_t offset) +{ + return *(uint8_t*)(address + offset); +} + +int16_t CCDynamicArray::ReadInt16(const char *address, intptr_t offset) +{ + return *(int16_t*)(address + offset); +} + +int32_t CCDynamicArray::ReadInt32(const char *address, intptr_t offset) +{ + return *(int32_t*)(address + offset); +} + +float CCDynamicArray::ReadFloat(const char *address, intptr_t offset) +{ + return *(float*)(address + offset); +} + +void CCDynamicArray::Write(const char *address, intptr_t offset, void *src, int size) +{ + memcpy((void*)(address + offset), src, size); +} + +void CCDynamicArray::WriteInt8(const char *address, intptr_t offset, uint8_t val) +{ + *(uint8_t*)(address + offset) = val; +} + +void CCDynamicArray::WriteInt16(const char *address, intptr_t offset, int16_t val) +{ + *(int16_t*)(address + offset) = val; +} + +void CCDynamicArray::WriteInt32(const char *address, intptr_t offset, int32_t val) +{ + *(int32_t*)(address + offset) = val; +} + +void CCDynamicArray::WriteFloat(const char *address, intptr_t offset, float val) +{ + *(float*)(address + offset) = val; +} + +CCDynamicArray globalDynamicArray; + + +DynObjectRef DynamicArrayHelpers::CreateStringArray(const std::vector items) +{ + // NOTE: we need element size of "handle" for array of managed pointers + DynObjectRef arr = globalDynamicArray.Create(items.size(), sizeof(int32_t), true); + if (!arr.second) + return arr; + // Create script strings and put handles into array + int32_t *slots = static_cast(arr.second); + for (auto s : items) + { + DynObjectRef str = stringClassImpl->CreateString(s); + *(slots++) = str.first; + } + return arr; +} diff --git a/engines/ags/engine/ac/dynobj/cc_dynamicarray.h b/engines/ags/engine/ac/dynobj/cc_dynamicarray.h new file mode 100644 index 00000000000..4619ff7137d --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_dynamicarray.h @@ -0,0 +1,58 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __CC_DYNAMICARRAY_H +#define __CC_DYNAMICARRAY_H + +#include +#include "ac/dynobj/cc_dynamicobject.h" // ICCDynamicObject + +#define CC_DYNAMIC_ARRAY_TYPE_NAME "CCDynamicArray" +#define ARRAY_MANAGED_TYPE_FLAG 0x80000000 + +struct CCDynamicArray final : ICCDynamicObject +{ + // return the type name of the object + const char *GetType() override; + int Dispose(const char *address, bool force) override; + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + virtual void Unserialize(int index, const char *serializedData, int dataSize); + // Create managed array object and return a pointer to the beginning of a buffer + DynObjectRef Create(int numElements, int elementSize, bool isManagedType); + + // Legacy support for reading and writing object values by their relative offset + const char* GetFieldPtr(const char *address, intptr_t offset) override; + void Read(const char *address, intptr_t offset, void *dest, int size) override; + uint8_t ReadInt8(const char *address, intptr_t offset) override; + int16_t ReadInt16(const char *address, intptr_t offset) override; + int32_t ReadInt32(const char *address, intptr_t offset) override; + float ReadFloat(const char *address, intptr_t offset) override; + void Write(const char *address, intptr_t offset, void *src, int size) override; + void WriteInt8(const char *address, intptr_t offset, uint8_t val) override; + void WriteInt16(const char *address, intptr_t offset, int16_t val) override; + void WriteInt32(const char *address, intptr_t offset, int32_t val) override; + void WriteFloat(const char *address, intptr_t offset, float val) override; +}; + +extern CCDynamicArray globalDynamicArray; + +// Helper functions for setting up dynamic arrays. +namespace DynamicArrayHelpers +{ + // Create array of managed strings + DynObjectRef CreateStringArray(const std::vector); +}; + +#endif \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_dynamicobject.cpp b/engines/ags/engine/ac/dynobj/cc_dynamicobject.cpp new file mode 100644 index 00000000000..7c9a1039d4d --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_dynamicobject.cpp @@ -0,0 +1,151 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// C-Script run-time interpreter (c) 2001 Chris Jones +// +// You must DISABLE OPTIMIZATIONS AND REGISTER VARIABLES in your compiler +// when compiling this, or strange results can happen. +// +// There is a problem with importing functions on 16-bit compilers: the +// script system assumes that all parameters are passed as 4 bytes, which +// ints are not on 16-bit systems. Be sure to define all parameters as longs, +// or join the 21st century and switch to DJGPP or Visual C++. +// +//============================================================================= + +//#define DEBUG_MANAGED_OBJECTS + +#include +#include +#include "ac/dynobj/cc_dynamicobject.h" +#include "ac/dynobj/managedobjectpool.h" +#include "debug/out.h" +#include "script/cc_error.h" +#include "script/script_common.h" +#include "util/stream.h" + +using namespace AGS::Common; + +ICCStringClass *stringClassImpl = nullptr; + +// set the class that will be used for dynamic strings +void ccSetStringClassImpl(ICCStringClass *theClass) { + stringClassImpl = theClass; +} + +// register a memory handle for the object and allow script +// pointers to point to it +int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *callback, bool plugin_object) { + int32_t handl = pool.AddObject((const char*)object, callback, plugin_object); + + ManagedObjectLog("Register managed object type '%s' handle=%d addr=%08X", + ((callback == NULL) ? "(unknown)" : callback->GetType()), handl, object); + + return handl; +} + +// register a de-serialized object +int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *callback, bool plugin_object) { + return pool.AddUnserializedObject((const char*)object, callback, plugin_object, index); +} + +// unregister a particular object +int ccUnRegisterManagedObject(const void *object) { + return pool.RemoveObject((const char*)object); +} + +// remove all registered objects +void ccUnregisterAllObjects() { + pool.reset(); +} + +// serialize all objects to disk +void ccSerializeAllObjects(Stream *out) { + pool.WriteToDisk(out); +} + +// un-serialise all objects (will remove all currently registered ones) +int ccUnserializeAllObjects(Stream *in, ICCObjectReader *callback) { + return pool.ReadFromDisk(in, callback); +} + +// dispose the object if RefCount==0 +void ccAttemptDisposeObject(int32_t handle) { + pool.CheckDispose(handle); +} + +// translate between object handles and memory addresses +int32_t ccGetObjectHandleFromAddress(const char *address) { + // set to null + if (address == nullptr) + return 0; + + int32_t handl = pool.AddressToHandle(address); + + ManagedObjectLog("Line %d WritePtr: %08X to %d", currentline, address, handl); + + if (handl == 0) { + cc_error("Pointer cast failure: the object being pointed to is not in the managed object pool"); + return -1; + } + return handl; +} + +const char *ccGetObjectAddressFromHandle(int32_t handle) { + if (handle == 0) { + return nullptr; + } + const char *addr = pool.HandleToAddress(handle); + + ManagedObjectLog("Line %d ReadPtr: %d to %08X", currentline, handle, addr); + + if (addr == nullptr) { + cc_error("Error retrieving pointer: invalid handle %d", handle); + return nullptr; + } + return addr; +} + +ScriptValueType ccGetObjectAddressAndManagerFromHandle(int32_t handle, void *&object, ICCDynamicObject *&manager) +{ + if (handle == 0) { + object = nullptr; + manager = nullptr; + return kScValUndefined; + } + ScriptValueType obj_type = pool.HandleToAddressAndManager(handle, object, manager); + if (obj_type == kScValUndefined) { + cc_error("Error retrieving pointer: invalid handle %d", handle); + } + return obj_type; +} + +int ccAddObjectReference(int32_t handle) { + if (handle == 0) + return 0; + + return pool.AddRef(handle); +} + +int ccReleaseObjectReference(int32_t handle) { + if (handle == 0) + return 0; + + if (pool.HandleToAddress(handle) == nullptr) { + cc_error("Error releasing pointer: invalid handle %d", handle); + return -1; + } + + return pool.SubRef(handle); +} diff --git a/engines/ags/engine/ac/dynobj/cc_dynamicobject.h b/engines/ags/engine/ac/dynobj/cc_dynamicobject.h new file mode 100644 index 00000000000..2d417d5018a --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_dynamicobject.h @@ -0,0 +1,116 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Managed script object interface. +// +//============================================================================= +#ifndef __CC_DYNAMICOBJECT_H +#define __CC_DYNAMICOBJECT_H + +#include +#include "core/types.h" + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +// A pair of managed handle and abstract object pointer +typedef std::pair DynObjectRef; + + +// OBJECT-BASED SCRIPTING RUNTIME FUNCTIONS +// interface +struct ICCDynamicObject { + // when a ref count reaches 0, this is called with the address + // of the object. Return 1 to remove the object from memory, 0 to + // leave it + // The "force" flag tells system to detach the object, breaking any links and references + // to other managed objects or game resources (instead of disposing these too). + // TODO: it might be better to rewrite the managed pool and remove this flag at all, + // because it makes the use of this interface prone to mistakes. + virtual int Dispose(const char *address, bool force = false) = 0; + // return the type name of the object + virtual const char *GetType() = 0; + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + // TODO: pass savegame format version + virtual int Serialize(const char *address, char *buffer, int bufsize) = 0; + + // Legacy support for reading and writing object values by their relative offset. + // WARNING: following were never a part of plugin API, therefore these methods + // should **never** be called for kScValPluginObject script objects! + // + // RE: GetFieldPtr() + // According to AGS script specification, when the old-string pointer or char array is passed + // as an argument, the byte-code does not include any specific command for the member variable + // retrieval and instructs to pass an address of the object itself with certain offset. + // This results in functions like StrCopy writing directly over object address. + // There may be other implementations, but the big question is: how to detect when this is + // necessary, because byte-code does not contain any distinct operation for this case. + // The worst thing here is that with the current byte-code structure we can never tell whether + // offset 0 means getting pointer to whole object or a pointer to its first field. + virtual const char* GetFieldPtr(const char *address, intptr_t offset) = 0; + virtual void Read(const char *address, intptr_t offset, void *dest, int size) = 0; + virtual uint8_t ReadInt8(const char *address, intptr_t offset) = 0; + virtual int16_t ReadInt16(const char *address, intptr_t offset) = 0; + virtual int32_t ReadInt32(const char *address, intptr_t offset) = 0; + virtual float ReadFloat(const char *address, intptr_t offset) = 0; + virtual void Write(const char *address, intptr_t offset, void *src, int size) = 0; + virtual void WriteInt8(const char *address, intptr_t offset, uint8_t val) = 0; + virtual void WriteInt16(const char *address, intptr_t offset, int16_t val) = 0; + virtual void WriteInt32(const char *address, intptr_t offset, int32_t val) = 0; + virtual void WriteFloat(const char *address, intptr_t offset, float val) = 0; + +protected: + ICCDynamicObject() = default; + ~ICCDynamicObject() = default; +}; + +struct ICCObjectReader { + // TODO: pass savegame format version + virtual void Unserialize(int index, const char *objectType, const char *serializedData, int dataSize) = 0; +}; +struct ICCStringClass { + virtual DynObjectRef CreateString(const char *fromText) = 0; +}; + +// set the class that will be used for dynamic strings +extern void ccSetStringClassImpl(ICCStringClass *theClass); +// register a memory handle for the object and allow script +// pointers to point to it +extern int32_t ccRegisterManagedObject(const void *object, ICCDynamicObject *, bool plugin_object = false); +// register a de-serialized object +extern int32_t ccRegisterUnserializedObject(int index, const void *object, ICCDynamicObject *, bool plugin_object = false); +// unregister a particular object +extern int ccUnRegisterManagedObject(const void *object); +// remove all registered objects +extern void ccUnregisterAllObjects(); +// serialize all objects to disk +extern void ccSerializeAllObjects(Common::Stream *out); +// un-serialise all objects (will remove all currently registered ones) +extern int ccUnserializeAllObjects(Common::Stream *in, ICCObjectReader *callback); +// dispose the object if RefCount==0 +extern void ccAttemptDisposeObject(int32_t handle); +// translate between object handles and memory addresses +extern int32_t ccGetObjectHandleFromAddress(const char *address); +// TODO: not sure if it makes any sense whatsoever to use "const char*" +// in these functions, might as well change to char* or just void*. +extern const char *ccGetObjectAddressFromHandle(int32_t handle); + +extern int ccAddObjectReference(int32_t handle); +extern int ccReleaseObjectReference(int32_t handle); + +extern ICCStringClass *stringClassImpl; + +#endif // __CC_DYNAMICOBJECT_H diff --git a/engines/ags/engine/ac/dynobj/cc_dynamicobject_addr_and_manager.h b/engines/ags/engine/ac/dynobj/cc_dynamicobject_addr_and_manager.h new file mode 100644 index 00000000000..0f22f30f174 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_dynamicobject_addr_and_manager.h @@ -0,0 +1,11 @@ +#ifndef ADDR_AND_MANAGER_H +#define ADDR_AND_MANAGER_H + +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_dynamicobject.h" + +extern ScriptValueType ccGetObjectAddressAndManagerFromHandle( + int32_t handle, void *&object, ICCDynamicObject *&manager); + +#endif + diff --git a/engines/ags/engine/ac/dynobj/cc_gui.cpp b/engines/ags/engine/ac/dynobj/cc_gui.cpp new file mode 100644 index 00000000000..40ff70c4a1e --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_gui.cpp @@ -0,0 +1,38 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_gui.h" +#include "ac/dynobj/scriptgui.h" + +extern ScriptGUI *scrGui; + +// return the type name of the object +const char *CCGUI::GetType() { + return "GUI"; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int CCGUI::Serialize(const char *address, char *buffer, int bufsize) { + ScriptGUI *shh = (ScriptGUI*)address; + StartSerialize(buffer); + SerializeInt(shh->id); + return EndSerialize(); +} + +void CCGUI::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int num = UnserializeInt(); + ccRegisterUnserializedObject(index, &scrGui[num], this); +} \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_gui.h b/engines/ags/engine/ac/dynobj/cc_gui.h new file mode 100644 index 00000000000..b29f5171bce --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_gui.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CCGUI_H +#define __AC_CCGUI_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCGUI final : AGSCCDynamicObject { + + // return the type name of the object + const char *GetType() override; + + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + + void Unserialize(int index, const char *serializedData, int dataSize) override; +}; + +#endif // __AC_CCGUI_H diff --git a/engines/ags/engine/ac/dynobj/cc_guiobject.cpp b/engines/ags/engine/ac/dynobj/cc_guiobject.cpp new file mode 100644 index 00000000000..a4d61533270 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_guiobject.cpp @@ -0,0 +1,42 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_guiobject.h" +#include "ac/dynobj/scriptgui.h" +#include "gui/guimain.h" +#include "gui/guiobject.h" + +using AGS::Common::GUIObject; + +// return the type name of the object +const char *CCGUIObject::GetType() { + return "GUIObject"; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int CCGUIObject::Serialize(const char *address, char *buffer, int bufsize) { + GUIObject *guio = (GUIObject*)address; + StartSerialize(buffer); + SerializeInt(guio->ParentId); + SerializeInt(guio->Id); + return EndSerialize(); +} + +void CCGUIObject::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int guinum = UnserializeInt(); + int objnum = UnserializeInt(); + ccRegisterUnserializedObject(index, guis[guinum].GetControl(objnum), this); +} diff --git a/engines/ags/engine/ac/dynobj/cc_guiobject.h b/engines/ags/engine/ac/dynobj/cc_guiobject.h new file mode 100644 index 00000000000..2d261956f24 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_guiobject.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CCGUIOBJECT_H +#define __AC_CCGUIOBJECT_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCGUIObject final : AGSCCDynamicObject { + + // return the type name of the object + const char *GetType() override; + + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + + void Unserialize(int index, const char *serializedData, int dataSize) override; + +}; + +#endif // __AC_CCGUIOBJECT_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.cpp b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp new file mode 100644 index 00000000000..cc109cd228f --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_hotspot.cpp @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_hotspot.h" +#include "ac/dynobj/scripthotspot.h" +#include "ac/common_defines.h" +#include "game/roomstruct.h" + +extern ScriptHotspot scrHotspot[MAX_ROOM_HOTSPOTS]; + +// return the type name of the object +const char *CCHotspot::GetType() { + return "Hotspot"; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int CCHotspot::Serialize(const char *address, char *buffer, int bufsize) { + ScriptHotspot *shh = (ScriptHotspot*)address; + StartSerialize(buffer); + SerializeInt(shh->id); + return EndSerialize(); +} + +void CCHotspot::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int num = UnserializeInt(); + ccRegisterUnserializedObject(index, &scrHotspot[num], this); +} \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_hotspot.h b/engines/ags/engine/ac/dynobj/cc_hotspot.h new file mode 100644 index 00000000000..8137cd37407 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_hotspot.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CCHOTSPOT_H +#define __AC_CCHOTSPOT_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCHotspot final : AGSCCDynamicObject { + + // return the type name of the object + const char *GetType() override; + + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + + void Unserialize(int index, const char *serializedData, int dataSize) override; + +}; + +#endif // __AC_CCHOTSPOT_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.cpp b/engines/ags/engine/ac/dynobj/cc_inventory.cpp new file mode 100644 index 00000000000..cf9268c997d --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_inventory.cpp @@ -0,0 +1,39 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_inventory.h" +#include "ac/dynobj/scriptinvitem.h" +#include "ac/characterinfo.h" + +extern ScriptInvItem scrInv[MAX_INV]; + +// return the type name of the object +const char *CCInventory::GetType() { + return "Inventory"; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int CCInventory::Serialize(const char *address, char *buffer, int bufsize) { + ScriptInvItem *shh = (ScriptInvItem*)address; + StartSerialize(buffer); + SerializeInt(shh->id); + return EndSerialize(); +} + +void CCInventory::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int num = UnserializeInt(); + ccRegisterUnserializedObject(index, &scrInv[num], this); +} \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_inventory.h b/engines/ags/engine/ac/dynobj/cc_inventory.h new file mode 100644 index 00000000000..4e668aff706 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_inventory.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CCINVENTORY_H +#define __AC_CCINVENTORY_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCInventory final : AGSCCDynamicObject { + + // return the type name of the object + const char *GetType() override; + + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + + void Unserialize(int index, const char *serializedData, int dataSize) override; + +}; + +#endif // __AC_CCINVENTORY_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_object.cpp b/engines/ags/engine/ac/dynobj/cc_object.cpp new file mode 100644 index 00000000000..215ad0e469c --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_object.cpp @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_object.h" +#include "ac/dynobj/scriptobject.h" +#include "ac/common_defines.h" +#include "game/roomstruct.h" + +extern ScriptObject scrObj[MAX_ROOM_OBJECTS]; + +// return the type name of the object +const char *CCObject::GetType() { + return "Object"; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int CCObject::Serialize(const char *address, char *buffer, int bufsize) { + ScriptObject *shh = (ScriptObject*)address; + StartSerialize(buffer); + SerializeInt(shh->id); + return EndSerialize(); +} + +void CCObject::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int num = UnserializeInt(); + ccRegisterUnserializedObject(index, &scrObj[num], this); +} diff --git a/engines/ags/engine/ac/dynobj/cc_object.h b/engines/ags/engine/ac/dynobj/cc_object.h new file mode 100644 index 00000000000..6e654441325 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_object.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CCOBJECT_H +#define __AC_CCOBJECT_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCObject final : AGSCCDynamicObject { + + // return the type name of the object + const char *GetType() override; + + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + + void Unserialize(int index, const char *serializedData, int dataSize) override; + +}; + +#endif // __AC_CCOBJECT_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_region.cpp b/engines/ags/engine/ac/dynobj/cc_region.cpp new file mode 100644 index 00000000000..92ba3491182 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_region.cpp @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_region.h" +#include "ac/dynobj/scriptregion.h" +#include "ac/common_defines.h" +#include "game/roomstruct.h" + +extern ScriptRegion scrRegion[MAX_ROOM_REGIONS]; + +// return the type name of the object +const char *CCRegion::GetType() { + return "Region"; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int CCRegion::Serialize(const char *address, char *buffer, int bufsize) { + ScriptRegion *shh = (ScriptRegion*)address; + StartSerialize(buffer); + SerializeInt(shh->id); + return EndSerialize(); +} + +void CCRegion::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int num = UnserializeInt(); + ccRegisterUnserializedObject(index, &scrRegion[num], this); +} diff --git a/engines/ags/engine/ac/dynobj/cc_region.h b/engines/ags/engine/ac/dynobj/cc_region.h new file mode 100644 index 00000000000..626ee4e2bc5 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_region.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CCREGION_H +#define __AC_CCREGION_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct CCRegion final : AGSCCDynamicObject { + + // return the type name of the object + const char *GetType() override; + + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + + void Unserialize(int index, const char *serializedData, int dataSize) override; + +}; + +#endif // __AC_CCREGION_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.cpp b/engines/ags/engine/ac/dynobj/cc_serializer.cpp new file mode 100644 index 00000000000..140613116a6 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_serializer.cpp @@ -0,0 +1,144 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/dynobj/cc_serializer.h" +#include "ac/dynobj/all_dynamicclasses.h" +#include "ac/dynobj/all_scriptclasses.h" +#include "ac/dynobj/scriptcamera.h" +#include "ac/dynobj/scriptcontainers.h" +#include "ac/dynobj/scriptfile.h" +#include "ac/dynobj/scriptuserobject.h" +#include "ac/dynobj/scriptviewport.h" +#include "ac/game.h" +#include "debug/debug_log.h" +#include "plugin/agsplugin.h" +#include "plugin/pluginobjectreader.h" + +extern CCGUIObject ccDynamicGUIObject; +extern CCCharacter ccDynamicCharacter; +extern CCHotspot ccDynamicHotspot; +extern CCRegion ccDynamicRegion; +extern CCInventory ccDynamicInv; +extern CCGUI ccDynamicGUI; +extern CCObject ccDynamicObject; +extern CCDialog ccDynamicDialog; +extern ScriptDrawingSurface* dialogOptionsRenderingSurface; +extern ScriptDialogOptionsRendering ccDialogOptionsRendering; +extern PluginObjectReader pluginReaders[MAX_PLUGIN_OBJECT_READERS]; +extern int numPluginReaders; + +// *** De-serialization of script objects + + + +void AGSDeSerializer::Unserialize(int index, const char *objectType, const char *serializedData, int dataSize) { + if (strcmp(objectType, "GUIObject") == 0) { + ccDynamicGUIObject.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "Character") == 0) { + ccDynamicCharacter.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "Hotspot") == 0) { + ccDynamicHotspot.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "Region") == 0) { + ccDynamicRegion.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "Inventory") == 0) { + ccDynamicInv.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "Dialog") == 0) { + ccDynamicDialog.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "GUI") == 0) { + ccDynamicGUI.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "Object") == 0) { + ccDynamicObject.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "String") == 0) { + ScriptString *scf = new ScriptString(); + scf->Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "File") == 0) { + // files cannot be restored properly -- so just recreate + // the object; attempting any operations on it will fail + sc_File *scf = new sc_File(); + ccRegisterUnserializedObject(index, scf, scf); + } + else if (strcmp(objectType, "Overlay") == 0) { + ScriptOverlay *scf = new ScriptOverlay(); + scf->Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "DateTime") == 0) { + ScriptDateTime *scf = new ScriptDateTime(); + scf->Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "ViewFrame") == 0) { + ScriptViewFrame *scf = new ScriptViewFrame(); + scf->Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "DynamicSprite") == 0) { + ScriptDynamicSprite *scf = new ScriptDynamicSprite(); + scf->Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "DrawingSurface") == 0) { + ScriptDrawingSurface *sds = new ScriptDrawingSurface(); + sds->Unserialize(index, serializedData, dataSize); + + if (sds->isLinkedBitmapOnly) + { + dialogOptionsRenderingSurface = sds; + } + } + else if (strcmp(objectType, "DialogOptionsRendering") == 0) + { + ccDialogOptionsRendering.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "StringDictionary") == 0) + { + Dict_Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "StringSet") == 0) + { + Set_Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "Viewport2") == 0) + { + Viewport_Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "Camera2") == 0) + { + Camera_Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "UserObject") == 0) { + ScriptUserObject *suo = new ScriptUserObject(); + suo->Unserialize(index, serializedData, dataSize); + } + else if (!unserialize_audio_script_object(index, objectType, serializedData, dataSize)) + { + // check if the type is read by a plugin + for (int ii = 0; ii < numPluginReaders; ii++) { + if (strcmp(objectType, pluginReaders[ii].type) == 0) { + pluginReaders[ii].reader->Unserialize(index, serializedData, dataSize); + return; + } + } + quitprintf("Unserialise: unknown object type: '%s'", objectType); + } +} + +AGSDeSerializer ccUnserializer; + diff --git a/engines/ags/engine/ac/dynobj/cc_serializer.h b/engines/ags/engine/ac/dynobj/cc_serializer.h new file mode 100644 index 00000000000..d30a8f477d4 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/cc_serializer.h @@ -0,0 +1,27 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SERIALIZER_H +#define __AC_SERIALIZER_H + +#include "ac/dynobj/cc_dynamicobject.h" + +struct AGSDeSerializer : ICCObjectReader { + + void Unserialize(int index, const char *objectType, const char *serializedData, int dataSize) override; +}; + +extern AGSDeSerializer ccUnserializer; + +#endif // __AC_SERIALIZER_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/managedobjectpool.cpp b/engines/ags/engine/ac/dynobj/managedobjectpool.cpp new file mode 100644 index 00000000000..422aff050fe --- /dev/null +++ b/engines/ags/engine/ac/dynobj/managedobjectpool.cpp @@ -0,0 +1,322 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include +#include +#include "ac/dynobj/managedobjectpool.h" +#include "ac/dynobj/cc_dynamicarray.h" // globalDynamicArray, constants +#include "debug/out.h" +#include "util/string_utils.h" // fputstring, etc +#include "script/cc_error.h" +#include "script/script_common.h" +#include "util/stream.h" + +using namespace AGS::Common; + +const auto OBJECT_CACHE_MAGIC_NUMBER = 0xa30b; +const auto SERIALIZE_BUFFER_SIZE = 10240; +const auto GARBAGE_COLLECTION_INTERVAL = 1024; +const auto RESERVED_SIZE = 2048; + +int ManagedObjectPool::Remove(ManagedObject &o, bool force) { + if (!o.isUsed()) { return 1; } // already removed + + bool canBeRemovedFromPool = o.callback->Dispose(o.addr, force) != 0; + if (!(canBeRemovedFromPool || force)) { return 0; } + + auto handle = o.handle; + available_ids.push(o.handle); + + handleByAddress.erase(o.addr); + o = ManagedObject(); + + ManagedObjectLog("Line %d Disposed managed object handle=%d", currentline, handle); + + return 1; +} + +int32_t ManagedObjectPool::AddRef(int32_t handle) { + if (handle < 0 || (size_t)handle >= objects.size()) { return 0; } + auto & o = objects[handle]; + if (!o.isUsed()) { return 0; } + + o.refCount += 1; + ManagedObjectLog("Line %d AddRef: handle=%d new refcount=%d", currentline, o.handle, o.refCount); + return o.refCount; +} + +int ManagedObjectPool::CheckDispose(int32_t handle) { + if (handle < 0 || (size_t)handle >= objects.size()) { return 1; } + auto & o = objects[handle]; + if (!o.isUsed()) { return 1; } + if (o.refCount >= 1) { return 0; } + return Remove(o); +} + +int32_t ManagedObjectPool::SubRef(int32_t handle) { + if (handle < 0 || (size_t)handle >= objects.size()) { return 0; } + auto & o = objects[handle]; + if (!o.isUsed()) { return 0; } + + o.refCount--; + auto newRefCount = o.refCount; + auto canBeDisposed = (o.addr != disableDisposeForObject); + if (canBeDisposed) { + CheckDispose(handle); + } + // object could be removed at this point, don't use any values. + ManagedObjectLog("Line %d SubRef: handle=%d new refcount=%d canBeDisposed=%d", currentline, handle, newRefCount, canBeDisposed); + return newRefCount; +} + +int32_t ManagedObjectPool::AddressToHandle(const char *addr) { + if (addr == nullptr) { return 0; } + auto it = handleByAddress.find(addr); + if (it == handleByAddress.end()) { return 0; } + return it->second; +} + +// this function is called often (whenever a pointer is used) +const char* ManagedObjectPool::HandleToAddress(int32_t handle) { + if (handle < 0 || (size_t)handle >= objects.size()) { return nullptr; } + auto & o = objects[handle]; + if (!o.isUsed()) { return nullptr; } + return o.addr; +} + +// this function is called often (whenever a pointer is used) +ScriptValueType ManagedObjectPool::HandleToAddressAndManager(int32_t handle, void *&object, ICCDynamicObject *&manager) { + if (handle < 0 || (size_t)handle >= objects.size()) { return kScValUndefined; } + auto & o = objects[handle]; + if (!o.isUsed()) { return kScValUndefined; } + + object = (void *)o.addr; // WARNING: This strips the const from the char* pointer. + manager = o.callback; + return o.obj_type; +} + +int ManagedObjectPool::RemoveObject(const char *address) { + if (address == nullptr) { return 0; } + auto it = handleByAddress.find(address); + if (it == handleByAddress.end()) { return 0; } + + auto & o = objects[it->second]; + return Remove(o, true); +} + +void ManagedObjectPool::RunGarbageCollectionIfAppropriate() +{ + if (objectCreationCounter <= GARBAGE_COLLECTION_INTERVAL) { return; } + RunGarbageCollection(); + objectCreationCounter = 0; +} + +void ManagedObjectPool::RunGarbageCollection() +{ + for (int i = 1; i < nextHandle; i++) { + auto & o = objects[i]; + if (!o.isUsed()) { continue; } + if (o.refCount < 1) { + Remove(o); + } + } + ManagedObjectLog("Ran garbage collection"); +} + +int ManagedObjectPool::AddObject(const char *address, ICCDynamicObject *callback, bool plugin_object) +{ + int32_t handle; + + if (!available_ids.empty()) { + handle = available_ids.front(); + available_ids.pop(); + } else { + handle = nextHandle++; + if ((size_t)handle >= objects.size()) { + objects.resize(handle + 1024, ManagedObject()); + } + } + + auto & o = objects[handle]; + if (o.isUsed()) { cc_error("used: %d", handle); return 0; } + + o = ManagedObject(plugin_object ? kScValPluginObject : kScValDynamicObject, handle, address, callback); + + handleByAddress.insert({address, o.handle}); + objectCreationCounter++; + ManagedObjectLog("Allocated managed object handle=%d, type=%s", handle, callback->GetType()); + return o.handle; +} + + +int ManagedObjectPool::AddUnserializedObject(const char *address, ICCDynamicObject *callback, bool plugin_object, int handle) +{ + if (handle < 0) { cc_error("Attempt to assign invalid handle: %d", handle); return 0; } + if ((size_t)handle >= objects.size()) { + objects.resize(handle + 1024, ManagedObject()); + } + + auto & o = objects[handle]; + if (o.isUsed()) { cc_error("bad save. used: %d", o.handle); return 0; } + + o = ManagedObject(plugin_object ? kScValPluginObject : kScValDynamicObject, handle, address, callback); + + handleByAddress.insert({address, o.handle}); + ManagedObjectLog("Allocated unserialized managed object handle=%d, type=%s", o.handle, callback->GetType()); + return o.handle; +} + +void ManagedObjectPool::WriteToDisk(Stream *out) { + + // use this opportunity to clean up any non-referenced pointers + RunGarbageCollection(); + + std::vector serializeBuffer; + serializeBuffer.resize(SERIALIZE_BUFFER_SIZE); + + out->WriteInt32(OBJECT_CACHE_MAGIC_NUMBER); + out->WriteInt32(2); // version + + int size = 0; + for (int i = 1; i < nextHandle; i++) { + auto const & o = objects[i]; + if (o.isUsed()) { + size += 1; + } + } + out->WriteInt32(size); + + for (int i = 1; i < nextHandle; i++) { + auto const & o = objects[i]; + if (!o.isUsed()) { continue; } + + // handle + out->WriteInt32(o.handle); + // write the type of the object + StrUtil::WriteCStr((char*)o.callback->GetType(), out); + // now write the object data + int bytesWritten = o.callback->Serialize(o.addr, &serializeBuffer.front(), serializeBuffer.size()); + if ((bytesWritten < 0) && ((size_t)(-bytesWritten) > serializeBuffer.size())) + { + // buffer not big enough, re-allocate with requested size + serializeBuffer.resize(-bytesWritten); + bytesWritten = o.callback->Serialize(o.addr, &serializeBuffer.front(), serializeBuffer.size()); + } + assert(bytesWritten >= 0); + out->WriteInt32(bytesWritten); + out->Write(&serializeBuffer.front(), bytesWritten); + out->WriteInt32(o.refCount); + + ManagedObjectLog("Wrote handle = %d", o.handle); + } +} + +int ManagedObjectPool::ReadFromDisk(Stream *in, ICCObjectReader *reader) { + if (in->ReadInt32() != OBJECT_CACHE_MAGIC_NUMBER) { + cc_error("Data was not written by ccSeralize"); + return -1; + } + + char typeNameBuffer[200]; + std::vector serializeBuffer; + serializeBuffer.resize(SERIALIZE_BUFFER_SIZE); + + auto version = in->ReadInt32(); + + switch (version) { + case 1: + { + // IMPORTANT: numObjs is "nextHandleId", which is why we iterate from 1 to numObjs-1 + int numObjs = in->ReadInt32(); + for (int i = 1; i < numObjs; i++) { + StrUtil::ReadCStr(typeNameBuffer, in, sizeof(typeNameBuffer)); + if (typeNameBuffer[0] != 0) { + size_t numBytes = in->ReadInt32(); + if (numBytes > serializeBuffer.size()) { + serializeBuffer.resize(numBytes); + } + in->Read(&serializeBuffer.front(), numBytes); + if (strcmp(typeNameBuffer, CC_DYNAMIC_ARRAY_TYPE_NAME) == 0) { + globalDynamicArray.Unserialize(i, &serializeBuffer.front(), numBytes); + } else { + reader->Unserialize(i, typeNameBuffer, &serializeBuffer.front(), numBytes); + } + objects[i].refCount = in->ReadInt32(); + ManagedObjectLog("Read handle = %d", objects[i].handle); + } + } + } + break; + case 2: + { + // This is actually number of objects written. + int objectsSize = in->ReadInt32(); + for (int i = 0; i < objectsSize; i++) { + auto handle = in->ReadInt32(); + assert (handle >= 1); + StrUtil::ReadCStr(typeNameBuffer, in, sizeof(typeNameBuffer)); + assert (typeNameBuffer[0] != 0); + size_t numBytes = in->ReadInt32(); + if (numBytes > serializeBuffer.size()) { + serializeBuffer.resize(numBytes); + } + in->Read(&serializeBuffer.front(), numBytes); + if (strcmp(typeNameBuffer, CC_DYNAMIC_ARRAY_TYPE_NAME) == 0) { + globalDynamicArray.Unserialize(handle, &serializeBuffer.front(), numBytes); + } else { + reader->Unserialize(handle, typeNameBuffer, &serializeBuffer.front(), numBytes); + } + objects[handle].refCount = in->ReadInt32(); + ManagedObjectLog("Read handle = %d", objects[i].handle); + } + } + break; + default: + cc_error("Invalid data version: %d", version); + return -1; + } + + // re-adjust next handles. (in case saved in random order) + while (!available_ids.empty()) { available_ids.pop(); } + nextHandle = 1; + + for (const auto &o : objects) { + if (o.isUsed()) { + nextHandle = o.handle + 1; + } + } + for (int i = 1; i < nextHandle; i++) { + if (!objects[i].isUsed()) { + available_ids.push(i); + } + } + + return 0; +} + +// de-allocate all objects +void ManagedObjectPool::reset() { + for (int i = 1; i < nextHandle; i++) { + auto & o = objects[i]; + if (!o.isUsed()) { continue; } + Remove(o, true); + } + while (!available_ids.empty()) { available_ids.pop(); } + nextHandle = 1; +} + +ManagedObjectPool::ManagedObjectPool() : objectCreationCounter(0), nextHandle(1), available_ids(), objects(RESERVED_SIZE, ManagedObject()), handleByAddress() { + handleByAddress.reserve(RESERVED_SIZE); +} + +ManagedObjectPool pool; diff --git a/engines/ags/engine/ac/dynobj/managedobjectpool.h b/engines/ags/engine/ac/dynobj/managedobjectpool.h new file mode 100644 index 00000000000..6107cadb81c --- /dev/null +++ b/engines/ags/engine/ac/dynobj/managedobjectpool.h @@ -0,0 +1,88 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __CC_MANAGEDOBJECTPOOL_H +#define __CC_MANAGEDOBJECTPOOL_H + +#include +#include +#include + +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_dynamicobject.h" // ICCDynamicObject + +namespace AGS { namespace Common { class Stream; }} +using namespace AGS; // FIXME later + +struct ManagedObjectPool final { +private: + // TODO: find out if we can make handle size_t + struct ManagedObject { + ScriptValueType obj_type; + int32_t handle; + // TODO: this makes no sense having this as "const char*", + // void* will be proper (and in all related functions) + const char *addr; + ICCDynamicObject *callback; + int refCount; + + bool isUsed() const { return obj_type != kScValUndefined; } + + ManagedObject() + : obj_type(kScValUndefined), handle(0), addr(nullptr), callback(nullptr), refCount(0) {} + ManagedObject(ScriptValueType obj_type, int32_t handle, const char *addr, ICCDynamicObject * callback) + : obj_type(obj_type), handle(handle), addr(addr), callback(callback), refCount(0) {} + }; + + int objectCreationCounter; // used to do garbage collection every so often + + int32_t nextHandle {}; // TODO: manage nextHandle's going over INT32_MAX ! + std::queue available_ids; + std::vector objects; + std::unordered_map handleByAddress; + + void Init(int32_t theHandle, const char *theAddress, ICCDynamicObject *theCallback, ScriptValueType objType); + int Remove(ManagedObject &o, bool force = false); + + void RunGarbageCollection(); + +public: + + int32_t AddRef(int32_t handle); + int CheckDispose(int32_t handle); + int32_t SubRef(int32_t handle); + int32_t AddressToHandle(const char *addr); + const char* HandleToAddress(int32_t handle); + ScriptValueType HandleToAddressAndManager(int32_t handle, void *&object, ICCDynamicObject *&manager); + int RemoveObject(const char *address); + void RunGarbageCollectionIfAppropriate(); + int AddObject(const char *address, ICCDynamicObject *callback, bool plugin_object); + int AddUnserializedObject(const char *address, ICCDynamicObject *callback, bool plugin_object, int handle); + void WriteToDisk(Common::Stream *out); + int ReadFromDisk(Common::Stream *in, ICCObjectReader *reader); + void reset(); + ManagedObjectPool(); + + const char* disableDisposeForObject {nullptr}; +}; + +extern ManagedObjectPool pool; + +#ifdef DEBUG_MANAGED_OBJECTS +#define ManagedObjectLog(...) Debug::Printf(kDbgGroup_ManObj, kDbgMsg_Debug, __VA_ARGS__) +#else +#define ManagedObjectLog(...) +#endif + +#endif // __CC_MANAGEDOBJECTPOOL_H diff --git a/engines/ags/engine/ac/dynobj/scriptaudiochannel.h b/engines/ags/engine/ac/dynobj/scriptaudiochannel.h new file mode 100644 index 00000000000..c256ead6e2f --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptaudiochannel.h @@ -0,0 +1,27 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTAUDIOCHANNEL_H +#define __AGS_EE_DYNOBJ__SCRIPTAUDIOCHANNEL_H + +struct ScriptAudioChannel +{ + int id; + int reserved; +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTAUDIOCHANNEL_H diff --git a/engines/ags/engine/ac/dynobj/scriptcamera.cpp b/engines/ags/engine/ac/dynobj/scriptcamera.cpp new file mode 100644 index 00000000000..e9db1cb9986 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptcamera.cpp @@ -0,0 +1,64 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptcamera.h" +#include "ac/gamestate.h" +#include "util/bbop.h" + +using namespace AGS::Common; + +ScriptCamera::ScriptCamera(int id) : _id(id) {} + +const char *ScriptCamera::GetType() +{ + return "Camera2"; +} + +int ScriptCamera::Dispose(const char *address, bool force) +{ + // Note that ScriptCamera is a reference to actual Camera object, + // and this deletes the reference, while camera may remain in GameState. + delete this; + return 1; +} + +int ScriptCamera::Serialize(const char *address, char *buffer, int bufsize) +{ + StartSerialize(buffer); + SerializeInt(_id); + return EndSerialize(); +} + +void ScriptCamera::Unserialize(int index, const char *serializedData, int dataSize) +{ + StartUnserialize(serializedData, dataSize); + _id = UnserializeInt(); + ccRegisterUnserializedObject(index, this, this); +} + +ScriptCamera *Camera_Unserialize(int handle, const char *serializedData, int dataSize) +{ + // The way it works now, we must not create a new script object, + // but acquire one from the GameState, which keeps the first reference. + // This is essential because GameState should be able to invalidate any + // script references when Camera gets removed. + const int id = BBOp::Int32FromLE(*((int*)serializedData)); + if (id >= 0) + { + auto scam = play.RegisterRoomCamera(id, handle); + if (scam) + return scam; + } + return new ScriptCamera(-1); // make invalid reference +} diff --git a/engines/ags/engine/ac/dynobj/scriptcamera.h b/engines/ags/engine/ac/dynobj/scriptcamera.h new file mode 100644 index 00000000000..4aa25304e58 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptcamera.h @@ -0,0 +1,44 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SCRIPTCAMERA_H +#define __AC_SCRIPTCAMERA_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +// ScriptCamera keeps a reference to actual room Camera in script. +struct ScriptCamera final : AGSCCDynamicObject +{ +public: + ScriptCamera(int id); + + // Get camera index; negative means the camera was deleted + int GetID() const { return _id; } + void SetID(int id) { _id = id; } + // Reset camera index to indicate that this reference is no longer valid + void Invalidate() { _id = -1; } + + const char *GetType() override; + int Dispose(const char *address, bool force) override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + +private: + int _id = -1; // index of camera in the game state array +}; + +// Unserialize camera from the memory stream +ScriptCamera *Camera_Unserialize(int handle, const char *serializedData, int dataSize); + +#endif // __AC_SCRIPTCAMERA_H diff --git a/engines/ags/engine/ac/dynobj/scriptcontainers.h b/engines/ags/engine/ac/dynobj/scriptcontainers.h new file mode 100644 index 00000000000..a0a374831a9 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptcontainers.h @@ -0,0 +1,29 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __AC_SCRIPTCONTAINERS_H +#define __AC_SCRIPTCONTAINERS_H + +class ScriptDictBase; +class ScriptSetBase; + +// Create and register new dictionary +ScriptDictBase *Dict_Create(bool sorted, bool case_sensitive); +// Unserialize dictionary from the memory stream +ScriptDictBase *Dict_Unserialize(int index, const char *serializedData, int dataSize); +// Create and register new set +ScriptSetBase *Set_Create(bool sorted, bool case_sensitive); +// Unserialize set from the memory stream +ScriptSetBase *Set_Unserialize(int index, const char *serializedData, int dataSize); + +#endif // __AC_SCRIPTCONTAINERS_H diff --git a/engines/ags/engine/ac/dynobj/scriptdatetime.cpp b/engines/ags/engine/ac/dynobj/scriptdatetime.cpp new file mode 100644 index 00000000000..eb57fbdf765 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdatetime.cpp @@ -0,0 +1,55 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptdatetime.h" + +int ScriptDateTime::Dispose(const char *address, bool force) { + // always dispose a DateTime + delete this; + return 1; +} + +const char *ScriptDateTime::GetType() { + return "DateTime"; +} + +int ScriptDateTime::Serialize(const char *address, char *buffer, int bufsize) { + StartSerialize(buffer); + SerializeInt(year); + SerializeInt(month); + SerializeInt(day); + SerializeInt(hour); + SerializeInt(minute); + SerializeInt(second); + SerializeInt(rawUnixTime); + return EndSerialize(); +} + +void ScriptDateTime::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + year = UnserializeInt(); + month = UnserializeInt(); + day = UnserializeInt(); + hour = UnserializeInt(); + minute = UnserializeInt(); + second = UnserializeInt(); + rawUnixTime = UnserializeInt(); + ccRegisterUnserializedObject(index, this, this); +} + +ScriptDateTime::ScriptDateTime() { + year = month = day = 0; + hour = minute = second = 0; + rawUnixTime = 0; +} diff --git a/engines/ags/engine/ac/dynobj/scriptdatetime.h b/engines/ags/engine/ac/dynobj/scriptdatetime.h new file mode 100644 index 00000000000..bc12bf27972 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdatetime.h @@ -0,0 +1,36 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTDATETIME_H +#define __AGS_EE_DYNOBJ__SCRIPTDATETIME_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct ScriptDateTime final : AGSCCDynamicObject { + int year, month, day; + int hour, minute, second; + int rawUnixTime; + + int Dispose(const char *address, bool force) override; + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + + ScriptDateTime(); +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTDATETIME_H diff --git a/engines/ags/engine/ac/dynobj/scriptdialog.h b/engines/ags/engine/ac/dynobj/scriptdialog.h new file mode 100644 index 00000000000..9d8222e5503 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdialog.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTDIALOG_H +#define __AGS_EE_DYNOBJ__SCRIPTDIALOG_H + +struct ScriptDialog { + int id; + int reserved; +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTDIALOG_H diff --git a/engines/ags/engine/ac/dynobj/scriptdialogoptionsrendering.cpp b/engines/ags/engine/ac/dynobj/scriptdialogoptionsrendering.cpp new file mode 100644 index 00000000000..d05f6389052 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdialogoptionsrendering.cpp @@ -0,0 +1,53 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptdialogoptionsrendering.h" + +// return the type name of the object +const char *ScriptDialogOptionsRendering::GetType() { + return "DialogOptionsRendering"; +} + +// serialize the object into BUFFER (which is BUFSIZE bytes) +// return number of bytes used +int ScriptDialogOptionsRendering::Serialize(const char *address, char *buffer, int bufsize) { + return 0; +} + +void ScriptDialogOptionsRendering::Unserialize(int index, const char *serializedData, int dataSize) { + ccRegisterUnserializedObject(index, this, this); +} + +void ScriptDialogOptionsRendering::Reset() +{ + x = 0; + y = 0; + width = 0; + height = 0; + hasAlphaChannel = false; + parserTextboxX = 0; + parserTextboxY = 0; + parserTextboxWidth = 0; + dialogID = 0; + surfaceToRenderTo = nullptr; + surfaceAccessed = false; + activeOptionID = -1; + chosenOptionID = -1; + needRepaint = false; +} + +ScriptDialogOptionsRendering::ScriptDialogOptionsRendering() +{ + Reset(); +} diff --git a/engines/ags/engine/ac/dynobj/scriptdialogoptionsrendering.h b/engines/ags/engine/ac/dynobj/scriptdialogoptionsrendering.h new file mode 100644 index 00000000000..a50d9d02e29 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdialogoptionsrendering.h @@ -0,0 +1,47 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SCRIPTDIALOGOPTIONSRENDERING_H +#define __AC_SCRIPTDIALOGOPTIONSRENDERING_H + +#include "ac/dynobj/scriptdrawingsurface.h" + +struct ScriptDialogOptionsRendering final : AGSCCDynamicObject { + int x, y, width, height; + bool hasAlphaChannel; + int parserTextboxX, parserTextboxY; + int parserTextboxWidth; + int dialogID; + int activeOptionID; + int chosenOptionID; + ScriptDrawingSurface *surfaceToRenderTo; + bool surfaceAccessed; + bool needRepaint; + + // return the type name of the object + const char *GetType() override; + + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + + void Unserialize(int index, const char *serializedData, int dataSize) override; + + void Reset(); + + ScriptDialogOptionsRendering(); +}; + + +#endif // __AC_SCRIPTDIALOGOPTIONSRENDERING_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/scriptdict.cpp b/engines/ags/engine/ac/dynobj/scriptdict.cpp new file mode 100644 index 00000000000..5676ae26039 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdict.cpp @@ -0,0 +1,50 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include "ac/dynobj/scriptdict.h" + +int ScriptDictBase::Dispose(const char *address, bool force) +{ + Clear(); + delete this; + return 1; +} + +const char *ScriptDictBase::GetType() +{ + return "StringDictionary"; +} + +int ScriptDictBase::Serialize(const char *address, char *buffer, int bufsize) +{ + size_t total_sz = CalcSerializeSize() + sizeof(int32_t) * 2; + if (bufsize < 0 || total_sz >(size_t)bufsize) + { + // buffer not big enough, ask for a bigger one + return -((int)total_sz); + } + StartSerialize(buffer); + SerializeInt(IsSorted()); + SerializeInt(IsCaseSensitive()); + SerializeContainer(); + return EndSerialize(); +} + +void ScriptDictBase::Unserialize(int index, const char *serializedData, int dataSize) +{ + // NOTE: we expect sorted/case flags are read by external reader; + // this is awkward, but I did not find better design solution atm + StartUnserialize(serializedData, dataSize); + UnserializeContainer(serializedData); + ccRegisterUnserializedObject(index, this, this); +} diff --git a/engines/ags/engine/ac/dynobj/scriptdict.h b/engines/ags/engine/ac/dynobj/scriptdict.h new file mode 100644 index 00000000000..e89d22c10e7 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdict.h @@ -0,0 +1,186 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Managed script object wrapping std::map and +// unordered_map. +// +// TODO: support wrapping non-owned Dictionary, passed by the reference, - +// that would let expose internal engine's dicts using same interface. +// TODO: maybe optimize key lookup operations further by not creating a String +// object from const char*. It seems, C++14 standard allows to use convertible +// types as keys; need to research what perfomance impact that would make. +// +//============================================================================= +#ifndef __AC_SCRIPTDICT_H +#define __AC_SCRIPTDICT_H + +#include +#include +#include +#include "ac/dynobj/cc_agsdynamicobject.h" +#include "util/string.h" +#include "util/string_types.h" + +using namespace AGS::Common; + +class ScriptDictBase : public AGSCCDynamicObject +{ +public: + int Dispose(const char *address, bool force) override; + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + + virtual bool IsCaseSensitive() const = 0; + virtual bool IsSorted() const = 0; + + virtual void Clear() = 0; + virtual bool Contains(const char *key) = 0; + virtual const char *Get(const char *key) = 0; + virtual bool Remove(const char *key) = 0; + virtual bool Set(const char *key, const char *value) = 0; + virtual int GetItemCount() = 0; + virtual void GetKeys(std::vector &buf) const = 0; + virtual void GetValues(std::vector &buf) const = 0; + +private: + virtual size_t CalcSerializeSize() = 0; + virtual void SerializeContainer() = 0; + virtual void UnserializeContainer(const char *serializedData) = 0; +}; + +template +class ScriptDictImpl final : public ScriptDictBase +{ +public: + typedef typename TDict::const_iterator ConstIterator; + + ScriptDictImpl() = default; + + bool IsCaseSensitive() const override { return is_casesensitive; } + bool IsSorted() const override { return is_sorted; } + + void Clear() override + { + for (auto it = _dic.begin(); it != _dic.end(); ++it) + DeleteItem(it); + _dic.clear(); + } + bool Contains(const char *key) override { return _dic.count(String::Wrapper(key)) != 0; } + const char *Get(const char *key) override + { + auto it = _dic.find(String::Wrapper(key)); + if (it == _dic.end()) return nullptr; + return it->second.GetNullableCStr(); + } + bool Remove(const char *key) override + { + auto it = _dic.find(String::Wrapper(key)); + if (it == _dic.end()) return false; + DeleteItem(it); + _dic.erase(it); + return true; + } + bool Set(const char *key, const char *value) override + { + if (!key) return false; + size_t key_len = strlen(key); + size_t value_len = value ? strlen(value) : 0; + return TryAddItem(key, key_len, value, value_len); + } + int GetItemCount() override { return _dic.size(); } + void GetKeys(std::vector &buf) const override + { + for (auto it = _dic.begin(); it != _dic.end(); ++it) + buf.push_back(it->first.GetCStr()); // keys cannot be null + } + void GetValues(std::vector &buf) const override + { + for (auto it = _dic.begin(); it != _dic.end(); ++it) + buf.push_back(it->second.GetNullableCStr()); // values may be null + } + +private: + bool TryAddItem(const char *key, size_t key_len, const char *value, size_t value_len) + { + String elem_key(key, key_len); + String elem_value; + if (value) + elem_value.SetString(value, value_len); + _dic[elem_key] = elem_value; + return true; + } + void DeleteItem(ConstIterator it) { /* do nothing */ } + + size_t CalcSerializeSize() override + { + size_t total_sz = sizeof(int32_t); + for (auto it = _dic.begin(); it != _dic.end(); ++it) + { + total_sz += sizeof(int32_t) + it->first.GetLength(); + total_sz += sizeof(int32_t) + it->second.GetLength(); + } + return total_sz; + } + + void SerializeContainer() override + { + SerializeInt((int)_dic.size()); + for (auto it = _dic.begin(); it != _dic.end(); ++it) + { + SerializeInt((int)it->first.GetLength()); + memcpy(&serbuffer[bytesSoFar], it->first.GetCStr(), it->first.GetLength()); + bytesSoFar += it->first.GetLength(); + if (it->second.GetNullableCStr()) // values may be null + { + SerializeInt((int)it->second.GetLength()); + memcpy(&serbuffer[bytesSoFar], it->second.GetCStr(), it->second.GetLength()); + bytesSoFar += it->second.GetLength(); + } + else + { + SerializeInt(-1); + } + } + } + + void UnserializeContainer(const char *serializedData) override + { + size_t item_count = (size_t)UnserializeInt(); + for (size_t i = 0; i < item_count; ++i) + { + size_t key_len = UnserializeInt(); + int key_pos = bytesSoFar; bytesSoFar += key_len; + size_t value_len = UnserializeInt(); + if (value_len == (size_t)-1) + { + TryAddItem(&serializedData[key_pos], key_len, nullptr, 0); + } + else + { + int value_pos = bytesSoFar; bytesSoFar += value_len; + TryAddItem(&serializedData[key_pos], key_len, &serializedData[value_pos], value_len); + } + } + } + + TDict _dic; +}; + +typedef ScriptDictImpl< std::map, true, true > ScriptDict; +typedef ScriptDictImpl< std::map, true, false > ScriptDictCI; +typedef ScriptDictImpl< std::unordered_map, false, true > ScriptHashDict; +typedef ScriptDictImpl< std::unordered_map, false, false > ScriptHashDictCI; + +#endif // __AC_SCRIPTDICT_H diff --git a/engines/ags/engine/ac/dynobj/scriptdrawingsurface.cpp b/engines/ags/engine/ac/dynobj/scriptdrawingsurface.cpp new file mode 100644 index 00000000000..1f68eb1ec57 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdrawingsurface.cpp @@ -0,0 +1,129 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptdrawingsurface.h" +#include "ac/spritecache.h" +#include "ac/runtime_defines.h" +#include "ac/common.h" +#include "ac/drawingsurface.h" +#include "ac/gamestate.h" +#include "ac/gamesetupstruct.h" +#include "game/roomstruct.h" +#include "gfx/bitmap.h" + +using namespace AGS::Common; + +extern RoomStruct thisroom; +extern SpriteCache spriteset; +extern Bitmap *dynamicallyCreatedSurfaces[MAX_DYNAMIC_SURFACES]; +extern GameState play; +extern GameSetupStruct game; + +Bitmap* ScriptDrawingSurface::GetBitmapSurface() +{ + // TODO: consider creating weak_ptr here, and store one in the DrawingSurface! + if (roomBackgroundNumber >= 0) + return thisroom.BgFrames[roomBackgroundNumber].Graphic.get(); + else if (dynamicSpriteNumber >= 0) + return spriteset[dynamicSpriteNumber]; + else if (dynamicSurfaceNumber >= 0) + return dynamicallyCreatedSurfaces[dynamicSurfaceNumber]; + else if (linkedBitmapOnly != nullptr) + return linkedBitmapOnly; + else if (roomMaskType > kRoomAreaNone) + return thisroom.GetMask(roomMaskType); + quit("!DrawingSurface: attempted to use surface after Release was called"); + return nullptr; +} + +Bitmap *ScriptDrawingSurface::StartDrawing() +{ + //abufBackup = abuf; + return this->GetBitmapSurface(); +} + +void ScriptDrawingSurface::FinishedDrawingReadOnly() +{ + //abuf = abufBackup; +} + +void ScriptDrawingSurface::FinishedDrawing() +{ + FinishedDrawingReadOnly(); + modified = 1; +} + +int ScriptDrawingSurface::Dispose(const char *address, bool force) { + + // dispose the drawing surface + DrawingSurface_Release(this); + delete this; + return 1; +} + +const char *ScriptDrawingSurface::GetType() { + return "DrawingSurface"; +} + +int ScriptDrawingSurface::Serialize(const char *address, char *buffer, int bufsize) { + StartSerialize(buffer); + SerializeInt(roomBackgroundNumber & 0xFFFF | (roomMaskType << 16)); + SerializeInt(dynamicSpriteNumber); + SerializeInt(dynamicSurfaceNumber); + SerializeInt(currentColour); + SerializeInt(currentColourScript); + SerializeInt(highResCoordinates); + SerializeInt(modified); + SerializeInt(hasAlphaChannel); + SerializeInt(isLinkedBitmapOnly ? 1 : 0); + return EndSerialize(); +} + +void ScriptDrawingSurface::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int room_ds = UnserializeInt(); + roomBackgroundNumber = static_cast(room_ds & 0xFFFF); + roomMaskType = (RoomAreaMask)(room_ds >> 16); + dynamicSpriteNumber = UnserializeInt(); + dynamicSurfaceNumber = UnserializeInt(); + currentColour = UnserializeInt(); + currentColourScript = UnserializeInt(); + highResCoordinates = UnserializeInt(); + modified = UnserializeInt(); + hasAlphaChannel = UnserializeInt(); + isLinkedBitmapOnly = (UnserializeInt() != 0); + ccRegisterUnserializedObject(index, this, this); +} + +ScriptDrawingSurface::ScriptDrawingSurface() +{ + roomBackgroundNumber = -1; + roomMaskType = kRoomAreaNone; + dynamicSpriteNumber = -1; + dynamicSurfaceNumber = -1; + isLinkedBitmapOnly = false; + linkedBitmapOnly = nullptr; + currentColour = play.raw_color; + currentColourScript = 0; + modified = 0; + hasAlphaChannel = 0; + highResCoordinates = 0; + // NOTE: Normally in contemporary games coordinates ratio will always be 1:1. + // But we still support legacy drawing, so have to set this up even for modern games, + // otherwise we'd have to complicate conversion conditions further. + if (game.IsLegacyHiRes() && game.IsDataInNativeCoordinates()) + { + highResCoordinates = 1; + } +} diff --git a/engines/ags/engine/ac/dynobj/scriptdrawingsurface.h b/engines/ags/engine/ac/dynobj/scriptdrawingsurface.h new file mode 100644 index 00000000000..4751be333a9 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdrawingsurface.h @@ -0,0 +1,55 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SCRIPTDRAWINGSURFACE_H +#define __AC_SCRIPTDRAWINGSURFACE_H + +#include "ac/dynobj/cc_agsdynamicobject.h" +#include "game/roomstruct.h" + +namespace AGS { namespace Common { class Bitmap; }} + +struct ScriptDrawingSurface final : AGSCCDynamicObject { + // These numbers and types are used to determine the source of this drawing surface; + // only one of them can be valid for this surface. + int roomBackgroundNumber; + RoomAreaMask roomMaskType; + int dynamicSpriteNumber; + int dynamicSurfaceNumber; + bool isLinkedBitmapOnly; + Common::Bitmap *linkedBitmapOnly; + int currentColour; + int currentColourScript; + int highResCoordinates; + int modified; + int hasAlphaChannel; + //Common::Bitmap* abufBackup; + + int Dispose(const char *address, bool force) override; + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + Common::Bitmap* GetBitmapSurface(); + Common::Bitmap *StartDrawing(); + void PointToGameResolution(int *xcoord, int *ycoord); + void SizeToGameResolution(int *width, int *height); + void SizeToGameResolution(int *adjustValue); + void SizeToDataResolution(int *adjustValue); + void FinishedDrawing(); + void FinishedDrawingReadOnly(); + + ScriptDrawingSurface(); +}; + +#endif // __AC_SCRIPTDRAWINGSURFACE_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/scriptdynamicsprite.cpp b/engines/ags/engine/ac/dynobj/scriptdynamicsprite.cpp new file mode 100644 index 00000000000..6023eb607f9 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdynamicsprite.cpp @@ -0,0 +1,50 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptdynamicsprite.h" +#include "ac/dynamicsprite.h" + +int ScriptDynamicSprite::Dispose(const char *address, bool force) { + // always dispose + if ((slot) && (!force)) + free_dynamic_sprite(slot); + + delete this; + return 1; +} + +const char *ScriptDynamicSprite::GetType() { + return "DynamicSprite"; +} + +int ScriptDynamicSprite::Serialize(const char *address, char *buffer, int bufsize) { + StartSerialize(buffer); + SerializeInt(slot); + return EndSerialize(); +} + +void ScriptDynamicSprite::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + slot = UnserializeInt(); + ccRegisterUnserializedObject(index, this, this); +} + +ScriptDynamicSprite::ScriptDynamicSprite(int theSlot) { + slot = theSlot; + ccRegisterManagedObject(this, this); +} + +ScriptDynamicSprite::ScriptDynamicSprite() { + slot = 0; +} diff --git a/engines/ags/engine/ac/dynobj/scriptdynamicsprite.h b/engines/ags/engine/ac/dynobj/scriptdynamicsprite.h new file mode 100644 index 00000000000..11a53ed68da --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptdynamicsprite.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SCRIPTDYNAMICSPRITE_H +#define __AC_SCRIPTDYNAMICSPRITE_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct ScriptDynamicSprite final : AGSCCDynamicObject { + int slot; + + int Dispose(const char *address, bool force) override; + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + + ScriptDynamicSprite(int slot); + ScriptDynamicSprite(); +}; + +#endif // __AC_SCRIPTDYNAMICSPRITE_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/scriptfile.cpp b/engines/ags/engine/ac/dynobj/scriptfile.cpp new file mode 100644 index 00000000000..d52000c7d51 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptfile.cpp @@ -0,0 +1,105 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptfile.h" +#include "ac/global_file.h" + +// CHECKME: actually NULLs here will be equal to kFile_Open & kFile_Read +const Common::FileOpenMode sc_File::fopenModes[] = + {Common::kFile_Open/*CHECKME, was undefined*/, Common::kFile_Open, Common::kFile_CreateAlways, Common::kFile_Create}; +const Common::FileWorkMode sc_File::fworkModes[] = + {Common::kFile_Read/*CHECKME, was undefined*/, Common::kFile_Read, Common::kFile_Write, Common::kFile_Write}; + +int sc_File::Dispose(const char *address, bool force) { + Close(); + delete this; + return 1; +} + +const char *sc_File::GetType() { + return "File"; +} + +int sc_File::Serialize(const char *address, char *buffer, int bufsize) { + // we cannot serialize an open file, so it will get closed + return 0; +} + +int sc_File::OpenFile(const char *filename, int mode) { + handle = FileOpen(filename, fopenModes[mode], fworkModes[mode]); + if (handle <= 0) + return 0; + return 1; +} + +void sc_File::Close() { + if (handle > 0) { + FileClose(handle); + handle = 0; + } +} + +sc_File::sc_File() { + handle = 0; +} + + +const char* sc_File::GetFieldPtr(const char *address, intptr_t offset) +{ + return address; +} + +void sc_File::Read(const char *address, intptr_t offset, void *dest, int size) +{ +} + +uint8_t sc_File::ReadInt8(const char *address, intptr_t offset) +{ + return 0; +} + +int16_t sc_File::ReadInt16(const char *address, intptr_t offset) +{ + return 0; +} + +int32_t sc_File::ReadInt32(const char *address, intptr_t offset) +{ + return 0; +} + +float sc_File::ReadFloat(const char *address, intptr_t offset) +{ + return 0.0; +} + +void sc_File::Write(const char *address, intptr_t offset, void *src, int size) +{ +} + +void sc_File::WriteInt8(const char *address, intptr_t offset, uint8_t val) +{ +} + +void sc_File::WriteInt16(const char *address, intptr_t offset, int16_t val) +{ +} + +void sc_File::WriteInt32(const char *address, intptr_t offset, int32_t val) +{ +} + +void sc_File::WriteFloat(const char *address, intptr_t offset, float val) +{ +} diff --git a/engines/ags/engine/ac/dynobj/scriptfile.h b/engines/ags/engine/ac/dynobj/scriptfile.h new file mode 100644 index 00000000000..f039e9768e9 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptfile.h @@ -0,0 +1,61 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTFILE_H +#define __AGS_EE_DYNOBJ__SCRIPTFILE_H + +#include "ac/dynobj/cc_dynamicobject.h" +#include "util/file.h" + +using namespace AGS; // FIXME later + +#define scFileRead 1 +#define scFileWrite 2 +#define scFileAppend 3 + +struct sc_File final : ICCDynamicObject { + int32_t handle; + + static const Common::FileOpenMode fopenModes[]; + static const Common::FileWorkMode fworkModes[]; + + int Dispose(const char *address, bool force) override; + + const char *GetType() override; + + int Serialize(const char *address, char *buffer, int bufsize) override; + + int OpenFile(const char *filename, int mode); + void Close(); + + sc_File(); + + // Legacy support for reading and writing object values by their relative offset + const char* GetFieldPtr(const char *address, intptr_t offset) override; + void Read(const char *address, intptr_t offset, void *dest, int size) override; + uint8_t ReadInt8(const char *address, intptr_t offset) override; + int16_t ReadInt16(const char *address, intptr_t offset) override; + int32_t ReadInt32(const char *address, intptr_t offset) override; + float ReadFloat(const char *address, intptr_t offset) override; + void Write(const char *address, intptr_t offset, void *src, int size) override; + void WriteInt8(const char *address, intptr_t offset, uint8_t val) override; + void WriteInt16(const char *address, intptr_t offset, int16_t val) override; + void WriteInt32(const char *address, intptr_t offset, int32_t val) override; + void WriteFloat(const char *address, intptr_t offset, float val) override; +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTFILE_H diff --git a/engines/ags/engine/ac/dynobj/scriptgui.h b/engines/ags/engine/ac/dynobj/scriptgui.h new file mode 100644 index 00000000000..c6651a22c50 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptgui.h @@ -0,0 +1,27 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTGUI_H +#define __AGS_EE_DYNOBJ__SCRIPTGUI_H + +// 64 bit: This struct must be 8 byte long +struct ScriptGUI { + int id; + int __padding; +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTGUI_H diff --git a/engines/ags/engine/ac/dynobj/scripthotspot.h b/engines/ags/engine/ac/dynobj/scripthotspot.h new file mode 100644 index 00000000000..1af83a772ac --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scripthotspot.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTHOTSPOT_H +#define __AGS_EE_DYNOBJ__SCRIPTHOTSPOT_H + +struct ScriptHotspot { + int id; + int reserved; +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTHOTSPOT_H diff --git a/engines/ags/engine/ac/dynobj/scriptinvitem.h b/engines/ags/engine/ac/dynobj/scriptinvitem.h new file mode 100644 index 00000000000..a614a5f5bfd --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptinvitem.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTINVITEM_H +#define __AGS_EE_DYNOBJ__SCRIPTINVITEM_H + +struct ScriptInvItem { + int id; + int reserved; +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTINVITEM_H diff --git a/engines/ags/engine/ac/dynobj/scriptmouse.h b/engines/ags/engine/ac/dynobj/scriptmouse.h new file mode 100644 index 00000000000..d0ee4316eaf --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptmouse.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTMOUSE_H +#define __AGS_EE_DYNOBJ__SCRIPTMOUSE_H + +// The text script's "mouse" struct +struct ScriptMouse { + int x,y; +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTMOUSE_H diff --git a/engines/ags/engine/ac/dynobj/scriptobject.h b/engines/ags/engine/ac/dynobj/scriptobject.h new file mode 100644 index 00000000000..57a9ee98686 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptobject.h @@ -0,0 +1,30 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTOBJECT_H +#define __AGS_EE_DYNOBJ__SCRIPTOBJECT_H + +#include "ac/roomobject.h" + +// 64 bit: Struct size must be 8 byte for scripts to work +struct ScriptObject { + int id; + //RoomObject *obj; + int __padding; +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTOBJECT_H diff --git a/engines/ags/engine/ac/dynobj/scriptoverlay.cpp b/engines/ags/engine/ac/dynobj/scriptoverlay.cpp new file mode 100644 index 00000000000..d640ab8eb6d --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptoverlay.cpp @@ -0,0 +1,83 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptoverlay.h" +#include "ac/common.h" +#include "ac/overlay.h" +#include "ac/runtime_defines.h" +#include "ac/screenoverlay.h" + +int ScriptOverlay::Dispose(const char *address, bool force) +{ + // since the managed object is being deleted, remove the + // reference so it doesn't try and dispose something else + // with that handle later + int overlayIndex = find_overlay_of_type(overlayId); + if (overlayIndex >= 0) + { + screenover[overlayIndex].associatedOverlayHandle = 0; + } + + // if this is being removed voluntarily (ie. pointer out of + // scope) then remove the associateed overlay + // Otherwise, it's a Restre Game or something so don't + if ((!force) && (!isBackgroundSpeech) && (Overlay_GetValid(this))) + { + Remove(); + } + + delete this; + return 1; +} + +const char *ScriptOverlay::GetType() { + return "Overlay"; +} + +int ScriptOverlay::Serialize(const char *address, char *buffer, int bufsize) { + StartSerialize(buffer); + SerializeInt(overlayId); + SerializeInt(borderWidth); + SerializeInt(borderHeight); + SerializeInt(isBackgroundSpeech); + return EndSerialize(); +} + +void ScriptOverlay::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + overlayId = UnserializeInt(); + borderWidth = UnserializeInt(); + borderHeight = UnserializeInt(); + isBackgroundSpeech = UnserializeInt(); + ccRegisterUnserializedObject(index, this, this); +} + +void ScriptOverlay::Remove() +{ + int overlayIndex = find_overlay_of_type(overlayId); + if (overlayIndex < 0) + { + quit("ScriptOverlay::Remove: overlay is not there!"); + } + remove_screen_overlay_index(overlayIndex); + overlayId = -1; +} + + +ScriptOverlay::ScriptOverlay() { + overlayId = -1; + borderWidth = 0; + borderHeight = 0; + isBackgroundSpeech = 0; +} diff --git a/engines/ags/engine/ac/dynobj/scriptoverlay.h b/engines/ags/engine/ac/dynobj/scriptoverlay.h new file mode 100644 index 00000000000..0b027b62bb7 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptoverlay.h @@ -0,0 +1,34 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SCRIPTOVERLAY_H +#define __AC_SCRIPTOVERLAY_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct ScriptOverlay final : AGSCCDynamicObject { + int overlayId; + int borderWidth; + int borderHeight; + int isBackgroundSpeech; + + int Dispose(const char *address, bool force) override; + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + void Remove(); + ScriptOverlay(); +}; + +#endif // __AC_SCRIPTOVERLAY_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/scriptregion.h b/engines/ags/engine/ac/dynobj/scriptregion.h new file mode 100644 index 00000000000..9e2aff4fb39 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptregion.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTREGION_H +#define __AGS_EE_DYNOBJ__SCRIPTREGION_H + +struct ScriptRegion { + int id; + int reserved; +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTREGION_H diff --git a/engines/ags/engine/ac/dynobj/scriptset.cpp b/engines/ags/engine/ac/dynobj/scriptset.cpp new file mode 100644 index 00000000000..472671add1a --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptset.cpp @@ -0,0 +1,50 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include "ac/dynobj/scriptset.h" + +int ScriptSetBase::Dispose(const char *address, bool force) +{ + Clear(); + delete this; + return 1; +} + +const char *ScriptSetBase::GetType() +{ + return "StringSet"; +} + +int ScriptSetBase::Serialize(const char *address, char *buffer, int bufsize) +{ + size_t total_sz = CalcSerializeSize() + sizeof(int32_t) * 2; + if (bufsize < 0 || total_sz > (size_t)bufsize) + { + // buffer not big enough, ask for a bigger one + return -((int)total_sz); + } + StartSerialize(buffer); + SerializeInt(IsSorted()); + SerializeInt(IsCaseSensitive()); + SerializeContainer(); + return EndSerialize(); +} + +void ScriptSetBase::Unserialize(int index, const char *serializedData, int dataSize) +{ + // NOTE: we expect sorted/case flags are read by external reader; + // this is awkward, but I did not find better design solution atm + StartUnserialize(serializedData, dataSize); + UnserializeContainer(serializedData); + ccRegisterUnserializedObject(index, this, this); +} diff --git a/engines/ags/engine/ac/dynobj/scriptset.h b/engines/ags/engine/ac/dynobj/scriptset.h new file mode 100644 index 00000000000..12dc89c914f --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptset.h @@ -0,0 +1,144 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Managed script object wrapping std::set and unordered_set. +// +// TODO: support wrapping non-owned Set, passed by the reference, - +// that would let expose internal engine's sets using same interface. +// TODO: maybe optimize key lookup operations further by not creating a String +// object from const char*. It seems, C++14 standard allows to use convertible +// types as keys; need to research what perfomance impact that would make. +// +//============================================================================= +#ifndef __AC_SCRIPTSET_H +#define __AC_SCRIPTSET_H + +#include +#include +#include +#include "ac/dynobj/cc_agsdynamicobject.h" +#include "util/string.h" +#include "util/string_types.h" + +using namespace AGS::Common; + +class ScriptSetBase : public AGSCCDynamicObject +{ +public: + int Dispose(const char *address, bool force) override; + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + + virtual bool IsCaseSensitive() const = 0; + virtual bool IsSorted() const = 0; + + virtual bool Add(const char *item) = 0; + virtual void Clear() = 0; + virtual bool Contains(const char *item) const = 0; + virtual bool Remove(const char *item) = 0; + virtual int GetItemCount() const = 0; + virtual void GetItems(std::vector &buf) const = 0; + +private: + virtual size_t CalcSerializeSize() = 0; + virtual void SerializeContainer() = 0; + virtual void UnserializeContainer(const char *serializedData) = 0; +}; + +template +class ScriptSetImpl final : public ScriptSetBase +{ +public: + typedef typename TSet::const_iterator ConstIterator; + + ScriptSetImpl() = default; + + bool IsCaseSensitive() const override { return is_casesensitive; } + bool IsSorted() const override { return is_sorted; } + + bool Add(const char *item) override + { + if (!item) return false; + size_t len = strlen(item); + return TryAddItem(item, len); + } + void Clear() override + { + for (auto it = _set.begin(); it != _set.end(); ++it) + DeleteItem(it); + _set.clear(); + } + bool Contains(const char *item) const override { return _set.count(String::Wrapper(item)) != 0; } + bool Remove(const char *item) override + { + auto it = _set.find(String::Wrapper(item)); + if (it == _set.end()) return false; + DeleteItem(it); + _set.erase(it); + return true; + } + int GetItemCount() const override { return _set.size(); } + void GetItems(std::vector &buf) const override + { + for (auto it = _set.begin(); it != _set.end(); ++it) + buf.push_back(it->GetCStr()); + } + +private: + bool TryAddItem(const char *item, size_t len) + { + return _set.insert(String(item, len)).second; + } + void DeleteItem(ConstIterator it) { /* do nothing */ } + + size_t CalcSerializeSize() override + { + size_t total_sz = sizeof(int32_t); + for (auto it = _set.begin(); it != _set.end(); ++it) + total_sz += sizeof(int32_t) + it->GetLength(); + return total_sz; + } + + void SerializeContainer() override + { + SerializeInt((int)_set.size()); + for (auto it = _set.begin(); it != _set.end(); ++it) + { + SerializeInt((int)it->GetLength()); + memcpy(&serbuffer[bytesSoFar], it->GetCStr(), it->GetLength()); + bytesSoFar += it->GetLength(); + } + } + + void UnserializeContainer(const char *serializedData) override + { + size_t item_count = (size_t)UnserializeInt(); + for (size_t i = 0; i < item_count; ++i) + { + size_t len = UnserializeInt(); + TryAddItem(&serializedData[bytesSoFar], len); + bytesSoFar += len; + } + } + + TSet _set; +}; + +typedef ScriptSetImpl< std::set, true, true > ScriptSet; +typedef ScriptSetImpl< std::set, true, false > ScriptSetCI; +typedef ScriptSetImpl< std::unordered_set, false, true > ScriptHashSet; +typedef ScriptSetImpl< std::unordered_set, false, false > ScriptHashSetCI; + +#endif // __AC_SCRIPTSET_H diff --git a/engines/ags/engine/ac/dynobj/scriptstring.cpp b/engines/ags/engine/ac/dynobj/scriptstring.cpp new file mode 100644 index 00000000000..d130d63e5b4 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptstring.cpp @@ -0,0 +1,66 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptstring.h" +#include "ac/string.h" +#include +#include + +DynObjectRef ScriptString::CreateString(const char *fromText) { + return CreateNewScriptStringObj(fromText); +} + +int ScriptString::Dispose(const char *address, bool force) { + // always dispose + if (text) { + free(text); + text = nullptr; + } + delete this; + return 1; +} + +const char *ScriptString::GetType() { + return "String"; +} + +int ScriptString::Serialize(const char *address, char *buffer, int bufsize) { + StartSerialize(buffer); + + auto toSerialize = text ? text : ""; + + auto len = strlen(toSerialize); + SerializeInt(len); + strcpy(&serbuffer[bytesSoFar], toSerialize); + bytesSoFar += len + 1; + + return EndSerialize(); +} + +void ScriptString::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + int textsize = UnserializeInt(); + text = (char*)malloc(textsize + 1); + strcpy(text, &serializedData[bytesSoFar]); + ccRegisterUnserializedObject(index, text, this); +} + +ScriptString::ScriptString() { + text = nullptr; +} + +ScriptString::ScriptString(const char *fromText) { + text = (char*)malloc(strlen(fromText) + 1); + strcpy(text, fromText); +} diff --git a/engines/ags/engine/ac/dynobj/scriptstring.h b/engines/ags/engine/ac/dynobj/scriptstring.h new file mode 100644 index 00000000000..916d48998d5 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptstring.h @@ -0,0 +1,34 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SCRIPTSTRING_H +#define __AC_SCRIPTSTRING_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct ScriptString final : AGSCCDynamicObject, ICCStringClass { + char *text; + + int Dispose(const char *address, bool force) override; + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + + DynObjectRef CreateString(const char *fromText) override; + + ScriptString(); + ScriptString(const char *fromText); +}; + +#endif // __AC_SCRIPTSTRING_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/scriptsystem.h b/engines/ags/engine/ac/dynobj/scriptsystem.h new file mode 100644 index 00000000000..e7c29b3d7a3 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptsystem.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTSYSTEM_H +#define __AGS_EE_DYNOBJ__SCRIPTSYSTEM_H + +// The text script's "system" struct +struct ScriptSystem { + int width,height; + int coldepth; + int os; + int windowed; + int vsync; + int viewport_width, viewport_height; + char aci_version[10]; // FIXME this when possible, version format is different now + int reserved[5]; // so that future scripts don't overwrite data +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTSYSTEM_H diff --git a/engines/ags/engine/ac/dynobj/scriptuserobject.cpp b/engines/ags/engine/ac/dynobj/scriptuserobject.cpp new file mode 100644 index 00000000000..4b70941a13d --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptuserobject.cpp @@ -0,0 +1,144 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "scriptuserobject.h" + +// return the type name of the object +const char *ScriptUserObject::GetType() +{ + return "UserObject"; +} + +ScriptUserObject::ScriptUserObject() + : _size(0) + , _data(nullptr) +{ +} + +ScriptUserObject::~ScriptUserObject() +{ + delete [] _data; +} + +/* static */ ScriptUserObject *ScriptUserObject::CreateManaged(size_t size) +{ + ScriptUserObject *suo = new ScriptUserObject(); + suo->Create(nullptr, size); + ccRegisterManagedObject(suo, suo); + return suo; +} + +void ScriptUserObject::Create(const char *data, size_t size) +{ + delete [] _data; + _data = nullptr; + + _size = size; + if (_size > 0) + { + _data = new char[size]; + if (data) + memcpy(_data, data, _size); + else + memset(_data, 0, _size); + } +} + +int ScriptUserObject::Dispose(const char *address, bool force) +{ + delete this; + return 1; +} + +int ScriptUserObject::Serialize(const char *address, char *buffer, int bufsize) +{ + if (_size > bufsize) + // buffer not big enough, ask for a bigger one + return -_size; + + memcpy(buffer, _data, _size); + return _size; +} + +void ScriptUserObject::Unserialize(int index, const char *serializedData, int dataSize) +{ + Create(serializedData, dataSize); + ccRegisterUnserializedObject(index, this, this); +} + +const char* ScriptUserObject::GetFieldPtr(const char *address, intptr_t offset) +{ + return _data + offset; +} + +void ScriptUserObject::Read(const char *address, intptr_t offset, void *dest, int size) +{ + memcpy(dest, _data + offset, size); +} + +uint8_t ScriptUserObject::ReadInt8(const char *address, intptr_t offset) +{ + return *(uint8_t*)(_data + offset); +} + +int16_t ScriptUserObject::ReadInt16(const char *address, intptr_t offset) +{ + return *(int16_t*)(_data + offset); +} + +int32_t ScriptUserObject::ReadInt32(const char *address, intptr_t offset) +{ + return *(int32_t*)(_data + offset); +} + +float ScriptUserObject::ReadFloat(const char *address, intptr_t offset) +{ + return *(float*)(_data + offset); +} + +void ScriptUserObject::Write(const char *address, intptr_t offset, void *src, int size) +{ + memcpy((void*)(_data + offset), src, size); +} + +void ScriptUserObject::WriteInt8(const char *address, intptr_t offset, uint8_t val) +{ + *(uint8_t*)(_data + offset) = val; +} + +void ScriptUserObject::WriteInt16(const char *address, intptr_t offset, int16_t val) +{ + *(int16_t*)(_data + offset) = val; +} + +void ScriptUserObject::WriteInt32(const char *address, intptr_t offset, int32_t val) +{ + *(int32_t*)(_data + offset) = val; +} + +void ScriptUserObject::WriteFloat(const char *address, intptr_t offset, float val) +{ + *(float*)(_data + offset) = val; +} + + +// Allocates managed struct containing two ints: X and Y +ScriptUserObject *ScriptStructHelpers::CreatePoint(int x, int y) +{ + ScriptUserObject *suo = ScriptUserObject::CreateManaged(sizeof(int32_t) * 2); + suo->WriteInt32((const char*)suo, 0, x); + suo->WriteInt32((const char*)suo, sizeof(int32_t), y); + return suo; +} diff --git a/engines/ags/engine/ac/dynobj/scriptuserobject.h b/engines/ags/engine/ac/dynobj/scriptuserobject.h new file mode 100644 index 00000000000..931bdaa23c5 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptuserobject.h @@ -0,0 +1,75 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Managed object, which size and contents are defined by user script +// +//============================================================================= +#ifndef __AGS_EE_DYNOBJ__SCRIPTUSERSTRUCT_H +#define __AGS_EE_DYNOBJ__SCRIPTUSERSTRUCT_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct ScriptUserObject final : ICCDynamicObject +{ +public: + ScriptUserObject(); + +protected: + virtual ~ScriptUserObject(); + +public: + static ScriptUserObject *CreateManaged(size_t size); + void Create(const char *data, size_t size); + + // return the type name of the object + const char *GetType() override; + int Dispose(const char *address, bool force) override; + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + int Serialize(const char *address, char *buffer, int bufsize) override; + virtual void Unserialize(int index, const char *serializedData, int dataSize); + + // Support for reading and writing object values by their relative offset + const char* GetFieldPtr(const char *address, intptr_t offset) override; + void Read(const char *address, intptr_t offset, void *dest, int size) override; + uint8_t ReadInt8(const char *address, intptr_t offset) override; + int16_t ReadInt16(const char *address, intptr_t offset) override; + int32_t ReadInt32(const char *address, intptr_t offset) override; + float ReadFloat(const char *address, intptr_t offset) override; + void Write(const char *address, intptr_t offset, void *src, int size) override; + void WriteInt8(const char *address, intptr_t offset, uint8_t val) override; + void WriteInt16(const char *address, intptr_t offset, int16_t val) override; + void WriteInt32(const char *address, intptr_t offset, int32_t val) override; + void WriteFloat(const char *address, intptr_t offset, float val) override; + +private: + // NOTE: we use signed int for Size at the moment, because the managed + // object interface's Serialize() function requires the object to return + // negative value of size in case the provided buffer was not large + // enough. Since this interface is also a part of Plugin API, we would + // need more significant change to program before we could use different + // approach. + int32_t _size; + char *_data; +}; + + +// Helper functions for setting up custom managed structs based on ScriptUserObject. +namespace ScriptStructHelpers +{ + // Creates a managed Point object, represented as a pair of X and Y coordinates. + ScriptUserObject *CreatePoint(int x, int y); +}; + +#endif // __AGS_EE_DYNOBJ__SCRIPTUSERSTRUCT_H diff --git a/engines/ags/engine/ac/dynobj/scriptviewframe.cpp b/engines/ags/engine/ac/dynobj/scriptviewframe.cpp new file mode 100644 index 00000000000..803269a46f4 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptviewframe.cpp @@ -0,0 +1,53 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptviewframe.h" + +int ScriptViewFrame::Dispose(const char *address, bool force) { + // always dispose a ViewFrame + delete this; + return 1; +} + +const char *ScriptViewFrame::GetType() { + return "ViewFrame"; +} + +int ScriptViewFrame::Serialize(const char *address, char *buffer, int bufsize) { + StartSerialize(buffer); + SerializeInt(view); + SerializeInt(loop); + SerializeInt(frame); + return EndSerialize(); +} + +void ScriptViewFrame::Unserialize(int index, const char *serializedData, int dataSize) { + StartUnserialize(serializedData, dataSize); + view = UnserializeInt(); + loop = UnserializeInt(); + frame = UnserializeInt(); + ccRegisterUnserializedObject(index, this, this); +} + +ScriptViewFrame::ScriptViewFrame(int p_view, int p_loop, int p_frame) { + view = p_view; + loop = p_loop; + frame = p_frame; +} + +ScriptViewFrame::ScriptViewFrame() { + view = -1; + loop = -1; + frame = -1; +} diff --git a/engines/ags/engine/ac/dynobj/scriptviewframe.h b/engines/ags/engine/ac/dynobj/scriptviewframe.h new file mode 100644 index 00000000000..1abb47e6d9d --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptviewframe.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SCRIPTVIEWFRAME_H +#define __AC_SCRIPTVIEWFRAME_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +struct ScriptViewFrame final : AGSCCDynamicObject { + int view, loop, frame; + + int Dispose(const char *address, bool force) override; + const char *GetType() override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + + ScriptViewFrame(int p_view, int p_loop, int p_frame); + ScriptViewFrame(); +}; + +#endif // __AC_SCRIPTVIEWFRAME_H \ No newline at end of file diff --git a/engines/ags/engine/ac/dynobj/scriptviewport.cpp b/engines/ags/engine/ac/dynobj/scriptviewport.cpp new file mode 100644 index 00000000000..0bd8a0af4f9 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptviewport.cpp @@ -0,0 +1,64 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptviewport.h" +#include "ac/gamestate.h" +#include "util/bbop.h" + +using namespace AGS::Common; + +ScriptViewport::ScriptViewport(int id) : _id(id) {} + +const char *ScriptViewport::GetType() +{ + return "Viewport2"; +} + +int ScriptViewport::Dispose(const char *address, bool force) +{ + // Note that ScriptViewport is a reference to actual Viewport object, + // and this deletes the reference, while viewport may remain in GameState. + delete this; + return 1; +} + +int ScriptViewport::Serialize(const char *address, char *buffer, int bufsize) +{ + StartSerialize(buffer); + SerializeInt(_id); + return EndSerialize(); +} + +void ScriptViewport::Unserialize(int index, const char *serializedData, int dataSize) +{ + StartUnserialize(serializedData, dataSize); + _id = UnserializeInt(); + ccRegisterUnserializedObject(index, this, this); +} + +ScriptViewport *Viewport_Unserialize(int handle, const char *serializedData, int dataSize) +{ + // The way it works now, we must not create a new script object, + // but acquire one from the GameState, which keeps the first reference. + // This is essential because GameState should be able to invalidate any + // script references when Viewport gets removed. + const int id = BBOp::Int32FromLE(*((int*)serializedData)); + if (id >= 0) + { + auto scview = play.RegisterRoomViewport(id, handle); + if (scview) + return scview; + } + return new ScriptViewport(-1); // make invalid reference +} diff --git a/engines/ags/engine/ac/dynobj/scriptviewport.h b/engines/ags/engine/ac/dynobj/scriptviewport.h new file mode 100644 index 00000000000..d742dfe12d0 --- /dev/null +++ b/engines/ags/engine/ac/dynobj/scriptviewport.h @@ -0,0 +1,43 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SCRIPTVIEWPORT_H +#define __AC_SCRIPTVIEWPORT_H + +#include "ac/dynobj/cc_agsdynamicobject.h" + +// ScriptViewport keeps a reference to actual room Viewport in script. +struct ScriptViewport final : AGSCCDynamicObject +{ +public: + ScriptViewport(int id); + // Get viewport index; negative means the viewport was deleted + int GetID() const { return _id; } + void SetID(int id) { _id = id; } + // Reset viewport index to indicate that this reference is no longer valid + void Invalidate() { _id = -1; } + + const char *GetType() override; + int Dispose(const char *address, bool force) override; + int Serialize(const char *address, char *buffer, int bufsize) override; + void Unserialize(int index, const char *serializedData, int dataSize) override; + +private: + int _id = -1; // index of viewport in the game state array +}; + +// Unserialize viewport from the memory stream +ScriptViewport *Viewport_Unserialize(int handle, const char *serializedData, int dataSize); + +#endif // __AC_SCRIPTVIEWPORT_H diff --git a/engines/ags/engine/ac/event.cpp b/engines/ags/engine/ac/event.cpp new file mode 100644 index 00000000000..dc3bf6f6f84 --- /dev/null +++ b/engines/ags/engine/ac/event.cpp @@ -0,0 +1,426 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "event.h" +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_game.h" +#include "ac/global_room.h" +#include "ac/global_screen.h" +#include "ac/gui.h" +#include "ac/roomstatus.h" +#include "ac/screen.h" +#include "script/cc_error.h" +#include "platform/base/agsplatformdriver.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "script/script.h" +#include "gfx/bitmap.h" +#include "gfx/ddb.h" +#include "gfx/graphicsdriver.h" +#include "media/audio/audio_system.h" +#include "ac/timer.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern RoomStruct thisroom; +extern RoomStatus*croom; +extern int displayed_room; +extern GameState play; +extern color palette[256]; +extern IGraphicsDriver *gfxDriver; +extern AGSPlatformDriver *platform; +extern color old_palette[256]; + +int in_enters_screen=0,done_es_error = 0; +int in_leaves_screen = -1; + +EventHappened event[MAXEVENTS+1]; +int numevents=0; + +const char*evblockbasename; +int evblocknum; + +int inside_processevent=0; +int eventClaimed = EVENT_NONE; + +const char*tsnames[4]={nullptr, REP_EXEC_NAME, "on_key_press","on_mouse_click"}; + + +int run_claimable_event(const char *tsname, bool includeRoom, int numParams, const RuntimeScriptValue *params, bool *eventWasClaimed) { + *eventWasClaimed = true; + // Run the room script function, and if it is not claimed, + // then run the main one + // We need to remember the eventClaimed variable's state, in case + // this is a nested event + int eventClaimedOldValue = eventClaimed; + eventClaimed = EVENT_INPROGRESS; + int toret; + + if (includeRoom && roominst) { + toret = RunScriptFunctionIfExists(roominst, tsname, numParams, params); + + if (eventClaimed == EVENT_CLAIMED) { + eventClaimed = eventClaimedOldValue; + return toret; + } + } + + // run script modules + for (int kk = 0; kk < numScriptModules; kk++) { + toret = RunScriptFunctionIfExists(moduleInst[kk], tsname, numParams, params); + + if (eventClaimed == EVENT_CLAIMED) { + eventClaimed = eventClaimedOldValue; + return toret; + } + } + + eventClaimed = eventClaimedOldValue; + *eventWasClaimed = false; + return 0; +} + +// runs the global script on_event function +void run_on_event (int evtype, RuntimeScriptValue &wparam) +{ + QueueScriptFunction(kScInstGame, "on_event", 2, RuntimeScriptValue().SetInt32(evtype), wparam); +} + +void run_room_event(int id) { + evblockbasename="room"; + + if (thisroom.EventHandlers != nullptr) + { + run_interaction_script(thisroom.EventHandlers.get(), id); + } + else + { + run_interaction_event (&croom->intrRoom, id); + } +} + +void run_event_block_inv(int invNum, int event) { + evblockbasename="inventory%d"; + if (loaded_game_file_version > kGameVersion_272) + { + run_interaction_script(game.invScripts[invNum].get(), event); + } + else + { + run_interaction_event(game.intrInv[invNum].get(), event); + } + +} + +// event list functions +void setevent(int evtyp,int ev1,int ev2,int ev3) { + event[numevents].type=evtyp; + event[numevents].data1=ev1; + event[numevents].data2=ev2; + event[numevents].data3=ev3; + event[numevents].player=game.playercharacter; + numevents++; + if (numevents>=MAXEVENTS) quit("too many events posted"); +} + +// TODO: this is kind of a hack, which forces event to be processed even if +// it was fired from insides of other event processing. +// The proper solution would be to do the event processing overhaul in AGS. +void force_event(int evtyp,int ev1,int ev2,int ev3) +{ + if (inside_processevent) + runevent_now(evtyp, ev1, ev2, ev3); + else + setevent(evtyp, ev1, ev2, ev3); +} + +void process_event(EventHappened*evp) { + RuntimeScriptValue rval_null; + if (evp->type==EV_TEXTSCRIPT) { + ccError=0; + if (evp->data2 > -1000) { + QueueScriptFunction(kScInstGame, tsnames[evp->data1], 1, RuntimeScriptValue().SetInt32(evp->data2)); + } + else { + QueueScriptFunction(kScInstGame, tsnames[evp->data1]); + } + } + else if (evp->type==EV_NEWROOM) { + NewRoom(evp->data1); + } + else if (evp->type==EV_RUNEVBLOCK) { + Interaction*evpt=nullptr; + PInteractionScripts scriptPtr = nullptr; + const char *oldbasename = evblockbasename; + int oldblocknum = evblocknum; + + if (evp->data1==EVB_HOTSPOT) { + + if (thisroom.Hotspots[evp->data2].EventHandlers != nullptr) + scriptPtr = thisroom.Hotspots[evp->data2].EventHandlers; + else + evpt=&croom->intrHotspot[evp->data2]; + + evblockbasename="hotspot%d"; + evblocknum=evp->data2; + //Debug::Printf("Running hotspot interaction for hotspot %d, event %d", evp->data2, evp->data3); + } + else if (evp->data1==EVB_ROOM) { + + if (thisroom.EventHandlers != nullptr) + scriptPtr = thisroom.EventHandlers; + else + evpt=&croom->intrRoom; + + evblockbasename="room"; + if (evp->data3 == 5) { + in_enters_screen ++; + run_on_event (GE_ENTER_ROOM, RuntimeScriptValue().SetInt32(displayed_room)); + + } + //Debug::Printf("Running room interaction, event %d", evp->data3); + } + + if (scriptPtr != nullptr) + { + run_interaction_script(scriptPtr.get(), evp->data3); + } + else if (evpt != nullptr) + { + run_interaction_event(evpt,evp->data3); + } + else + quit("process_event: RunEvBlock: unknown evb type"); + + evblockbasename = oldbasename; + evblocknum = oldblocknum; + + if ((evp->data3 == 5) && (evp->data1 == EVB_ROOM)) + in_enters_screen --; + } + else if (evp->type==EV_FADEIN) { + // if they change the transition type before the fadein, make + // sure the screen doesn't freeze up + play.screen_is_faded_out = 0; + + // determine the transition style + int theTransition = play.fade_effect; + + if (play.next_screen_transition >= 0) { + // a one-off transition was selected, so use it + theTransition = play.next_screen_transition; + play.next_screen_transition = -1; + } + + if (pl_run_plugin_hooks(AGSE_TRANSITIONIN, 0)) + return; + + if (play.fast_forward) + return; + + const bool ignore_transition = (play.screen_tint > 0); + if (((theTransition == FADE_CROSSFADE) || (theTransition == FADE_DISSOLVE)) && + (saved_viewport_bitmap == nullptr) && !ignore_transition) + { + // transition type was not crossfade/dissolve when the screen faded out, + // but it is now when the screen fades in (Eg. a save game was restored + // with a different setting). Therefore just fade normally. + my_fade_out(5); + theTransition = FADE_NORMAL; + } + + // TODO: use normal coordinates instead of "native_size" and multiply_up_*? + const Size &data_res = game.GetDataRes(); + const Rect &viewport = play.GetMainViewport(); + + if ((theTransition == FADE_INSTANT) || ignore_transition) + set_palette_range(palette, 0, 255, 0); + else if (theTransition == FADE_NORMAL) + { + my_fade_in(palette,5); + } + else if (theTransition == FADE_BOXOUT) + { + if (!gfxDriver->UsesMemoryBackBuffer()) + { + gfxDriver->BoxOutEffect(false, get_fixed_pixel_size(16), 1000 / GetGameSpeed()); + } + else + { + // First of all we render the game once again and save backbuffer from further editing. + // We put temporary bitmap as a new backbuffer for the transition period, and + // will be drawing saved image of the game over to that backbuffer, simulating "box-out". + set_palette_range(palette, 0, 255, 0); + construct_game_scene(true); + construct_game_screen_overlay(false); + gfxDriver->RenderToBackBuffer(); + Bitmap *saved_backbuf = gfxDriver->GetMemoryBackBuffer(); + Bitmap *temp_scr = new Bitmap(saved_backbuf->GetWidth(), saved_backbuf->GetHeight(), saved_backbuf->GetColorDepth()); + gfxDriver->SetMemoryBackBuffer(temp_scr); + temp_scr->Clear(); + render_to_screen(); + + const int speed = get_fixed_pixel_size(16); + const int yspeed = viewport.GetHeight() / (viewport.GetWidth() / speed); + int boxwid = speed, boxhit = yspeed; + while (boxwid < temp_scr->GetWidth()) + { + boxwid += speed; + boxhit += yspeed; + boxwid = Math::Clamp(boxwid, 0, viewport.GetWidth()); + boxhit = Math::Clamp(boxhit, 0, viewport.GetHeight()); + int lxp = viewport.GetWidth() / 2 - boxwid / 2; + int lyp = viewport.GetHeight() / 2 - boxhit / 2; + gfxDriver->Vsync(); + temp_scr->Blit(saved_backbuf, lxp, lyp, lxp, lyp, + boxwid, boxhit); + render_to_screen(); + update_polled_mp3(); + WaitForNextFrame(); + } + gfxDriver->SetMemoryBackBuffer(saved_backbuf); + } + play.screen_is_faded_out = 0; + } + else if (theTransition == FADE_CROSSFADE) + { + if (game.color_depth == 1) + quit("!Cannot use crossfade screen transition in 256-colour games"); + + IDriverDependantBitmap *ddb = prepare_screen_for_transition_in(); + + int transparency = 254; + + while (transparency > 0) { + // do the crossfade + ddb->SetTransparency(transparency); + invalidate_screen(); + construct_game_scene(true); + construct_game_screen_overlay(false); + + if (transparency > 16) + { + // on last frame of fade (where transparency < 16), don't + // draw the old screen on top + gfxDriver->DrawSprite(0, 0, ddb); + } + render_to_screen(); + update_polled_stuff_if_runtime(); + WaitForNextFrame(); + transparency -= 16; + } + saved_viewport_bitmap->Release(); + + delete saved_viewport_bitmap; + saved_viewport_bitmap = nullptr; + set_palette_range(palette, 0, 255, 0); + gfxDriver->DestroyDDB(ddb); + } + else if (theTransition == FADE_DISSOLVE) { + int pattern[16]={0,4,14,9,5,11,2,8,10,3,12,7,15,6,13,1}; + int aa,bb,cc; + color interpal[256]; + + IDriverDependantBitmap *ddb = prepare_screen_for_transition_in(); + for (aa=0;aa<16;aa++) { + // merge the palette while dithering + if (game.color_depth == 1) + { + fade_interpolate(old_palette,palette,interpal,aa*4,0,255); + set_palette_range(interpal, 0, 255, 0); + } + // do the dissolving + int maskCol = saved_viewport_bitmap->GetMaskColor(); + for (bb=0;bbPutPixel(bb+pattern[aa]/4, cc+pattern[aa]%4, maskCol); + } + } + gfxDriver->UpdateDDBFromBitmap(ddb, saved_viewport_bitmap, false); + construct_game_scene(true); + construct_game_screen_overlay(false); + gfxDriver->DrawSprite(0, 0, ddb); + render_to_screen(); + update_polled_stuff_if_runtime(); + WaitForNextFrame(); + } + + delete saved_viewport_bitmap; + saved_viewport_bitmap = nullptr; + set_palette_range(palette, 0, 255, 0); + gfxDriver->DestroyDDB(ddb); + } + + } + else if (evp->type==EV_IFACECLICK) + process_interface_click(evp->data1, evp->data2, evp->data3); + else quit("process_event: unknown event to process"); +} + + +void runevent_now (int evtyp, int ev1, int ev2, int ev3) { + EventHappened evh; + evh.type = evtyp; + evh.data1 = ev1; + evh.data2 = ev2; + evh.data3 = ev3; + evh.player = game.playercharacter; + process_event(&evh); +} + +void processallevents(int numev,EventHappened*evlist) { + int dd; + + if (inside_processevent) + return; + + // make a copy of the events - if processing an event includes + // a blocking function it will continue to the next game loop + // and wipe out the event pointer we were passed + EventHappened copyOfList[MAXEVENTS]; + memcpy(©OfList[0], &evlist[0], sizeof(EventHappened) * numev); + + int room_was = play.room_changes; + + inside_processevent++; + + for (dd=0;dd scFileAppend)) + quit("!OpenFile: invalid file mode"); + + sc_File *scf = new sc_File(); + if (scf->OpenFile(fnmm, mode) == 0) { + delete scf; + return nullptr; + } + ccRegisterManagedObject(scf, scf); + return scf; +} + +void File_Close(sc_File *fil) { + fil->Close(); +} + +void File_WriteString(sc_File *fil, const char *towrite) { + FileWrite(fil->handle, towrite); +} + +void File_WriteInt(sc_File *fil, int towrite) { + FileWriteInt(fil->handle, towrite); +} + +void File_WriteRawChar(sc_File *fil, int towrite) { + FileWriteRawChar(fil->handle, towrite); +} + +void File_WriteRawLine(sc_File *fil, const char *towrite) { + FileWriteRawLine(fil->handle, towrite); +} + +void File_ReadRawLine(sc_File *fil, char* buffer) { + Stream *in = get_valid_file_stream_from_handle(fil->handle, "File.ReadRawLine"); + check_strlen(buffer); + int i = 0; + while (i < MAXSTRLEN - 1) { + buffer[i] = in->ReadInt8(); + if (buffer[i] == 13) { + // CR -- skip LF and abort + in->ReadInt8(); + break; + } + if (buffer[i] == 10) // LF only -- abort + break; + if (in->EOS()) // EOF -- abort + break; + i++; + } + buffer[i] = 0; +} + +const char* File_ReadRawLineBack(sc_File *fil) { + char readbuffer[MAX_MAXSTRLEN + 1]; + File_ReadRawLine(fil, readbuffer); + return CreateNewScriptString(readbuffer); +} + +void File_ReadString(sc_File *fil, char *toread) { + FileRead(fil->handle, toread); +} + +const char* File_ReadStringBack(sc_File *fil) { + Stream *in = get_valid_file_stream_from_handle(fil->handle, "File.ReadStringBack"); + if (in->EOS()) { + return CreateNewScriptString(""); + } + + int lle = in->ReadInt32(); + if ((lle >= 20000) || (lle < 1)) + quit("!File.ReadStringBack: file was not written by WriteString"); + + char *retVal = (char*)malloc(lle); + in->Read(retVal, lle); + + return CreateNewScriptString(retVal, false); +} + +int File_ReadInt(sc_File *fil) { + return FileReadInt(fil->handle); +} + +int File_ReadRawChar(sc_File *fil) { + return FileReadRawChar(fil->handle); +} + +int File_ReadRawInt(sc_File *fil) { + return FileReadRawInt(fil->handle); +} + +int File_Seek(sc_File *fil, int offset, int origin) +{ + Stream *in = get_valid_file_stream_from_handle(fil->handle, "File.Seek"); + if (!in->Seek(offset, (StreamSeek)origin)) { return -1; } + return in->GetPosition(); +} + +int File_GetEOF(sc_File *fil) { + if (fil->handle <= 0) + return 1; + return FileIsEOF(fil->handle); +} + +int File_GetError(sc_File *fil) { + if (fil->handle <= 0) + return 1; + return FileIsError(fil->handle); +} + +int File_GetPosition(sc_File *fil) +{ + if (fil->handle <= 0) + return -1; + Stream *stream = get_valid_file_stream_from_handle(fil->handle, "File.Position"); + // TODO: a problem is that AGS script does not support unsigned or long int + return (int)stream->GetPosition(); +} + +//============================================================================= + + +const String GameInstallRootToken = "$INSTALLDIR$"; +const String UserSavedgamesRootToken = "$MYDOCS$"; +const String GameSavedgamesDirToken = "$SAVEGAMEDIR$"; +const String GameDataDirToken = "$APPDATADIR$"; + +void FixupFilename(char *filename) +{ + const char *illegal = platform->GetIllegalFileChars(); + for (char *name_ptr = filename; *name_ptr; ++name_ptr) + { + if (*name_ptr < ' ') + { + *name_ptr = '_'; + } + else + { + for (const char *ch_ptr = illegal; *ch_ptr; ++ch_ptr) + if (*name_ptr == *ch_ptr) + *name_ptr = '_'; + } + } +} + +// Tests if there is a special path token in the beginning of the given path; +// if there is and there is no slash between token and the rest of the string, +// then assigns new string that has such slash. +// Returns TRUE if the new string was created, and FALSE if the path was good. +bool FixSlashAfterToken(const String &path, const String &token, String &new_path) +{ + if (path.CompareLeft(token) == 0 && path.GetLength() > token.GetLength() && + path[token.GetLength()] != '/') + { + new_path = String::FromFormat("%s/%s", token.GetCStr(), path.Mid(token.GetLength()).GetCStr()); + return true; + } + return false; +} + +String FixSlashAfterToken(const String &path) +{ + String fixed_path = path; + Path::FixupPath(fixed_path); + if (FixSlashAfterToken(fixed_path, GameInstallRootToken, fixed_path) || + FixSlashAfterToken(fixed_path, UserSavedgamesRootToken, fixed_path) || + FixSlashAfterToken(fixed_path, GameSavedgamesDirToken, fixed_path) || + FixSlashAfterToken(fixed_path, GameDataDirToken, fixed_path)) + return fixed_path; + return path; +} + +String MakeSpecialSubDir(const String &sp_dir) +{ + if (is_relative_filename(sp_dir)) + return sp_dir; + String full_path = sp_dir; + if (full_path.GetLast() != '/' && full_path.GetLast() != '\\') + full_path.AppendChar('/'); + full_path.Append(game.saveGameFolderName); + Directory::CreateDirectory(full_path); + return full_path; +} + +String MakeAppDataPath() +{ + String app_data_path = usetup.shared_data_dir; + if (app_data_path.IsEmpty()) + app_data_path = MakeSpecialSubDir(PathOrCurDir(platform->GetAllUsersDataDirectory())); + Directory::CreateDirectory(app_data_path); + app_data_path.AppendChar('/'); + return app_data_path; +} + +bool ResolveScriptPath(const String &orig_sc_path, bool read_only, ResolvedPath &rp) +{ + rp = ResolvedPath(); + + bool is_absolute = !is_relative_filename(orig_sc_path); + if (is_absolute && !read_only) + { + debug_script_warn("Attempt to access file '%s' denied (cannot write to absolute path)", orig_sc_path.GetCStr()); + return false; + } + + if (is_absolute) + { + rp.FullPath = orig_sc_path; + return true; + } + + String sc_path = FixSlashAfterToken(orig_sc_path); + String parent_dir; + String child_path; + String alt_path; + if (sc_path.CompareLeft(GameInstallRootToken, GameInstallRootToken.GetLength()) == 0) + { + if (!read_only) + { + debug_script_warn("Attempt to access file '%s' denied (cannot write to game installation directory)", + sc_path.GetCStr()); + return false; + } + parent_dir = get_install_dir(); + parent_dir.AppendChar('/'); + child_path = sc_path.Mid(GameInstallRootToken.GetLength()); + } + else if (sc_path.CompareLeft(GameSavedgamesDirToken, GameSavedgamesDirToken.GetLength()) == 0) + { + parent_dir = get_save_game_directory(); + child_path = sc_path.Mid(GameSavedgamesDirToken.GetLength()); + } + else if (sc_path.CompareLeft(GameDataDirToken, GameDataDirToken.GetLength()) == 0) + { + parent_dir = MakeAppDataPath(); + child_path = sc_path.Mid(GameDataDirToken.GetLength()); + } + else + { + child_path = sc_path; + + // For games which were made without having safe paths in mind, + // provide two paths: a path to the local directory and a path to + // AppData directory. + // This is done in case game writes a file by local path, and would + // like to read it back later. Since AppData path has higher priority, + // game will first check the AppData location and find a previously + // written file. + // If no file was written yet, but game is trying to read a pre-created + // file in the installation directory, then such file will be found + // following the 'alt_path'. + parent_dir = MakeAppDataPath(); + // Set alternate non-remapped "unsafe" path for read-only operations + if (read_only) + alt_path = String::FromFormat("%s/%s", get_install_dir().GetCStr(), sc_path.GetCStr()); + + // For games made in the safe-path-aware versions of AGS, report a warning + // if the unsafe path is used for write operation + if (!read_only && game.options[OPT_SAFEFILEPATHS]) + { + debug_script_warn("Attempt to access file '%s' denied (cannot write to game installation directory);\nPath will be remapped to the app data directory: '%s'", + sc_path.GetCStr(), parent_dir.GetCStr()); + } + } + + if (child_path[0u] == '\\' || child_path[0u] == '/') + child_path.ClipLeft(1); + + String full_path = String::FromFormat("%s%s", parent_dir.GetCStr(), child_path.GetCStr()); + // don't allow write operations for relative paths outside game dir + if (!read_only) + { + if (!Path::IsSameOrSubDir(parent_dir, full_path)) + { + debug_script_warn("Attempt to access file '%s' denied (outside of game directory)", sc_path.GetCStr()); + return false; + } + } + rp.BaseDir = parent_dir; + rp.FullPath = full_path; + rp.AltPath = alt_path; + return true; +} + +bool ResolveWritePathAndCreateDirs(const String &sc_path, ResolvedPath &rp) +{ + if (!ResolveScriptPath(sc_path, false, rp)) + return false; + if (!Directory::CreateAllDirectories(rp.BaseDir, Path::GetDirectoryPath(rp.FullPath))) + { + debug_script_warn("ResolveScriptPath: failed to create all subdirectories: %s", rp.FullPath.GetCStr()); + return false; + } + return true; +} + +Stream *LocateAsset(const AssetPath &path, size_t &asset_size) +{ + String assetlib = path.first; + String assetname = path.second; + bool needsetback = false; + // Change to the different library, if required + // TODO: teaching AssetManager to register multiple libraries simultaneously + // will let us skip this step, and also make this operation much faster. + if (!assetlib.IsEmpty() && assetlib.CompareNoCase(ResPaths.GamePak.Name) != 0) + { + AssetManager::SetDataFile(get_known_assetlib(assetlib)); + needsetback = true; + } + Stream *asset_stream = AssetManager::OpenAsset(assetname); + asset_size = AssetManager::GetLastAssetSize(); + if (needsetback) + AssetManager::SetDataFile(ResPaths.GamePak.Path); + return asset_stream; +} + +// +// AGS custom PACKFILE callbacks, that use our own Stream object +// +static int ags_pf_fclose(void *userdata) +{ + delete (AGS_PACKFILE_OBJ*)userdata; + return 0; +} + +static int ags_pf_getc(void *userdata) +{ + AGS_PACKFILE_OBJ* obj = (AGS_PACKFILE_OBJ*)userdata; + if (obj->remains > 0) + { + obj->remains--; + return obj->stream->ReadByte(); + } + return -1; +} + +static int ags_pf_ungetc(int c, void *userdata) +{ + return -1; // we do not want to support this +} + +static long ags_pf_fread(void *p, long n, void *userdata) +{ + AGS_PACKFILE_OBJ* obj = (AGS_PACKFILE_OBJ*)userdata; + if (obj->remains > 0) + { + size_t read = Math::Min(obj->remains, (size_t)n); + obj->remains -= read; + return obj->stream->Read(p, read); + } + return -1; +} + +static int ags_pf_putc(int c, void *userdata) +{ + return -1; // don't support write +} + +static long ags_pf_fwrite(AL_CONST void *p, long n, void *userdata) +{ + return -1; // don't support write +} + +static int ags_pf_fseek(void *userdata, int offset) +{ + return -1; // don't support seek +} + +static int ags_pf_feof(void *userdata) +{ + return ((AGS_PACKFILE_OBJ*)userdata)->remains == 0; +} + +static int ags_pf_ferror(void *userdata) +{ + return ((AGS_PACKFILE_OBJ*)userdata)->stream->HasErrors() ? 1 : 0; +} + +// Custom PACKFILE callback table +static PACKFILE_VTABLE ags_packfile_vtable = { + ags_pf_fclose, + ags_pf_getc, + ags_pf_ungetc, + ags_pf_fread, + ags_pf_putc, + ags_pf_fwrite, + ags_pf_fseek, + ags_pf_feof, + ags_pf_ferror +}; +// + +PACKFILE *PackfileFromAsset(const AssetPath &path, size_t &asset_size) +{ + Stream *asset_stream = LocateAsset(path, asset_size); + if (asset_stream && asset_size > 0) + { + AGS_PACKFILE_OBJ* obj = new AGS_PACKFILE_OBJ; + obj->stream.reset(asset_stream); + obj->asset_size = asset_size; + obj->remains = asset_size; + return pack_fopen_vtable(&ags_packfile_vtable, obj); + } + return nullptr; +} + +DUMBFILE *DUMBfileFromAsset(const AssetPath &path, size_t &asset_size) +{ + PACKFILE *pf = PackfileFromAsset(path, asset_size); + if (pf) + return dumbfile_open_packfile(pf); + return nullptr; +} + +bool DoesAssetExistInLib(const AssetPath &assetname) +{ + bool needsetback = false; + // Change to the different library, if required + // TODO: teaching AssetManager to register multiple libraries simultaneously + // will let us skip this step, and also make this operation much faster. + if (!assetname.first.IsEmpty() && assetname.first.CompareNoCase(ResPaths.GamePak.Name) != 0) + { + AssetManager::SetDataFile(get_known_assetlib(assetname.first)); + needsetback = true; + } + bool res = AssetManager::DoesAssetExist(assetname.second); + if (needsetback) + AssetManager::SetDataFile(ResPaths.GamePak.Path); + return res; +} + +void set_install_dir(const String &path, const String &audio_path, const String &voice_path) +{ + if (path.IsEmpty()) + installDirectory = "."; + else + installDirectory = Path::MakePathNoSlash(path); + if (audio_path.IsEmpty()) + installAudioDirectory = "."; + else + installAudioDirectory = Path::MakePathNoSlash(audio_path); + if (voice_path.IsEmpty()) + installVoiceDirectory = "."; + else + installVoiceDirectory = Path::MakePathNoSlash(voice_path); +} + +String get_install_dir() +{ + return installDirectory; +} + +String get_audio_install_dir() +{ + return installAudioDirectory; +} + +String get_voice_install_dir() +{ + return installVoiceDirectory; +} + +void get_install_dir_path(char* buffer, const char *fileName) +{ + sprintf(buffer, "%s/%s", installDirectory.GetCStr(), fileName); +} + +String find_assetlib(const String &filename) +{ + String libname = cbuf_to_string_and_free( ci_find_file(ResPaths.DataDir, filename) ); + if (AssetManager::IsDataFile(libname)) + return libname; + if (Path::ComparePaths(ResPaths.DataDir, installDirectory) != 0) + { + // Hack for running in Debugger + libname = cbuf_to_string_and_free( ci_find_file(installDirectory, filename) ); + if (AssetManager::IsDataFile(libname)) + return libname; + } + return ""; +} + +// Looks up for known valid asset library and returns path, or empty string if failed +String get_known_assetlib(const String &filename) +{ + // TODO: write now there's only 3 regular PAKs, so we may do this quick + // string comparison, but if we support more maybe we could use a table. + if (filename.CompareNoCase(ResPaths.GamePak.Name) == 0) + return ResPaths.GamePak.Path; + if (filename.CompareNoCase(ResPaths.AudioPak.Name) == 0) + return ResPaths.AudioPak.Path; + if (filename.CompareNoCase(ResPaths.SpeechPak.Name) == 0) + return ResPaths.SpeechPak.Path; + return String(); +} + +Stream *find_open_asset(const String &filename) +{ + Stream *asset_s = Common::AssetManager::OpenAsset(filename); + if (!asset_s && Path::ComparePaths(ResPaths.DataDir, installDirectory) != 0) + { + // Just in case they're running in Debug, try standalone file in compiled folder + asset_s = ci_fopen(String::FromFormat("%s/%s", installDirectory.GetCStr(), filename.GetCStr())); + } + return asset_s; +} + +AssetPath get_audio_clip_assetpath(int bundling_type, const String &filename) +{ + // Special case is explicitly defined audio directory, which should be + // tried first regardless of bundling type. + if (Path::ComparePaths(ResPaths.DataDir, installAudioDirectory) != 0) + { + String filepath = String::FromFormat("%s/%s", installAudioDirectory.GetCStr(), filename.GetCStr()); + if (Path::IsFile(filepath)) + return AssetPath("", filepath); + } + + if (bundling_type == AUCL_BUNDLE_EXE) + return AssetPath(ResPaths.GamePak.Name, filename); + else if (bundling_type == AUCL_BUNDLE_VOX) + return AssetPath(game.GetAudioVOXName(), filename); + return AssetPath(); +} + +AssetPath get_voice_over_assetpath(const String &filename) +{ + // Special case is explicitly defined voice-over directory, which should be + // tried first. + if (Path::ComparePaths(ResPaths.DataDir, installVoiceDirectory) != 0) + { + String filepath = String::FromFormat("%s/%s", installVoiceDirectory.GetCStr(), filename.GetCStr()); + if (Path::IsFile(filepath)) + return AssetPath("", filepath); + } + return AssetPath(ResPaths.SpeechPak.Name, filename); +} + +ScriptFileHandle valid_handles[MAX_OPEN_SCRIPT_FILES + 1]; +// [IKM] NOTE: this is not precisely the number of files opened at this moment, +// but rather maximal number of handles that were used simultaneously during game run +int num_open_script_files = 0; +ScriptFileHandle *check_valid_file_handle_ptr(Stream *stream_ptr, const char *operation_name) +{ + if (stream_ptr) + { + for (int i = 0; i < num_open_script_files; ++i) + { + if (stream_ptr == valid_handles[i].stream) + { + return &valid_handles[i]; + } + } + } + + String exmsg = String::FromFormat("!%s: invalid file handle; file not previously opened or has been closed", operation_name); + quit(exmsg); + return nullptr; +} + +ScriptFileHandle *check_valid_file_handle_int32(int32_t handle, const char *operation_name) +{ + if (handle > 0) + { + for (int i = 0; i < num_open_script_files; ++i) + { + if (handle == valid_handles[i].handle) + { + return &valid_handles[i]; + } + } + } + + String exmsg = String::FromFormat("!%s: invalid file handle; file not previously opened or has been closed", operation_name); + quit(exmsg); + return nullptr; +} + +Stream *get_valid_file_stream_from_handle(int32_t handle, const char *operation_name) +{ + ScriptFileHandle *sc_handle = check_valid_file_handle_int32(handle, operation_name); + return sc_handle ? sc_handle->stream : nullptr; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// int (const char *fnmm) +RuntimeScriptValue Sc_File_Delete(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(File_Delete, const char); +} + +// int (const char *fnmm) +RuntimeScriptValue Sc_File_Exists(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(File_Exists, const char); +} + +// void *(const char *fnmm, int mode) +RuntimeScriptValue Sc_sc_OpenFile(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_POBJ_PINT(sc_File, sc_OpenFile, const char); +} + +// void (sc_File *fil) +RuntimeScriptValue Sc_File_Close(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(sc_File, File_Close); +} + +// int (sc_File *fil) +RuntimeScriptValue Sc_File_ReadInt(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(sc_File, File_ReadInt); +} + +// int (sc_File *fil) +RuntimeScriptValue Sc_File_ReadRawChar(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(sc_File, File_ReadRawChar); +} + +// int (sc_File *fil) +RuntimeScriptValue Sc_File_ReadRawInt(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(sc_File, File_ReadRawInt); +} + +// void (sc_File *fil, char* buffer) +RuntimeScriptValue Sc_File_ReadRawLine(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(sc_File, File_ReadRawLine, char); +} + +// const char* (sc_File *fil) +RuntimeScriptValue Sc_File_ReadRawLineBack(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(sc_File, const char, myScriptStringImpl, File_ReadRawLineBack); +} + +// void (sc_File *fil, char *toread) +RuntimeScriptValue Sc_File_ReadString(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(sc_File, File_ReadString, char); +} + +// const char* (sc_File *fil) +RuntimeScriptValue Sc_File_ReadStringBack(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(sc_File, const char, myScriptStringImpl, File_ReadStringBack); +} + +// void (sc_File *fil, int towrite) +RuntimeScriptValue Sc_File_WriteInt(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(sc_File, File_WriteInt); +} + +// void (sc_File *fil, int towrite) +RuntimeScriptValue Sc_File_WriteRawChar(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(sc_File, File_WriteRawChar); +} + +// void (sc_File *fil, const char *towrite) +RuntimeScriptValue Sc_File_WriteRawLine(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(sc_File, File_WriteRawLine, const char); +} + +// void (sc_File *fil, const char *towrite) +RuntimeScriptValue Sc_File_WriteString(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(sc_File, File_WriteString, const char); +} + +RuntimeScriptValue Sc_File_Seek(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT2(sc_File, File_Seek); +} + +// int (sc_File *fil) +RuntimeScriptValue Sc_File_GetEOF(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(sc_File, File_GetEOF); +} + +// int (sc_File *fil) +RuntimeScriptValue Sc_File_GetError(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(sc_File, File_GetError); +} + +RuntimeScriptValue Sc_File_GetPosition(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(sc_File, File_GetPosition); +} + + +void RegisterFileAPI() +{ + ccAddExternalStaticFunction("File::Delete^1", Sc_File_Delete); + ccAddExternalStaticFunction("File::Exists^1", Sc_File_Exists); + ccAddExternalStaticFunction("File::Open^2", Sc_sc_OpenFile); + ccAddExternalObjectFunction("File::Close^0", Sc_File_Close); + ccAddExternalObjectFunction("File::ReadInt^0", Sc_File_ReadInt); + ccAddExternalObjectFunction("File::ReadRawChar^0", Sc_File_ReadRawChar); + ccAddExternalObjectFunction("File::ReadRawInt^0", Sc_File_ReadRawInt); + ccAddExternalObjectFunction("File::ReadRawLine^1", Sc_File_ReadRawLine); + ccAddExternalObjectFunction("File::ReadRawLineBack^0", Sc_File_ReadRawLineBack); + ccAddExternalObjectFunction("File::ReadString^1", Sc_File_ReadString); + ccAddExternalObjectFunction("File::ReadStringBack^0", Sc_File_ReadStringBack); + ccAddExternalObjectFunction("File::WriteInt^1", Sc_File_WriteInt); + ccAddExternalObjectFunction("File::WriteRawChar^1", Sc_File_WriteRawChar); + ccAddExternalObjectFunction("File::WriteRawLine^1", Sc_File_WriteRawLine); + ccAddExternalObjectFunction("File::WriteString^1", Sc_File_WriteString); + ccAddExternalObjectFunction("File::Seek^2", Sc_File_Seek); + ccAddExternalObjectFunction("File::get_EOF", Sc_File_GetEOF); + ccAddExternalObjectFunction("File::get_Error", Sc_File_GetError); + ccAddExternalObjectFunction("File::get_Position", Sc_File_GetPosition); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("File::Delete^1", (void*)File_Delete); + ccAddExternalFunctionForPlugin("File::Exists^1", (void*)File_Exists); + ccAddExternalFunctionForPlugin("File::Open^2", (void*)sc_OpenFile); + ccAddExternalFunctionForPlugin("File::Close^0", (void*)File_Close); + ccAddExternalFunctionForPlugin("File::ReadInt^0", (void*)File_ReadInt); + ccAddExternalFunctionForPlugin("File::ReadRawChar^0", (void*)File_ReadRawChar); + ccAddExternalFunctionForPlugin("File::ReadRawInt^0", (void*)File_ReadRawInt); + ccAddExternalFunctionForPlugin("File::ReadRawLine^1", (void*)File_ReadRawLine); + ccAddExternalFunctionForPlugin("File::ReadRawLineBack^0", (void*)File_ReadRawLineBack); + ccAddExternalFunctionForPlugin("File::ReadString^1", (void*)File_ReadString); + ccAddExternalFunctionForPlugin("File::ReadStringBack^0", (void*)File_ReadStringBack); + ccAddExternalFunctionForPlugin("File::WriteInt^1", (void*)File_WriteInt); + ccAddExternalFunctionForPlugin("File::WriteRawChar^1", (void*)File_WriteRawChar); + ccAddExternalFunctionForPlugin("File::WriteRawLine^1", (void*)File_WriteRawLine); + ccAddExternalFunctionForPlugin("File::WriteString^1", (void*)File_WriteString); + ccAddExternalFunctionForPlugin("File::get_EOF", (void*)File_GetEOF); + ccAddExternalFunctionForPlugin("File::get_Error", (void*)File_GetError); +} diff --git a/engines/ags/engine/ac/file.h b/engines/ags/engine/ac/file.h new file mode 100644 index 00000000000..cc12e752062 --- /dev/null +++ b/engines/ags/engine/ac/file.h @@ -0,0 +1,57 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Script File API implementation. +// +//============================================================================= +#ifndef __AGS_EE_AC__FILE_H +#define __AGS_EE_AC__FILE_H + +#include "ac/dynobj/scriptfile.h" +#include "ac/runtime_defines.h" +using AGS::Common::Stream; + +int File_Exists(const char *fnmm); +int File_Delete(const char *fnmm); +void *sc_OpenFile(const char *fnmm, int mode); +void File_Close(sc_File *fil); +void File_WriteString(sc_File *fil, const char *towrite); +void File_WriteInt(sc_File *fil, int towrite); +void File_WriteRawChar(sc_File *fil, int towrite); +void File_WriteRawLine(sc_File *fil, const char *towrite); +void File_ReadRawLine(sc_File *fil, char* buffer); +const char* File_ReadRawLineBack(sc_File *fil); +void File_ReadString(sc_File *fil, char *toread); +const char* File_ReadStringBack(sc_File *fil); +int File_ReadInt(sc_File *fil); +int File_ReadRawChar(sc_File *fil); +int File_ReadRawInt(sc_File *fil); +int File_Seek(sc_File *fil, int offset, int origin); +int File_GetEOF(sc_File *fil); +int File_GetError(sc_File *fil); +int File_GetPosition(sc_File *fil); + +struct ScriptFileHandle +{ + Stream *stream; + int32_t handle; +}; +extern ScriptFileHandle valid_handles[MAX_OPEN_SCRIPT_FILES + 1]; +extern int num_open_script_files; + +ScriptFileHandle *check_valid_file_handle_ptr(Stream *stream_ptr, const char *operation_name); +ScriptFileHandle *check_valid_file_handle_int32(int32_t handle, const char *operation_name); +Stream *get_valid_file_stream_from_handle(int32_t handle, const char *operation_name); + +#endif // __AGS_EE_AC__FILE_H diff --git a/engines/ags/engine/ac/game.cpp b/engines/ags/engine/ac/game.cpp new file mode 100644 index 00000000000..ac3f6dc70a5 --- /dev/null +++ b/engines/ags/engine/ac/game.cpp @@ -0,0 +1,2545 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/game.h" + +#include "ac/common.h" +#include "ac/view.h" +#include "ac/audiocliptype.h" +#include "ac/audiochannel.h" +#include "ac/character.h" +#include "ac/charactercache.h" +#include "ac/characterextras.h" +#include "ac/dialogtopic.h" +#include "ac/draw.h" +#include "ac/dynamicsprite.h" +#include "ac/event.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_audio.h" +#include "ac/global_character.h" +#include "ac/global_display.h" +#include "ac/global_game.h" +#include "ac/global_gui.h" +#include "ac/global_object.h" +#include "ac/global_translation.h" +#include "ac/gui.h" +#include "ac/hotspot.h" +#include "ac/lipsync.h" +#include "ac/mouse.h" +#include "ac/movelist.h" +#include "ac/objectcache.h" +#include "ac/overlay.h" +#include "ac/path_helper.h" +#include "ac/sys_events.h" +#include "ac/region.h" +#include "ac/richgamemedia.h" +#include "ac/room.h" +#include "ac/roomobject.h" +#include "ac/roomstatus.h" +#include "ac/runtime_defines.h" +#include "ac/screenoverlay.h" +#include "ac/spritecache.h" +#include "ac/string.h" +#include "ac/system.h" +#include "ac/timer.h" +#include "ac/translation.h" +#include "ac/dynobj/all_dynamicclasses.h" +#include "ac/dynobj/all_scriptclasses.h" +#include "ac/dynobj/cc_audiochannel.h" +#include "ac/dynobj/cc_audioclip.h" +#include "ac/dynobj/scriptcamera.h" +#include "debug/debug_log.h" +#include "debug/out.h" +#include "device/mousew32.h" +#include "font/fonts.h" +#include "game/savegame.h" +#include "game/savegame_components.h" +#include "game/savegame_internal.h" +#include "gui/animatingguibutton.h" +#include "gfx/bitmap.h" +#include "gfx/graphicsdriver.h" +#include "gfx/gfxfilter.h" +#include "gui/guidialog.h" +#include "main/engine.h" +#include "main/graphics_mode.h" +#include "main/main.h" +#include "media/audio/audio_system.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "script/cc_error.h" +#include "script/runtimescriptvalue.h" +#include "script/script.h" +#include "script/script_runtime.h" +#include "util/alignedstream.h" +#include "util/directory.h" +#include "util/filestream.h" // TODO: needed only because plugins expect file handle +#include "util/path.h" +#include "util/string_utils.h" +#include "ac/keycode.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern ScriptAudioChannel scrAudioChannel[MAX_SOUND_CHANNELS + 1]; +extern int cur_mode,cur_cursor; +extern SpeechLipSyncLine *splipsync; +extern int numLipLines, curLipLine, curLipLinePhoneme; + +extern CharacterExtras *charextra; +extern DialogTopic *dialog; + +extern int ifacepopped; // currently displayed pop-up GUI (-1 if none) +extern int mouse_on_iface; // mouse cursor is over this interface +extern int mouse_ifacebut_xoffs,mouse_ifacebut_yoffs; + +extern AnimatingGUIButton animbuts[MAX_ANIMATING_BUTTONS]; +extern int numAnimButs; + +extern int is_complete_overlay,is_text_overlay; + +#if AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_ANDROID +extern int psp_gfx_renderer; +#endif + +extern int obj_lowest_yp, char_lowest_yp; + +extern int actSpsCount; +extern Bitmap **actsps; +extern IDriverDependantBitmap* *actspsbmp; +// temporary cache of walk-behind for this actsps image +extern Bitmap **actspswb; +extern IDriverDependantBitmap* *actspswbbmp; +extern CachedActSpsData* actspswbcache; +extern Bitmap **guibg; +extern IDriverDependantBitmap **guibgbmp; +extern char transFileName[MAX_PATH]; +extern color palette[256]; +extern unsigned int loopcounter; +extern Bitmap *raw_saved_screen; +extern Bitmap *dynamicallyCreatedSurfaces[MAX_DYNAMIC_SURFACES]; +extern IGraphicsDriver *gfxDriver; + +//============================================================================= +GameState play; +GameSetup usetup; +GameSetupStruct game; +RoomStatus troom; // used for non-saveable rooms, eg. intro +RoomObject*objs; +RoomStatus*croom=nullptr; +RoomStruct thisroom; + +volatile int switching_away_from_game = 0; +volatile bool switched_away = false; +volatile char want_exit = 0, abort_engine = 0; +GameDataVersion loaded_game_file_version = kGameVersion_Undefined; +int frames_per_second=40; +int displayed_room=-10,starting_room = -1; +int in_new_room=0, new_room_was = 0; // 1 in new room, 2 first time in new room, 3 loading saved game +int new_room_pos=0; +int new_room_x = SCR_NO_VALUE, new_room_y = SCR_NO_VALUE; +int new_room_loop = SCR_NO_VALUE; + +// initially size 1, this will be increased by the initFile function +SpriteCache spriteset(game.SpriteInfos); +int proper_exit=0,our_eip=0; + +std::vector guis; + +CCGUIObject ccDynamicGUIObject; +CCCharacter ccDynamicCharacter; +CCHotspot ccDynamicHotspot; +CCRegion ccDynamicRegion; +CCInventory ccDynamicInv; +CCGUI ccDynamicGUI; +CCObject ccDynamicObject; +CCDialog ccDynamicDialog; +CCAudioClip ccDynamicAudioClip; +CCAudioChannel ccDynamicAudio; +ScriptString myScriptStringImpl; +// TODO: IMPORTANT!! +// we cannot simply replace these arrays with vectors, or other C++ containers, +// until we implement safe management of such containers in script exports +// system. Noteably we would need an alternate to StaticArray class to track +// access to their elements. +ScriptObject scrObj[MAX_ROOM_OBJECTS]; +ScriptGUI *scrGui = nullptr; +ScriptHotspot scrHotspot[MAX_ROOM_HOTSPOTS]; +ScriptRegion scrRegion[MAX_ROOM_REGIONS]; +ScriptInvItem scrInv[MAX_INV]; +ScriptDialog *scrDialog; + +ViewStruct*views=nullptr; + +CharacterCache *charcache = nullptr; +ObjectCache objcache[MAX_ROOM_OBJECTS]; + +MoveList *mls = nullptr; + +//============================================================================= + +String saveGameDirectory = "./"; +// Custom save game parent directory +String saveGameParent; + +const char* sgnametemplate = "agssave.%03d"; +String saveGameSuffix; + +int game_paused=0; +char pexbuf[STD_BUFFER_SIZE]; + +unsigned int load_new_game = 0; +int load_new_game_restore = -1; + +// TODO: refactor these global vars into function arguments +int getloctype_index = 0, getloctype_throughgui = 0; + +//============================================================================= +// Audio +//============================================================================= + +void Game_StopAudio(int audioType) +{ + if (((audioType < 0) || ((size_t)audioType >= game.audioClipTypes.size())) && (audioType != SCR_NO_VALUE)) + quitprintf("!Game.StopAudio: invalid audio type %d", audioType); + int aa; + + for (aa = 0; aa < MAX_SOUND_CHANNELS; aa++) + { + if (audioType == SCR_NO_VALUE) + { + stop_or_fade_out_channel(aa); + } + else + { + ScriptAudioClip *clip = AudioChannel_GetPlayingClip(&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 >= game.audioClipTypes.size())) && (audioType != SCR_NO_VALUE)) + quitprintf("!Game.IsAudioPlaying: invalid audio type %d", audioType); + + if (play.fast_forward) + return 0; + + for (int aa = 0; aa < MAX_SOUND_CHANNELS; aa++) + { + ScriptAudioClip *clip = AudioChannel_GetPlayingClip(&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 >= game.audioClipTypes.size())) + quitprintf("!Game.SetAudioTypeVolume: invalid audio type: %d", audioType); + + Debug::Printf("Game.SetAudioTypeSpeechVolumeDrop: type: %d, drop: %d", audioType, volumeDrop); + 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 >= game.audioClipTypes.size())) + quitprintf("!Game.SetAudioTypeVolume: invalid audio type: %d", audioType); + + Debug::Printf("Game.SetAudioTypeVolume: type: %d, volume: %d, change: %d", audioType, volume, changeType); + if ((changeType == VOL_CHANGEEXISTING) || + (changeType == VOL_BOTH)) + { + AudioChannelsLock lock; + for (int aa = 0; aa < MAX_SOUND_CHANNELS; aa++) + { + ScriptAudioClip *clip = AudioChannel_GetPlayingClip(&scrAudioChannel[aa]); + if ((clip != nullptr) && (clip->type == audioType)) + { + auto* ch = lock.GetChannel(aa); + if (ch) + ch->set_volume_percent(volume); + } + } + } + + if ((changeType == VOL_SETFUTUREDEFAULT) || + (changeType == VOL_BOTH)) + { + play.default_audio_type_volumes[audioType] = volume; + + // update queued clip volumes + update_queued_clips_volume(audioType, volume); + } + +} + +int Game_GetMODPattern() { + if (current_music_type != MUS_MOD) + return -1; + AudioChannelsLock lock; + auto* music_ch = lock.GetChannelIfPlaying(SCHAN_MUSIC); + return music_ch ? music_ch->get_pos() : -1; +} + +//============================================================================= +// --- +//============================================================================= + +int Game_GetDialogCount() +{ + return game.numdialog; +} + +void set_debug_mode(bool on) +{ + play.debug_mode = on ? 1 : 0; + debug_set_console(on); +} + +void set_game_speed(int new_fps) { + frames_per_second = new_fps; + if (!isTimerFpsMaxed()) // if in maxed mode, don't update timer for now + setTimerFps(new_fps); +} + +extern int cbuttfont; +extern int acdialog_font; + +int oldmouse; +void setup_for_dialog() { + cbuttfont = play.normal_font; + acdialog_font = play.normal_font; + if (!play.mouse_cursor_hidden) + ags_domouse(DOMOUSE_ENABLE); + oldmouse=cur_cursor; set_mouse_cursor(CURS_ARROW); +} +void restore_after_dialog() { + set_mouse_cursor(oldmouse); + if (!play.mouse_cursor_hidden) + ags_domouse(DOMOUSE_DISABLE); + invalidate_screen(); +} + + + +String get_save_game_directory() +{ + return saveGameDirectory; +} + +String get_save_game_suffix() +{ + return saveGameSuffix; +} + +void set_save_game_suffix(const String &suffix) +{ + saveGameSuffix = suffix; +} + +String get_save_game_path(int slotNum) { + String filename; + filename.Format(sgnametemplate, slotNum); + String path = saveGameDirectory; + path.Append(filename); + path.Append(saveGameSuffix); + return path; +} + +// Convert a path possibly containing path tags into acceptable save path +bool MakeSaveGameDir(const String &newFolder, ResolvedPath &rp) +{ + rp = ResolvedPath(); + // don't allow absolute paths + if (!is_relative_filename(newFolder)) + return false; + + String base_dir; + String newSaveGameDir = FixSlashAfterToken(newFolder); + + if (newSaveGameDir.CompareLeft(UserSavedgamesRootToken, UserSavedgamesRootToken.GetLength()) == 0) + { + if (saveGameParent.IsEmpty()) + { + base_dir = PathOrCurDir(platform->GetUserSavedgamesDirectory()); + newSaveGameDir.ReplaceMid(0, UserSavedgamesRootToken.GetLength(), base_dir); + } + else + { + // If there is a custom save parent directory, then replace + // not only root token, but also first subdirectory + newSaveGameDir.ClipSection('/', 0, 1); + if (!newSaveGameDir.IsEmpty()) + newSaveGameDir.PrependChar('/'); + newSaveGameDir.Prepend(saveGameParent); + base_dir = saveGameParent; + } + } + else + { + // Convert the path relative to installation folder into path relative to the + // safe save path with default name + if (saveGameParent.IsEmpty()) + { + base_dir = PathOrCurDir(platform->GetUserSavedgamesDirectory()); + newSaveGameDir.Format("%s/%s/%s", base_dir.GetCStr(), game.saveGameFolderName, newFolder.GetCStr()); + } + else + { + base_dir = saveGameParent; + newSaveGameDir.Format("%s/%s", saveGameParent.GetCStr(), newFolder.GetCStr()); + } + // For games made in the safe-path-aware versions of AGS, report a warning + if (game.options[OPT_SAFEFILEPATHS]) + { + 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'", + newFolder.GetCStr(), newSaveGameDir.GetCStr()); + } + } + rp.BaseDir = Path::MakeTrailingSlash(base_dir); + rp.FullPath = Path::MakeTrailingSlash(newSaveGameDir); + return true; +} + +bool SetCustomSaveParent(const String &path) +{ + if (SetSaveGameDirectoryPath(path, true)) + { + saveGameParent = path; + return true; + } + return false; +} + +bool SetSaveGameDirectoryPath(const char *newFolder, bool explicit_path) +{ + if (!newFolder || newFolder[0] == 0) + newFolder = "."; + String newSaveGameDir; + if (explicit_path) + { + newSaveGameDir = Path::MakeTrailingSlash(newFolder); + if (!Directory::CreateDirectory(newSaveGameDir)) + return false; + } + else + { + ResolvedPath rp; + if (!MakeSaveGameDir(newFolder, rp)) + return false; + if (!Directory::CreateAllDirectories(rp.BaseDir, rp.FullPath)) + { + debug_script_warn("SetSaveGameDirectory: failed to create all subdirectories: %s", rp.FullPath.GetCStr()); + return false; + } + newSaveGameDir = rp.FullPath; + } + + String newFolderTempFile = String::FromFormat("%s""agstmp.tmp", newSaveGameDir.GetCStr()); + if (!Common::File::TestCreateFile(newFolderTempFile)) + return false; + + // copy the Restart Game file, if applicable + String restartGamePath = String::FromFormat("%s""agssave.%d%s", saveGameDirectory.GetCStr(), RESTART_POINT_SAVE_GAME_NUMBER, saveGameSuffix.GetCStr()); + Stream *restartGameFile = Common::File::OpenFileRead(restartGamePath); + if (restartGameFile != nullptr) + { + long fileSize = restartGameFile->GetLength(); + char *mbuffer = (char*)malloc(fileSize); + restartGameFile->Read(mbuffer, fileSize); + delete restartGameFile; + + restartGamePath.Format("%s""agssave.%d%s", newSaveGameDir.GetCStr(), RESTART_POINT_SAVE_GAME_NUMBER, saveGameSuffix.GetCStr()); + restartGameFile = Common::File::CreateFile(restartGamePath); + restartGameFile->Write(mbuffer, fileSize); + delete restartGameFile; + free(mbuffer); + } + + saveGameDirectory = newSaveGameDir; + return true; +} + +int Game_SetSaveGameDirectory(const char *newFolder) +{ + return SetSaveGameDirectoryPath(newFolder, false) ? 1 : 0; +} + +const char* Game_GetSaveSlotDescription(int slnum) { + String description; + if (read_savedgame_description(get_save_game_path(slnum), description)) + { + return CreateNewScriptString(description); + } + return nullptr; +} + + +void restore_game_dialog() { + can_run_delayed_command(); + if (thisroom.Options.SaveLoadDisabled == 1) { + DisplayMessage (983); + return; + } + if (inside_script) { + curscript->queue_action(ePSARestoreGameDialog, 0, "RestoreGameDialog"); + return; + } + setup_for_dialog(); + int toload=loadgamedialog(); + restore_after_dialog(); + if (toload>=0) { + try_restore_save(toload); + } +} + +void save_game_dialog() { + if (thisroom.Options.SaveLoadDisabled == 1) { + DisplayMessage (983); + return; + } + if (inside_script) { + curscript->queue_action(ePSASaveGameDialog, 0, "SaveGameDialog"); + return; + } + setup_for_dialog(); + int toload=savegamedialog(); + restore_after_dialog(); + if (toload>=0) + save_game(toload, get_gui_dialog_buffer()); +} + +void free_do_once_tokens() +{ + play.do_once_tokens.resize(0); +} + + +// Free all the memory associated with the game +// TODO: call this when exiting the game (currently only called in RunAGSGame) +void unload_game_file() +{ + close_translation(); + + play.FreeViewportsAndCameras(); + + characterScriptObjNames.clear(); + free(charextra); + free(mls); + free(actsps); + free(actspsbmp); + free(actspswb); + free(actspswbbmp); + free(actspswbcache); + + if ((gameinst != nullptr) && (gameinst->pc != 0)) + { + quit("Error: unload_game called while script still running"); + } + else + { + delete gameinstFork; + delete gameinst; + gameinstFork = nullptr; + gameinst = nullptr; + } + + gamescript.reset(); + + if ((dialogScriptsInst != nullptr) && (dialogScriptsInst->pc != 0)) + { + quit("Error: unload_game called while dialog script still running"); + } + else if (dialogScriptsInst != nullptr) + { + delete dialogScriptsInst; + dialogScriptsInst = nullptr; + } + + dialogScriptsScript.reset(); + + for (int i = 0; i < numScriptModules; ++i) + { + delete moduleInstFork[i]; + delete moduleInst[i]; + scriptModules[i].reset(); + } + moduleInstFork.resize(0); + moduleInst.resize(0); + scriptModules.resize(0); + repExecAlways.moduleHasFunction.resize(0); + lateRepExecAlways.moduleHasFunction.resize(0); + getDialogOptionsDimensionsFunc.moduleHasFunction.resize(0); + renderDialogOptionsFunc.moduleHasFunction.resize(0); + getDialogOptionUnderCursorFunc.moduleHasFunction.resize(0); + runDialogOptionMouseClickHandlerFunc.moduleHasFunction.resize(0); + runDialogOptionKeyPressHandlerFunc.moduleHasFunction.resize(0); + runDialogOptionRepExecFunc.moduleHasFunction.resize(0); + numScriptModules = 0; + + free(views); + views = nullptr; + + free(charcache); + charcache = nullptr; + + if (splipsync != nullptr) + { + for (int i = 0; i < numLipLines; ++i) + { + free(splipsync[i].endtimeoffs); + free(splipsync[i].frame); + } + free(splipsync); + splipsync = nullptr; + numLipLines = 0; + curLipLine = -1; + } + + for (int i = 0; i < game.numdialog; ++i) + { + if (dialog[i].optionscripts != nullptr) + free(dialog[i].optionscripts); + dialog[i].optionscripts = nullptr; + } + free(dialog); + dialog = nullptr; + delete[] scrDialog; + scrDialog = nullptr; + + for (int i = 0; i < game.numgui; ++i) { + free(guibg[i]); + guibg[i] = nullptr; + } + + guiScriptObjNames.clear(); + free(guibg); + guis.clear(); + free(scrGui); + + pl_stop_plugins(); + ccRemoveAllSymbols(); + ccUnregisterAllObjects(); + + free_all_fonts(); + + free_do_once_tokens(); + free(play.gui_draw_order); + + resetRoomStatuses(); + + // free game struct last because it contains object counts + game.Free(); +} + + + + + + +const char* Game_GetGlobalStrings(int index) { + if ((index < 0) || (index >= MAXGLOBALSTRINGS)) + quit("!Game.GlobalStrings: invalid index"); + + return CreateNewScriptString(play.globalstrings[index]); +} + + + +char gamefilenamebuf[200]; + + +// ** GetGameParameter replacement functions + +int Game_GetInventoryItemCount() { + // because of the dummy item 0, this is always one higher than it should be + return game.numinvitems - 1; +} + +int Game_GetFontCount() { + return game.numfonts; +} + +int Game_GetMouseCursorCount() { + return game.numcursors; +} + +int Game_GetCharacterCount() { + return game.numcharacters; +} + +int Game_GetGUICount() { + return game.numgui; +} + +int Game_GetViewCount() { + return game.numviews; +} + +int Game_GetUseNativeCoordinates() +{ + return game.IsDataInNativeCoordinates() ? 1 : 0; +} + +int Game_GetSpriteWidth(int spriteNum) { + if (spriteNum < 0) + return 0; + + if (!spriteset.DoesSpriteExist(spriteNum)) + return 0; + + return game_to_data_coord(game.SpriteInfos[spriteNum].Width); +} + +int Game_GetSpriteHeight(int spriteNum) { + if (spriteNum < 0) + return 0; + + if (!spriteset.DoesSpriteExist(spriteNum)) + return 0; + + return game_to_data_coord(game.SpriteInfos[spriteNum].Height); +} + +int Game_GetLoopCountForView(int viewNumber) { + if ((viewNumber < 1) || (viewNumber > game.numviews)) + quit("!GetGameParameter: invalid view specified"); + + return views[viewNumber - 1].numLoops; +} + +int Game_GetRunNextSettingForLoop(int viewNumber, int loopNumber) { + if ((viewNumber < 1) || (viewNumber > game.numviews)) + quit("!GetGameParameter: invalid view specified"); + if ((loopNumber < 0) || (loopNumber >= views[viewNumber - 1].numLoops)) + quit("!GetGameParameter: invalid loop specified"); + + return (views[viewNumber - 1].loops[loopNumber].RunNextLoop()) ? 1 : 0; +} + +int Game_GetFrameCountForLoop(int viewNumber, int loopNumber) { + if ((viewNumber < 1) || (viewNumber > game.numviews)) + quit("!GetGameParameter: invalid view specified"); + if ((loopNumber < 0) || (loopNumber >= views[viewNumber - 1].numLoops)) + quit("!GetGameParameter: invalid loop specified"); + + return views[viewNumber - 1].loops[loopNumber].numFrames; +} + +ScriptViewFrame* Game_GetViewFrame(int viewNumber, int loopNumber, int frame) { + if ((viewNumber < 1) || (viewNumber > game.numviews)) + quit("!GetGameParameter: invalid view specified"); + if ((loopNumber < 0) || (loopNumber >= views[viewNumber - 1].numLoops)) + quit("!GetGameParameter: invalid loop specified"); + if ((frame < 0) || (frame >= views[viewNumber - 1].loops[loopNumber].numFrames)) + quit("!GetGameParameter: invalid frame specified"); + + ScriptViewFrame *sdt = new ScriptViewFrame(viewNumber - 1, loopNumber, frame); + ccRegisterManagedObject(sdt, sdt); + return sdt; +} + +int Game_DoOnceOnly(const char *token) +{ + for (int i = 0; i < (int)play.do_once_tokens.size(); i++) + { + if (play.do_once_tokens[i] == token) + { + return 0; + } + } + play.do_once_tokens.push_back(token); + return 1; +} + +int Game_GetTextReadingSpeed() +{ + return play.text_speed; +} + +void Game_SetTextReadingSpeed(int newTextSpeed) +{ + if (newTextSpeed < 1) + quitprintf("!Game.TextReadingSpeed: %d is an invalid speed", newTextSpeed); + + play.text_speed = newTextSpeed; +} + +int Game_GetMinimumTextDisplayTimeMs() +{ + return play.text_min_display_time_ms; +} + +void Game_SetMinimumTextDisplayTimeMs(int newTextMinTime) +{ + play.text_min_display_time_ms = newTextMinTime; +} + +int Game_GetIgnoreUserInputAfterTextTimeoutMs() +{ + return play.ignore_user_input_after_text_timeout_ms; +} + +void Game_SetIgnoreUserInputAfterTextTimeoutMs(int newValueMs) +{ + play.ignore_user_input_after_text_timeout_ms = newValueMs; +} + +const char *Game_GetFileName() { + return CreateNewScriptString(ResPaths.GamePak.Name); +} + +const char *Game_GetName() { + return CreateNewScriptString(play.game_name); +} + +void Game_SetName(const char *newName) { + strncpy(play.game_name, newName, 99); + play.game_name[99] = 0; + set_window_title(play.game_name); +} + +int Game_GetSkippingCutscene() +{ + if (play.fast_forward) + { + return 1; + } + return 0; +} + +int Game_GetInSkippableCutscene() +{ + if (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 (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]; + sc_inputbox(msg, buffer); + return CreateNewScriptString(buffer); +} + +const char* Game_GetLocationName(int x, int y) { + char buffer[STD_BUFFER_SIZE]; + GetLocationName(x, y, buffer); + return CreateNewScriptString(buffer); +} + +const char* Game_GetGlobalMessages(int index) { + if ((index < 500) || (index >= MAXGLOBALMES + 500)) { + return nullptr; + } + char buffer[STD_BUFFER_SIZE]; + buffer[0] = 0; + replace_tokens(get_translation(get_global_message(index)), buffer, STD_BUFFER_SIZE); + return CreateNewScriptString(buffer); +} + +int Game_GetSpeechFont() { + return play.speech_font; +} +int Game_GetNormalFont() { + return play.normal_font; +} + +const char* Game_GetTranslationFilename() { + char buffer[STD_BUFFER_SIZE]; + GetTranslationName(buffer); + return CreateNewScriptString(buffer); +} + +int Game_ChangeTranslation(const char *newFilename) +{ + if ((newFilename == nullptr) || (newFilename[0] == 0)) + { + close_translation(); + strcpy(transFileName, ""); + usetup.translation = ""; + return 1; + } + + String oldTransFileName; + oldTransFileName = transFileName; + + if (init_translation(newFilename, oldTransFileName.LeftSection('.'), false)) + { + usetup.translation = newFilename; + return 1; + } + else + { + strcpy(transFileName, oldTransFileName); + return 0; + } +} + +ScriptAudioClip *Game_GetAudioClip(int index) +{ + if (index < 0 || (size_t)index >= game.audioClips.size()) + return nullptr; + return &game.audioClips[index]; +} + +ScriptCamera* Game_GetCamera() +{ + return play.GetScriptCamera(0); +} + +int Game_GetCameraCount() +{ + return play.GetRoomCameraCount(); +} + +ScriptCamera* Game_GetAnyCamera(int index) +{ + return play.GetScriptCamera(index); +} + +void Game_SimulateKeyPress(int key) +{ + int platformKey = GetKeyForKeyPressCb(key); + platformKey = PlatformKeyFromAgsKey(platformKey); + if (platformKey >= 0) { + simulate_keypress(platformKey); + } +} + +//============================================================================= + +// save game functions + + + +void serialize_bitmap(const Common::Bitmap *thispic, Stream *out) { + if (thispic != nullptr) { + out->WriteInt32(thispic->GetWidth()); + out->WriteInt32(thispic->GetHeight()); + out->WriteInt32(thispic->GetColorDepth()); + for (int cc=0;ccGetHeight();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 platform-independant +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; + int 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) +{ + long fileSize = 0; + String tempFileName = String::FromFormat("%s""_tmpscht.bmp", saveGameDirectory.GetCStr()); + + screenshot->SaveToFile(tempFileName, palette); + + update_polled_stuff_if_runtime(); + + if (exists(tempFileName)) + { + fileSize = file_size_ex(tempFileName); + char *buffer = (char*)malloc(fileSize); + + Stream *temp_in = Common::File::OpenFileRead(tempFileName); + temp_in->Read(buffer, fileSize); + delete temp_in; + ::remove(tempFileName); + + out->Write(buffer, fileSize); + free(buffer); + } + return fileSize; +} + +void WriteGameSetupStructBase_Aligned(Stream *out) +{ + AlignedStream align_s(out, Common::kAligned_Write); + game.GameSetupStructBase::WriteToFile(&align_s); +} + +#define MAGICNUMBER 0xbeefcafe + +void create_savegame_screenshot(Bitmap *&screenShot) +{ + if (game.options[OPT_SAVESCREENSHOT]) { + int usewid = data_to_game_coord(play.screenshot_width); + int usehit = data_to_game_coord(play.screenshot_height); + const Rect &viewport = play.GetMainViewport(); + if (usewid > viewport.GetWidth()) + usewid = viewport.GetWidth(); + if (usehit > viewport.GetHeight()) + usehit = viewport.GetHeight(); + + if ((play.screenshot_width < 16) || (play.screenshot_height < 16)) + quit("!Invalid game.screenshot_width/height, must be from 16x16 to screen res"); + + screenShot = CopyScreenIntoBitmap(usewid, usehit); + } +} + +void save_game(int slotn, const char*descript) { + + // dont allow save in rep_exec_always, because we dont save + // the state of blocked scripts + can_run_delayed_command(); + + if (inside_script) { + strcpy(curscript->postScriptSaveSlotDescription[curscript->queue_action(ePSASaveGame, slotn, "SaveGameSlot")], descript); + return; + } + + if (platform->GetDiskFreeSpaceMB() < 2) { + Display("ERROR: There is not enough disk space free to save the game. Clear some disk space and try again."); + return; + } + + VALIDATE_STRING(descript); + String nametouse; + nametouse = get_save_game_path(slotn); + + Bitmap *screenShot = nullptr; + + // Screenshot + create_savegame_screenshot(screenShot); + + Common::PStream out = StartSavegame(nametouse, descript, screenShot); + if (out == nullptr) + quit("save_game: unable to open savegame file for writing"); + + update_polled_stuff_if_runtime(); + + // Actual dynamic game data is saved here + SaveGameState(out); + + if (screenShot != nullptr) + { + int screenShotOffset = out->GetPosition() - sizeof(RICH_GAME_MEDIA_HEADER); + int screenShotSize = write_screen_shot_for_vista(out.get(), screenShot); + + update_polled_stuff_if_runtime(); + + out.reset(Common::File::OpenFile(nametouse, Common::kFile_Open, Common::kFile_ReadWrite)); + out->Seek(12, kSeekBegin); + out->WriteInt32(screenShotOffset); + out->Seek(4); + out->WriteInt32(screenShotSize); + } + + if (screenShot != nullptr) + delete screenShot; +} + +HSaveError restore_game_head_dynamic_values(Stream *in, RestoredData &r_data) +{ + r_data.FPS = in->ReadInt32(); + r_data.CursorMode = in->ReadInt32(); + r_data.CursorID = in->ReadInt32(); + SavegameComponents::ReadLegacyCameraState(in, r_data); + set_loop_counter(in->ReadInt32()); + return HSaveError::None(); +} + +void restore_game_spriteset(Stream *in) +{ + // ensure the sprite set is at least as large as it was + // when the game was saved + spriteset.EnlargeTo(in->ReadInt32() - 1); // they saved top_index + 1 + // get serialized dynamic sprites + int sprnum = in->ReadInt32(); + while (sprnum) { + unsigned char spriteflag = in->ReadByte(); + add_dynamic_sprite(sprnum, read_serialized_bitmap(in)); + game.SpriteInfos[sprnum].Flags = spriteflag; + sprnum = in->ReadInt32(); + } +} + +HSaveError restore_game_scripts(Stream *in, const PreservedParams &pp, RestoredData &r_data) +{ + // read the global script data segment + int gdatasize = in->ReadInt32(); + if (pp.GlScDataSize != gdatasize) + { + return new SavegameError(kSvgErr_GameContentAssertion, "Mismatching size of global script data."); + } + r_data.GlobalScript.Len = gdatasize; + r_data.GlobalScript.Data.reset(new char[gdatasize]); + in->Read(r_data.GlobalScript.Data.get(), gdatasize); + + if (in->ReadInt32() != numScriptModules) + { + return new SavegameError(kSvgErr_GameContentAssertion, "Mismatching number of script modules."); + } + r_data.ScriptModules.resize(numScriptModules); + for (int i = 0; i < numScriptModules; ++i) + { + size_t module_size = in->ReadInt32(); + if (pp.ScMdDataSize[i] != module_size) + { + return new SavegameError(kSvgErr_GameContentAssertion, String::FromFormat("Mismatching size of script module data, module %d.", i)); + } + r_data.ScriptModules[i].Len = module_size; + r_data.ScriptModules[i].Data.reset(new char[module_size]); + in->Read(r_data.ScriptModules[i].Data.get(), module_size); + } + return HSaveError::None(); +} + +void ReadRoomStatus_Aligned(RoomStatus *roomstat, Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + roomstat->ReadFromFile_v321(&align_s); +} + +void restore_game_room_state(Stream *in) +{ + int vv; + + displayed_room = in->ReadInt32(); + + // read the room state for all the rooms the player has been in + RoomStatus* roomstat; + int beenhere; + for (vv=0;vvReadByte(); + if (beenhere) + { + roomstat = getRoomStatus(vv); + roomstat->beenhere = beenhere; + + if (roomstat->beenhere) + { + ReadRoomStatus_Aligned(roomstat, in); + if (roomstat->tsdatasize > 0) + { + roomstat->tsdata=(char*)malloc(roomstat->tsdatasize + 8); // JJS: Why allocate 8 additional bytes? + in->Read(&roomstat->tsdata[0], roomstat->tsdatasize); + } + } + } + } +} + +void ReadGameState_Aligned(Stream *in, RestoredData &r_data) +{ + AlignedStream align_s(in, Common::kAligned_Read); + play.ReadFromSavegame(&align_s, kGSSvgVersion_OldFormat, r_data); +} + +void restore_game_play_ex_data(Stream *in) +{ + char rbuffer[200]; + for (size_t i = 0; i < play.do_once_tokens.size(); ++i) + { + StrUtil::ReadCStr(rbuffer, in, sizeof(rbuffer)); + play.do_once_tokens[i] = rbuffer; + } + + in->ReadArrayOfInt32(&play.gui_draw_order[0], game.numgui); +} + +void restore_game_play(Stream *in, RestoredData &r_data) +{ + int screenfadedout_was = play.screen_is_faded_out; + int roomchanges_was = play.room_changes; + // make sure the pointer is preserved + int *gui_draw_order_was = play.gui_draw_order; + + ReadGameState_Aligned(in, r_data); + r_data.Cameras[0].Flags = r_data.Camera0_Flags; + + play.screen_is_faded_out = screenfadedout_was; + play.room_changes = roomchanges_was; + play.gui_draw_order = gui_draw_order_was; + + restore_game_play_ex_data(in); +} + +void ReadMoveList_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int i = 0; i < game.numcharacters + MAX_ROOM_OBJECTS + 1; ++i) + { + mls[i].ReadFromFile_Legacy(&align_s); + + align_s.Reset(); + } +} + +void ReadGameSetupStructBase_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + game.GameSetupStructBase::ReadFromFile(&align_s); +} + +void ReadCharacterExtras_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int i = 0; i < game.numcharacters; ++i) + { + charextra[i].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +void restore_game_palette(Stream *in) +{ + in->ReadArray(&palette[0],sizeof(color),256); +} + +void restore_game_dialogs(Stream *in) +{ + for (int vv=0;vvReadArrayOfInt32(&dialog[vv].optionflags[0],MAXTOPICOPTIONS); +} + +void restore_game_more_dynamic_values(Stream *in) +{ + mouse_on_iface=in->ReadInt32(); + in->ReadInt32(); // mouse_on_iface_button + in->ReadInt32(); // mouse_pushed_iface + ifacepopped = in->ReadInt32(); + game_paused=in->ReadInt32(); +} + +void ReadAnimatedButtons_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int i = 0; i < numAnimButs; ++i) + { + animbuts[i].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +HSaveError restore_game_gui(Stream *in, int numGuisWas) +{ + HError err = GUI::ReadGUI(guis, in, true); + if (!err) + return new SavegameError(kSvgErr_GameObjectInitFailed, err); + game.numgui = guis.size(); + + if (numGuisWas != game.numgui) + { + return new SavegameError(kSvgErr_GameContentAssertion, "Mismatching number of GUI."); + } + + numAnimButs = in->ReadInt32(); + ReadAnimatedButtons_Aligned(in); + return HSaveError::None(); +} + +HSaveError restore_game_audiocliptypes(Stream *in) +{ + if (in->ReadInt32() != game.audioClipTypes.size()) + { + return new SavegameError(kSvgErr_GameContentAssertion, "Mismatching number of Audio Clip Types."); + } + + for (size_t i = 0; i < game.audioClipTypes.size(); ++i) + { + game.audioClipTypes[i].ReadFromFile(in); + } + return HSaveError::None(); +} + +void restore_game_thisroom(Stream *in, RestoredData &r_data) +{ + in->ReadArrayOfInt16(r_data.RoomLightLevels, MAX_ROOM_REGIONS); + in->ReadArrayOfInt32(r_data.RoomTintLevels, MAX_ROOM_REGIONS); + in->ReadArrayOfInt16(r_data.RoomZoomLevels1, MAX_WALK_AREAS + 1); + in->ReadArrayOfInt16(r_data.RoomZoomLevels2, MAX_WALK_AREAS + 1); +} + +void restore_game_ambientsounds(Stream *in, RestoredData &r_data) +{ + for (int i = 0; i < MAX_SOUND_CHANNELS; ++i) + { + ambient[i].ReadFromFile(in); + } + + for (int bb = 1; bb < MAX_SOUND_CHANNELS; bb++) { + if (ambient[bb].channel == 0) + r_data.DoAmbient[bb] = 0; + else { + r_data.DoAmbient[bb] = ambient[bb].num; + ambient[bb].channel = 0; + } + } +} + +void ReadOverlays_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (auto &over : screenover) + { + over.ReadFromFile(&align_s, 0); + align_s.Reset(); + } +} + +void restore_game_overlays(Stream *in) +{ + screenover.resize(in->ReadInt32()); + ReadOverlays_Aligned(in); + for (auto &over : screenover) { + if (over.hasSerializedBitmap) + over.pic = read_serialized_bitmap(in); + } +} + +void restore_game_dynamic_surfaces(Stream *in, RestoredData &r_data) +{ + // load into a temp array since ccUnserialiseObjects will destroy + // it otherwise + r_data.DynamicSurfaces.resize(MAX_DYNAMIC_SURFACES); + for (int i = 0; i < MAX_DYNAMIC_SURFACES; ++i) + { + if (in->ReadInt8() == 0) + { + r_data.DynamicSurfaces[i] = nullptr; + } + else + { + r_data.DynamicSurfaces[i] = read_serialized_bitmap(in); + } + } +} + +void restore_game_displayed_room_status(Stream *in, RestoredData &r_data) +{ + int bb; + for (bb = 0; bb < MAX_ROOM_BGFRAMES; bb++) + r_data.RoomBkgScene[bb].reset(); + + if (displayed_room >= 0) { + + for (bb = 0; bb < MAX_ROOM_BGFRAMES; bb++) { + r_data.RoomBkgScene[bb] = nullptr; + if (play.raw_modified[bb]) { + r_data.RoomBkgScene[bb].reset(read_serialized_bitmap(in)); + } + } + bb = in->ReadInt32(); + + if (bb) + raw_saved_screen = read_serialized_bitmap(in); + + // get the current troom, in case they save in room 600 or whatever + ReadRoomStatus_Aligned(&troom, in); + + if (troom.tsdatasize > 0) { + troom.tsdata=(char*)malloc(troom.tsdatasize+5); + in->Read(&troom.tsdata[0],troom.tsdatasize); + } + else + troom.tsdata = nullptr; + } +} + +HSaveError restore_game_globalvars(Stream *in) +{ + if (in->ReadInt32() != numGlobalVars) + { + return new SavegameError(kSvgErr_GameContentAssertion, "Restore game error: mismatching number of Global Variables."); + } + + for (int i = 0; i < numGlobalVars; ++i) + { + globalvars[i].Read(in); + } + return HSaveError::None(); +} + +HSaveError restore_game_views(Stream *in) +{ + if (in->ReadInt32() != game.numviews) + { + return new SavegameError(kSvgErr_GameContentAssertion, "Mismatching number of Views."); + } + + for (int bb = 0; bb < game.numviews; bb++) { + for (int cc = 0; cc < views[bb].numLoops; cc++) { + for (int dd = 0; dd < views[bb].loops[cc].numFrames; dd++) + { + views[bb].loops[cc].frames[dd].sound = in->ReadInt32(); + views[bb].loops[cc].frames[dd].pic = in->ReadInt32(); + } + } + } + return HSaveError::None(); +} + +HSaveError restore_game_audioclips_and_crossfade(Stream *in, RestoredData &r_data) +{ + if (in->ReadInt32() != game.audioClips.size()) + { + return new SavegameError(kSvgErr_GameContentAssertion, "Mismatching number of Audio Clips."); + } + + for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) + { + RestoredData::ChannelInfo &chan_info = r_data.AudioChans[i]; + chan_info.Pos = 0; + chan_info.ClipID = in->ReadInt32(); + if (chan_info.ClipID >= 0) + { + if ((size_t)chan_info.ClipID >= game.audioClips.size()) + { + return new SavegameError(kSvgErr_GameObjectInitFailed, "Invalid audio clip index."); + } + + chan_info.Pos = in->ReadInt32(); + if (chan_info.Pos < 0) + chan_info.Pos = 0; + chan_info.Priority = in->ReadInt32(); + chan_info.Repeat = in->ReadInt32(); + chan_info.Vol = in->ReadInt32(); + chan_info.Pan = in->ReadInt32(); + chan_info.VolAsPercent = in->ReadInt32(); + chan_info.PanAsPercent = in->ReadInt32(); + chan_info.Speed = 1000; + if (loaded_game_file_version >= kGameVersion_340_2) + chan_info.Speed = in->ReadInt32(); + } + } + crossFading = in->ReadInt32(); + crossFadeVolumePerStep = in->ReadInt32(); + crossFadeStep = in->ReadInt32(); + crossFadeVolumeAtStart = in->ReadInt32(); + return HSaveError::None(); +} + +HSaveError restore_game_data(Stream *in, SavegameVersion svg_version, const PreservedParams &pp, RestoredData &r_data) +{ + int vv; + + HSaveError err = restore_game_head_dynamic_values(in, r_data); + if (!err) + return err; + restore_game_spriteset(in); + + update_polled_stuff_if_runtime(); + + err = restore_game_scripts(in, pp, r_data); + if (!err) + return err; + restore_game_room_state(in); + restore_game_play(in, r_data); + ReadMoveList_Aligned(in); + + // save pointer members before reading + char* gswas=game.globalscript; + ccScript* compsc=game.compiled_script; + CharacterInfo* chwas=game.chars; + WordsDictionary *olddict = game.dict; + char* mesbk[MAXGLOBALMES]; + int numchwas = game.numcharacters; + for (vv=0;vvReadInt32() != MAGICNUMBER+1) + { + return new SavegameError(kSvgErr_InconsistentFormat, "MAGICNUMBER not found before Audio Clips."); + } + + err = restore_game_audioclips_and_crossfade(in, r_data); + if (!err) + return err; + + auto pluginFileHandle = AGSE_RESTOREGAME; + pl_set_file_handle(pluginFileHandle, in); + pl_run_plugin_hooks(AGSE_RESTOREGAME, pluginFileHandle); + pl_clear_file_handle(); + if (in->ReadInt32() != (unsigned)MAGICNUMBER) + return new SavegameError(kSvgErr_InconsistentPlugin); + + // save the new room music vol for later use + r_data.RoomVolume = (RoomVolumeMod)in->ReadInt32(); + + if (ccUnserializeAllObjects(in, &ccUnserializer)) + { + return new SavegameError(kSvgErr_GameObjectInitFailed, + String::FromFormat("Managed pool deserialization failed: %s.", ccErrorString.GetCStr())); + } + + // preserve legacy music type setting + current_music_type = in->ReadInt32(); + + return HSaveError::None(); +} + +int gameHasBeenRestored = 0; +int oldeip; + +bool read_savedgame_description(const String &savedgame, String &description) +{ + SavegameDescription desc; + if (OpenSavegame(savedgame, desc, kSvgDesc_UserText)) + { + description = desc.UserText; + return true; + } + return false; +} + +bool read_savedgame_screenshot(const String &savedgame, int &want_shot) +{ + want_shot = 0; + + SavegameDescription desc; + HSaveError err = OpenSavegame(savedgame, desc, kSvgDesc_UserImage); + if (!err) + return false; + + if (desc.UserImage.get()) + { + int slot = spriteset.GetFreeIndex(); + if (slot > 0) + { + // add it into the sprite set + add_dynamic_sprite(slot, ReplaceBitmapWithSupportedFormat(desc.UserImage.release())); + want_shot = slot; + } + } + return true; +} + +HSaveError load_game(const String &path, int slotNumber, bool &data_overwritten) +{ + data_overwritten = false; + gameHasBeenRestored++; + + oldeip = our_eip; + our_eip = 2050; + + HSaveError err; + SavegameSource src; + SavegameDescription desc; + err = OpenSavegame(path, src, desc, kSvgDesc_EnvInfo); + + // saved in incompatible enviroment + if (!err) + return err; + // CHECKME: is this color depth test still essential? if yes, is there possible workaround? + else if (desc.ColorDepth != game.GetColorDepth()) + return new SavegameError(kSvgErr_DifferentColorDepth, String::FromFormat("Running: %d-bit, saved in: %d-bit.", game.GetColorDepth(), desc.ColorDepth)); + + // saved with different game file + if (Path::ComparePaths(desc.MainDataFilename, ResPaths.GamePak.Name)) + { + // [IKM] 2012-11-26: this is a workaround, indeed. + // Try to find wanted game's executable; if it does not exist, + // continue loading savedgame in current game, and pray for the best + get_install_dir_path(gamefilenamebuf, desc.MainDataFilename); + if (Common::File::TestReadFile(gamefilenamebuf)) + { + RunAGSGame (desc.MainDataFilename, 0, 0); + load_new_game_restore = slotNumber; + return HSaveError::None(); + } + Common::Debug::Printf(kDbgMsg_Warn, "WARNING: the saved game '%s' references game file '%s', but it cannot be found in the current directory. Trying to restore in the running game instead.", + path.GetCStr(), desc.MainDataFilename.GetCStr()); + } + + // do the actual restore + err = RestoreGameState(src.InputStream, src.Version); + data_overwritten = true; + if (!err) + return err; + src.InputStream.reset(); + our_eip = oldeip; + + // ensure keyboard buffer is clean + ags_clear_input_buffer(); + // 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 Common::String &path, int slot) +{ + bool data_overwritten; + 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()); + // 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(error); + else + Display(error); + return false; + } + return true; +} + +bool is_in_cutscene() +{ + return play.in_cutscene > 0; +} + +CutsceneSkipStyle get_cutscene_skipstyle() +{ + return static_cast(play.in_cutscene); +} + +void start_skipping_cutscene () { + play.fast_forward = 1; + // if a drop-down icon bar is up, remove it as it will pause the game + if (ifacepopped>=0) + remove_popup_interface(ifacepopped); + + // if a text message is currently displayed, remove it + if (is_text_overlay > 0) + remove_screen_overlay(OVER_TEXTMSG); + +} + +bool check_skip_cutscene_keypress (int kgn) { + + CutsceneSkipStyle skip = get_cutscene_skipstyle(); + if (skip == eSkipSceneAnyKey || skip == eSkipSceneKeyMouse || + (kgn == 27 && (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 == RIGHT && skip == eSkipSceneEscOrRMB)) + { + start_skipping_cutscene(); + return true; + } + return false; +} + +// Helper functions used by StartCutscene/EndCutscene, but also +// by SkipUntilCharacterStops +void initialize_skippable_cutscene() { + play.end_cutscene_music = -1; +} + +void stop_fast_forwarding() { + // when the skipping of a cutscene comes to an end, update things + play.fast_forward = 0; + setpal(); + if (play.end_cutscene_music >= 0) + newmusic(play.end_cutscene_music); + + { + AudioChannelsLock lock; + + // Restore actual volume of sounds + for (int aa = 0; aa <= MAX_SOUND_CHANNELS; aa++) + { + auto* ch = lock.GetChannelIfPlaying(aa); + if (ch) + { + ch->set_mute(false); + } + } + } // -- AudioChannelsLock + + update_music_volume(); +} + +// allowHotspot0 defines whether Hotspot 0 returns LOCTYPE_HOTSPOT +// or whether it returns 0 +int __GetLocationType(int xxx,int yyy, int allowHotspot0) { + getloctype_index = 0; + // If it's not in ProcessClick, then return 0 when over a GUI + if ((GetGUIAt(xxx, yyy) >= 0) && (getloctype_throughgui == 0)) + return 0; + + getloctype_throughgui = 0; + + const int scrx = xxx; + const int scry = yyy; + VpPoint vpt = play.ScreenToRoomDivDown(xxx, yyy); + if (vpt.second < 0) + return 0; + xxx = vpt.first.X; + yyy = vpt.first.Y; + if ((xxx>=thisroom.Width) | (xxx<0) | (yyy<0) | (yyy>=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 = thisroom.WalkBehindMask->GetPixel(xxx, yyy); + + if (wbat <= 0) wbat = 0; + else wbat = croom->walkbehind_base[wbat]; + + int winner = 0; + // if it's an Ignore Walkbehinds object, then ignore the walkbehind + if ((objat >= 0) && ((objs[objat].flags & OBJF_NOWALKBEHINDS) != 0)) + wbat = 0; + if ((charat >= 0) && ((game.chars[charat].flags & CHF_NOWALKBEHINDS) != 0)) + wbat = 0; + + if ((charat >= 0) && (objat >= 0)) { + if ((wbat > obj_lowest_yp) && (wbat > char_lowest_yp)) + winner = LOCTYPE_HOTSPOT; + else if (obj_lowest_yp > char_lowest_yp) + winner = LOCTYPE_OBJ; + else + winner = LOCTYPE_CHAR; + } + else if (charat >= 0) { + if (wbat > char_lowest_yp) + winner = LOCTYPE_HOTSPOT; + else + winner = LOCTYPE_CHAR; + } + else if (objat >= 0) { + if (wbat > 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) + getloctype_index = hsat; + else if (winner == LOCTYPE_CHAR) + getloctype_index = charat; + else if (winner == LOCTYPE_OBJ) + getloctype_index = objat; + + return winner; +} + +// Called whenever game looses input focus +void display_switch_out() +{ + switched_away = true; + ags_clear_input_buffer(); + // Always unlock mouse when switching out from the game + Mouse::UnlockFromWindow(); + platform->DisplaySwitchOut(); + platform->ExitFullscreenMode(); +} + +void display_switch_out_suspend() +{ + // this is only called if in SWITCH_PAUSE mode + //debug_script_warn("display_switch_out"); + display_switch_out(); + + switching_away_from_game++; + + platform->PauseApplication(); + + // allow background running temporarily to halt the sound + if (set_display_switch_mode(SWITCH_BACKGROUND) == -1) + set_display_switch_mode(SWITCH_BACKAMNESIA); + + { + // stop the sound stuttering + AudioChannelsLock lock; + for (int i = 0; i <= MAX_SOUND_CHANNELS; i++) { + auto* ch = lock.GetChannelIfPlaying(i); + if (ch) { + ch->pause(); + } + } + } // -- AudioChannelsLock + + platform->Delay(1000); + + // restore the callbacks + SetMultitasking(0); + + switching_away_from_game--; +} + +// Called whenever game gets input focus +void display_switch_in() +{ + switched_away = false; + if (gfxDriver) + { + DisplayMode mode = gfxDriver->GetDisplayMode(); + if (!mode.Windowed) + platform->EnterFullscreenMode(mode); + } + platform->DisplaySwitchIn(); + ags_clear_input_buffer(); + // If auto lock option is set, lock mouse to the game window + if (usetup.mouse_auto_lock && scsystem.windowed) + Mouse::TryLockToWindow(); +} + +void display_switch_in_resume() +{ + display_switch_in(); + + { + AudioChannelsLock lock; + for (int i = 0; i <= MAX_SOUND_CHANNELS; i++) { + auto* ch = lock.GetChannelIfPlaying(i); + if (ch) { + ch->resume(); + } + } + } // -- AudioChannelsLock + + // clear the screen if necessary + if (gfxDriver && gfxDriver->UsesMemoryBackBuffer()) + gfxDriver->ClearRectangle(0, 0, game.GetGameRes().Width - 1, game.GetGameRes().Height - 1, nullptr); + + platform->ResumeApplication(); +} + +void replace_tokens(const char*srcmes,char*destm, int maxlen) { + int 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>=game.numinvitems)) + quit("!Display: invalid inv item specified in @IN@"); + snprintf(tval,sizeof(tval),"%d",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)); + } + strcpy(destp,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 (game.messages[msnum-500] == nullptr) + return ""; + return get_translation(game.messages[msnum-500]); +} + +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) || (game.messages[msnum-500]==nullptr)) { + if (giveErr) + quit("!DisplayGlobalMessage: message does not exist"); + buffer[0] = 0; + return; + } + buffer[0] = 0; + replace_tokens(get_translation(game.messages[msnum-500]), buffer, maxlen); + return; + } + else if (msnum < 0 || (size_t)msnum >= thisroom.MessageCount) { + if (giveErr) + quit("!DisplayMessage: Invalid message number to display"); + buffer[0] = 0; + return; + } + + buffer[0]=0; + replace_tokens(get_translation(thisroom.Messages[msnum]), buffer, maxlen); +} + +bool unserialize_audio_script_object(int index, const char *objectType, const char *serializedData, int dataSize) +{ + if (strcmp(objectType, "AudioChannel") == 0) + { + ccDynamicAudio.Unserialize(index, serializedData, dataSize); + } + else if (strcmp(objectType, "AudioClip") == 0) + { + ccDynamicAudioClip.Unserialize(index, serializedData, dataSize); + } + else + { + return false; + } + return true; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// 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); +} + +// int (const char *token) +RuntimeScriptValue Sc_Game_DoOnceOnly(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(Game_DoOnceOnly, const char); +} + +// 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, 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, 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, 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, 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, myScriptStringImpl, Game_GetGlobalMessages); +} + +// const char* (int index) +RuntimeScriptValue Sc_Game_GetGlobalStrings(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT(const char, 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, 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, myScriptStringImpl, Game_GetTranslationFilename); +} + +// 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_VARGET_INT(game.audioClips.size()); +} + +RuntimeScriptValue Sc_Game_GetAudioClip(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT(ScriptAudioClip, ccDynamicAudioClip, Game_GetAudioClip); +} + +RuntimeScriptValue Sc_Game_IsPluginLoaded(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_BOOL_OBJ(pl_is_plugin_loaded, const char); +} + +RuntimeScriptValue Sc_Game_PlayVoiceClip(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_POBJ_PINT_PBOOL(ScriptAudioChannel, 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); +} + +void RegisterGameAPI() +{ + ccAddExternalStaticFunction("Game::IsAudioPlaying^1", Sc_Game_IsAudioPlaying); + ccAddExternalStaticFunction("Game::SetAudioTypeSpeechVolumeDrop^2", Sc_Game_SetAudioTypeSpeechVolumeDrop); + ccAddExternalStaticFunction("Game::SetAudioTypeVolume^3", Sc_Game_SetAudioTypeVolume); + ccAddExternalStaticFunction("Game::StopAudio^1", Sc_Game_StopAudio); + ccAddExternalStaticFunction("Game::ChangeTranslation^1", Sc_Game_ChangeTranslation); + ccAddExternalStaticFunction("Game::DoOnceOnly^1", Sc_Game_DoOnceOnly); + ccAddExternalStaticFunction("Game::GetColorFromRGB^3", Sc_Game_GetColorFromRGB); + ccAddExternalStaticFunction("Game::GetFrameCountForLoop^2", Sc_Game_GetFrameCountForLoop); + ccAddExternalStaticFunction("Game::GetLocationName^2", Sc_Game_GetLocationName); + ccAddExternalStaticFunction("Game::GetLoopCountForView^1", Sc_Game_GetLoopCountForView); + ccAddExternalStaticFunction("Game::GetMODPattern^0", Sc_Game_GetMODPattern); + ccAddExternalStaticFunction("Game::GetRunNextSettingForLoop^2", Sc_Game_GetRunNextSettingForLoop); + ccAddExternalStaticFunction("Game::GetSaveSlotDescription^1", Sc_Game_GetSaveSlotDescription); + ccAddExternalStaticFunction("Game::GetViewFrame^3", Sc_Game_GetViewFrame); + ccAddExternalStaticFunction("Game::InputBox^1", Sc_Game_InputBox); + ccAddExternalStaticFunction("Game::SetSaveGameDirectory^1", Sc_Game_SetSaveGameDirectory); + ccAddExternalStaticFunction("Game::StopSound^1", Sc_StopAllSounds); + ccAddExternalStaticFunction("Game::get_CharacterCount", Sc_Game_GetCharacterCount); + ccAddExternalStaticFunction("Game::get_DialogCount", Sc_Game_GetDialogCount); + ccAddExternalStaticFunction("Game::get_FileName", Sc_Game_GetFileName); + ccAddExternalStaticFunction("Game::get_FontCount", Sc_Game_GetFontCount); + ccAddExternalStaticFunction("Game::geti_GlobalMessages", Sc_Game_GetGlobalMessages); + ccAddExternalStaticFunction("Game::geti_GlobalStrings", Sc_Game_GetGlobalStrings); + ccAddExternalStaticFunction("Game::seti_GlobalStrings", Sc_SetGlobalString); + ccAddExternalStaticFunction("Game::get_GUICount", Sc_Game_GetGUICount); + ccAddExternalStaticFunction("Game::get_IgnoreUserInputAfterTextTimeoutMs", Sc_Game_GetIgnoreUserInputAfterTextTimeoutMs); + ccAddExternalStaticFunction("Game::set_IgnoreUserInputAfterTextTimeoutMs", Sc_Game_SetIgnoreUserInputAfterTextTimeoutMs); + ccAddExternalStaticFunction("Game::get_InSkippableCutscene", Sc_Game_GetInSkippableCutscene); + ccAddExternalStaticFunction("Game::get_InventoryItemCount", Sc_Game_GetInventoryItemCount); + ccAddExternalStaticFunction("Game::get_MinimumTextDisplayTimeMs", Sc_Game_GetMinimumTextDisplayTimeMs); + ccAddExternalStaticFunction("Game::set_MinimumTextDisplayTimeMs", Sc_Game_SetMinimumTextDisplayTimeMs); + ccAddExternalStaticFunction("Game::get_MouseCursorCount", Sc_Game_GetMouseCursorCount); + ccAddExternalStaticFunction("Game::get_Name", Sc_Game_GetName); + ccAddExternalStaticFunction("Game::set_Name", Sc_Game_SetName); + ccAddExternalStaticFunction("Game::get_NormalFont", Sc_Game_GetNormalFont); + ccAddExternalStaticFunction("Game::set_NormalFont", Sc_SetNormalFont); + ccAddExternalStaticFunction("Game::get_SkippingCutscene", Sc_Game_GetSkippingCutscene); + ccAddExternalStaticFunction("Game::get_SpeechFont", Sc_Game_GetSpeechFont); + ccAddExternalStaticFunction("Game::set_SpeechFont", Sc_SetSpeechFont); + ccAddExternalStaticFunction("Game::geti_SpriteWidth", Sc_Game_GetSpriteWidth); + ccAddExternalStaticFunction("Game::geti_SpriteHeight", Sc_Game_GetSpriteHeight); + ccAddExternalStaticFunction("Game::get_TextReadingSpeed", Sc_Game_GetTextReadingSpeed); + ccAddExternalStaticFunction("Game::set_TextReadingSpeed", Sc_Game_SetTextReadingSpeed); + ccAddExternalStaticFunction("Game::get_TranslationFilename", Sc_Game_GetTranslationFilename); + ccAddExternalStaticFunction("Game::get_UseNativeCoordinates", Sc_Game_GetUseNativeCoordinates); + ccAddExternalStaticFunction("Game::get_ViewCount", Sc_Game_GetViewCount); + ccAddExternalStaticFunction("Game::get_AudioClipCount", Sc_Game_GetAudioClipCount); + ccAddExternalStaticFunction("Game::geti_AudioClips", Sc_Game_GetAudioClip); + ccAddExternalStaticFunction("Game::IsPluginLoaded", Sc_Game_IsPluginLoaded); + ccAddExternalStaticFunction("Game::PlayVoiceClip", Sc_Game_PlayVoiceClip); + ccAddExternalStaticFunction("Game::SimulateKeyPress", Sc_Game_SimulateKeyPress); + + ccAddExternalStaticFunction("Game::get_Camera", Sc_Game_GetCamera); + ccAddExternalStaticFunction("Game::get_CameraCount", Sc_Game_GetCameraCount); + ccAddExternalStaticFunction("Game::geti_Cameras", Sc_Game_GetAnyCamera); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Game::IsAudioPlaying^1", (void*)Game_IsAudioPlaying); + ccAddExternalFunctionForPlugin("Game::SetAudioTypeSpeechVolumeDrop^2", (void*)Game_SetAudioTypeSpeechVolumeDrop); + ccAddExternalFunctionForPlugin("Game::SetAudioTypeVolume^3", (void*)Game_SetAudioTypeVolume); + ccAddExternalFunctionForPlugin("Game::StopAudio^1", (void*)Game_StopAudio); + ccAddExternalFunctionForPlugin("Game::ChangeTranslation^1", (void*)Game_ChangeTranslation); + ccAddExternalFunctionForPlugin("Game::DoOnceOnly^1", (void*)Game_DoOnceOnly); + ccAddExternalFunctionForPlugin("Game::GetColorFromRGB^3", (void*)Game_GetColorFromRGB); + ccAddExternalFunctionForPlugin("Game::GetFrameCountForLoop^2", (void*)Game_GetFrameCountForLoop); + ccAddExternalFunctionForPlugin("Game::GetLocationName^2", (void*)Game_GetLocationName); + ccAddExternalFunctionForPlugin("Game::GetLoopCountForView^1", (void*)Game_GetLoopCountForView); + ccAddExternalFunctionForPlugin("Game::GetMODPattern^0", (void*)Game_GetMODPattern); + ccAddExternalFunctionForPlugin("Game::GetRunNextSettingForLoop^2", (void*)Game_GetRunNextSettingForLoop); + ccAddExternalFunctionForPlugin("Game::GetSaveSlotDescription^1", (void*)Game_GetSaveSlotDescription); + ccAddExternalFunctionForPlugin("Game::GetViewFrame^3", (void*)Game_GetViewFrame); + ccAddExternalFunctionForPlugin("Game::InputBox^1", (void*)Game_InputBox); + ccAddExternalFunctionForPlugin("Game::SetSaveGameDirectory^1", (void*)Game_SetSaveGameDirectory); + ccAddExternalFunctionForPlugin("Game::StopSound^1", (void*)StopAllSounds); + ccAddExternalFunctionForPlugin("Game::get_CharacterCount", (void*)Game_GetCharacterCount); + ccAddExternalFunctionForPlugin("Game::get_DialogCount", (void*)Game_GetDialogCount); + ccAddExternalFunctionForPlugin("Game::get_FileName", (void*)Game_GetFileName); + ccAddExternalFunctionForPlugin("Game::get_FontCount", (void*)Game_GetFontCount); + ccAddExternalFunctionForPlugin("Game::geti_GlobalMessages", (void*)Game_GetGlobalMessages); + ccAddExternalFunctionForPlugin("Game::geti_GlobalStrings", (void*)Game_GetGlobalStrings); + ccAddExternalFunctionForPlugin("Game::seti_GlobalStrings", (void*)SetGlobalString); + ccAddExternalFunctionForPlugin("Game::get_GUICount", (void*)Game_GetGUICount); + ccAddExternalFunctionForPlugin("Game::get_IgnoreUserInputAfterTextTimeoutMs", (void*)Game_GetIgnoreUserInputAfterTextTimeoutMs); + ccAddExternalFunctionForPlugin("Game::set_IgnoreUserInputAfterTextTimeoutMs", (void*)Game_SetIgnoreUserInputAfterTextTimeoutMs); + ccAddExternalFunctionForPlugin("Game::get_InSkippableCutscene", (void*)Game_GetInSkippableCutscene); + ccAddExternalFunctionForPlugin("Game::get_InventoryItemCount", (void*)Game_GetInventoryItemCount); + ccAddExternalFunctionForPlugin("Game::get_MinimumTextDisplayTimeMs", (void*)Game_GetMinimumTextDisplayTimeMs); + ccAddExternalFunctionForPlugin("Game::set_MinimumTextDisplayTimeMs", (void*)Game_SetMinimumTextDisplayTimeMs); + ccAddExternalFunctionForPlugin("Game::get_MouseCursorCount", (void*)Game_GetMouseCursorCount); + ccAddExternalFunctionForPlugin("Game::get_Name", (void*)Game_GetName); + ccAddExternalFunctionForPlugin("Game::set_Name", (void*)Game_SetName); + ccAddExternalFunctionForPlugin("Game::get_NormalFont", (void*)Game_GetNormalFont); + ccAddExternalFunctionForPlugin("Game::set_NormalFont", (void*)SetNormalFont); + ccAddExternalFunctionForPlugin("Game::get_SkippingCutscene", (void*)Game_GetSkippingCutscene); + ccAddExternalFunctionForPlugin("Game::get_SpeechFont", (void*)Game_GetSpeechFont); + ccAddExternalFunctionForPlugin("Game::set_SpeechFont", (void*)SetSpeechFont); + ccAddExternalFunctionForPlugin("Game::geti_SpriteWidth", (void*)Game_GetSpriteWidth); + ccAddExternalFunctionForPlugin("Game::geti_SpriteHeight", (void*)Game_GetSpriteHeight); + ccAddExternalFunctionForPlugin("Game::get_TextReadingSpeed", (void*)Game_GetTextReadingSpeed); + ccAddExternalFunctionForPlugin("Game::set_TextReadingSpeed", (void*)Game_SetTextReadingSpeed); + ccAddExternalFunctionForPlugin("Game::get_TranslationFilename", (void*)Game_GetTranslationFilename); + ccAddExternalFunctionForPlugin("Game::get_UseNativeCoordinates", (void*)Game_GetUseNativeCoordinates); + ccAddExternalFunctionForPlugin("Game::get_ViewCount", (void*)Game_GetViewCount); + ccAddExternalFunctionForPlugin("Game::PlayVoiceClip", (void*)PlayVoiceClip); +} + +void RegisterStaticObjects() +{ + ccAddExternalStaticObject("game",&play, &GameStaticManager); + ccAddExternalStaticObject("gs_globals",&play.globalvars[0], &GlobalStaticManager); + ccAddExternalStaticObject("mouse",&scmouse, &GlobalStaticManager); + ccAddExternalStaticObject("palette",&palette[0], &GlobalStaticManager); + ccAddExternalStaticObject("system",&scsystem, &GlobalStaticManager); + ccAddExternalStaticObject("savegameindex",&play.filenumbers[0], &GlobalStaticManager); +} diff --git a/engines/ags/engine/ac/game.h b/engines/ags/engine/ac/game.h new file mode 100644 index 00000000000..9bed7f02c21 --- /dev/null +++ b/engines/ags/engine/ac/game.h @@ -0,0 +1,192 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AGS Runtime header +// +//============================================================================= + +#ifndef __AGS_EE_AC__GAME_H +#define __AGS_EE_AC__GAME_H + +#include "ac/dynobj/scriptviewframe.h" +#include "main/game_file.h" +#include "util/string.h" + +// Forward declaration +namespace AGS { namespace Common { class Bitmap; class Stream; } } +using namespace AGS; // FIXME later + +#define RAGMODE_PRESERVEGLOBALINT 1 +#define RAGMODE_LOADNOW 0x8000000 // just to make sure it's non-zero + +// Game parameter constants for backward-compatibility functions +#define GP_SPRITEWIDTH 1 +#define GP_SPRITEHEIGHT 2 +#define GP_NUMLOOPS 3 +#define GP_NUMFRAMES 4 +#define GP_ISRUNNEXTLOOP 5 +#define GP_FRAMESPEED 6 +#define GP_FRAMEIMAGE 7 +#define GP_FRAMESOUND 8 +#define GP_NUMGUIS 9 +#define GP_NUMOBJECTS 10 +#define GP_NUMCHARACTERS 11 +#define GP_NUMINVITEMS 12 +#define GP_ISFRAMEFLIPPED 13 + +enum CutsceneSkipStyle +{ + kSkipSceneUndefined = 0, + eSkipSceneEscOnly = 1, + eSkipSceneAnyKey = 2, + eSkipSceneMouse = 3, + eSkipSceneKeyMouse = 4, + eSkipSceneEscOrRMB = 5, + eSkipSceneScriptOnly = 6 +}; + +//============================================================================= +// Audio +//============================================================================= +#define VOL_CHANGEEXISTING 1678 +#define VOL_SETFUTUREDEFAULT 1679 +#define VOL_BOTH 1680 + +void Game_StopAudio(int audioType); +int Game_IsAudioPlaying(int audioType); +void Game_SetAudioTypeSpeechVolumeDrop(int audioType, int volumeDrop); +void Game_SetAudioTypeVolume(int audioType, int volume, int changeType); + +int Game_GetMODPattern(); + +//============================================================================= +// --- +//============================================================================= +int Game_GetDialogCount(); + +// Defines a custom save parent directory, which will replace $MYDOCS$/GameName +// when a new save directory is set from the script +bool SetCustomSaveParent(const Common::String &path); +// If explicit_path flag is false, the actual path will be constructed +// as a relative to system's user saves directory +bool SetSaveGameDirectoryPath(const char *newFolder, bool explicit_path = false); +int Game_SetSaveGameDirectory(const char *newFolder); +const char* Game_GetSaveSlotDescription(int slnum); + +const char* Game_GetGlobalStrings(int index); + +int Game_GetInventoryItemCount(); +int Game_GetFontCount(); +int Game_GetMouseCursorCount(); +int Game_GetCharacterCount(); +int Game_GetGUICount(); +int Game_GetViewCount(); +int Game_GetUseNativeCoordinates(); +int Game_GetSpriteWidth(int spriteNum); +int Game_GetSpriteHeight(int spriteNum); +int Game_GetLoopCountForView(int viewNumber); +int Game_GetRunNextSettingForLoop(int viewNumber, int loopNumber); +int Game_GetFrameCountForLoop(int viewNumber, int loopNumber); +ScriptViewFrame* Game_GetViewFrame(int viewNumber, int loopNumber, int frame); +int Game_DoOnceOnly(const char *token); + +int Game_GetTextReadingSpeed(); +void Game_SetTextReadingSpeed(int newTextSpeed); +int Game_GetMinimumTextDisplayTimeMs(); +void Game_SetMinimumTextDisplayTimeMs(int newTextMinTime); +int Game_GetIgnoreUserInputAfterTextTimeoutMs(); +void Game_SetIgnoreUserInputAfterTextTimeoutMs(int newValueMs); +const char *Game_GetFileName(); +const char *Game_GetName(); +void Game_SetName(const char *newName); + +int Game_GetSkippingCutscene(); +int Game_GetInSkippableCutscene(); + +int Game_GetColorFromRGB(int red, int grn, int blu); +const char* Game_InputBox(const char *msg); +const char* Game_GetLocationName(int x, int y); + +const char* Game_GetGlobalMessages(int index); + +int Game_GetSpeechFont(); +int Game_GetNormalFont(); + +const char* Game_GetTranslationFilename(); +int Game_ChangeTranslation(const char *newFilename); + +//============================================================================= + +void set_debug_mode(bool on); +void set_game_speed(int new_fps); +void setup_for_dialog(); +void restore_after_dialog(); +Common::String get_save_game_directory(); +Common::String get_save_game_suffix(); +void set_save_game_suffix(const Common::String &suffix); +Common::String get_save_game_path(int slotNum); +void restore_game_dialog(); +void save_game_dialog(); +void free_do_once_tokens(); +// Free all the memory associated with the game +void unload_game_file(); +void save_game(int slotn, const char*descript); +bool read_savedgame_description(const Common::String &savedgame, Common::String &description); +bool read_savedgame_screenshot(const Common::String &savedgame, int &want_shot); +// Tries to restore saved game and displays an error on failure; if the error occured +// too late, when the game data was already overwritten, shuts engine down. +bool try_restore_save(int slot); +bool try_restore_save(const Common::String &path, int slot); +void serialize_bitmap(const Common::Bitmap *thispic, Common::Stream *out); +// On Windows we could just use IIDFromString but this is platform-independant +void convert_guid_from_text_to_binary(const char *guidText, unsigned char *buffer); +Common::Bitmap *read_serialized_bitmap(Common::Stream *in); +void skip_serialized_bitmap(Common::Stream *in); +long write_screen_shot_for_vista(Common::Stream *out, Common::Bitmap *screenshot); + +bool is_in_cutscene(); +CutsceneSkipStyle get_cutscene_skipstyle(); +void start_skipping_cutscene (); +bool check_skip_cutscene_keypress(int kgn); +bool check_skip_cutscene_mclick(int mbut); +void initialize_skippable_cutscene(); +void stop_fast_forwarding(); + +int __GetLocationType(int xxx,int yyy, int allowHotspot0); + +// Called whenever game looses input focus +void display_switch_out(); +// Called whenever game gets input focus +void display_switch_in(); +// Called when the game looses input focus and must suspend +void display_switch_out_suspend(); +// Called when the game gets input focus and should resume +void display_switch_in_resume(); + +void replace_tokens(const char*srcmes,char*destm, int maxlen = 99999); +const char *get_global_message (int msnum); +void get_message_text (int msnum, char *buffer, char giveErr = 1); + +bool unserialize_audio_script_object(int index, const char *objectType, const char *serializedData, int dataSize); + +extern int in_new_room; +extern int new_room_pos; +extern int new_room_x, new_room_y, new_room_loop; +extern int displayed_room; +extern int frames_per_second; // fixed game fps, set by script +extern unsigned int loopcounter; +extern void set_loop_counter(unsigned int new_counter); +extern int game_paused; + +#endif // __AGS_EE_AC__GAME_H diff --git a/engines/ags/engine/ac/gamesetup.cpp b/engines/ags/engine/ac/gamesetup.cpp new file mode 100644 index 00000000000..88a22f4fcee --- /dev/null +++ b/engines/ags/engine/ac/gamesetup.cpp @@ -0,0 +1,45 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/wgt2allg.h" // DIGI_AUTODETECT & MIDI_AUTODETECT +#include "ac/gamesetup.h" + +GameSetup::GameSetup() +{ + digicard=DIGI_AUTODETECT; + midicard=MIDI_AUTODETECT; + mod_player=1; + no_speech_pack = false; + textheight = 0; + enable_antialiasing = false; + disable_exception_handling = false; + mouse_auto_lock = false; + override_script_os = -1; + override_multitasking = -1; + override_upscale = false; + mouse_speed = 1.f; + mouse_ctrl_when = kMouseCtrl_Fullscreen; + mouse_ctrl_enabled = true; + mouse_speed_def = kMouseSpeed_CurrentDisplay; + RenderAtScreenRes = false; + Supersampling = 1; + + Screen.DisplayMode.ScreenSize.MatchDeviceRatio = true; + Screen.DisplayMode.ScreenSize.SizeDef = kScreenDef_MaxDisplay; + Screen.DisplayMode.RefreshRate = 0; + Screen.DisplayMode.VSync = false; + Screen.DisplayMode.Windowed = false; + Screen.FsGameFrame = GameFrameSetup(kFrame_MaxProportional); + Screen.WinGameFrame = GameFrameSetup(kFrame_MaxRound); +} diff --git a/engines/ags/engine/ac/gamesetup.h b/engines/ags/engine/ac/gamesetup.h new file mode 100644 index 00000000000..672eb66d912 --- /dev/null +++ b/engines/ags/engine/ac/gamesetup.h @@ -0,0 +1,86 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GAMESETUP_H +#define __AC_GAMESETUP_H + +#include "main/graphics_mode.h" +#include "util/string.h" + + +// Mouse control activation type +enum MouseControlWhen +{ + kMouseCtrl_Never, // never control mouse (track system mouse position) + kMouseCtrl_Fullscreen, // control mouse in fullscreen only + kMouseCtrl_Always, // always control mouse (fullscreen and windowed) + kNumMouseCtrlOptions +}; + +// Mouse speed definition, specifies how the speed setting is applied to the mouse movement +enum MouseSpeedDef +{ + kMouseSpeed_Absolute, // apply speed multiplier directly + kMouseSpeed_CurrentDisplay, // keep speed/resolution relation based on current system display mode + kNumMouseSpeedDefs +}; + +using AGS::Common::String; + +// TODO: reconsider the purpose of this struct. +// Earlier I was trying to remove the uses of this struct from the engine +// and restrict it to only config/init stage, while applying its values to +// respective game/engine subcomponents at init stage. +// However, it did not work well at all times, and consequently I thought +// that engine may use a "config" object or combo of objects to store +// current user config, which may also be changed from script, and saved. +struct GameSetup { + int digicard; + int midicard; + int mod_player; + int textheight; // text height used on the certain built-in GUI // TODO: move out to game class? + bool no_speech_pack; + bool enable_antialiasing; + bool disable_exception_handling; + String data_files_dir; + String main_data_filename; + String main_data_filepath; + String install_dir; // optional custom install dir path + String install_audio_dir; // optional custom install audio dir path + String install_voice_dir; // optional custom install voice-over dir path + String user_data_dir; // directory to write savedgames and user files to + String shared_data_dir; // directory to write shared game files to + String translation; + bool mouse_auto_lock; + int override_script_os; + char override_multitasking; + bool override_upscale; + float mouse_speed; + MouseControlWhen mouse_ctrl_when; + bool mouse_ctrl_enabled; + MouseSpeedDef mouse_speed_def; + bool RenderAtScreenRes; // render sprites at screen resolution, as opposed to native one + int Supersampling; + + ScreenSetup Screen; + + GameSetup(); +}; + +// TODO: setup object is used for two purposes: temporarily storing config +// options before engine is initialized, and storing certain runtime variables. +// Perhaps it makes sense to separate those two group of vars at some point. +extern GameSetup usetup; + +#endif // __AC_GAMESETUP_H \ No newline at end of file diff --git a/engines/ags/engine/ac/gamestate.cpp b/engines/ags/engine/ac/gamestate.cpp new file mode 100644 index 00000000000..da4ec8079da --- /dev/null +++ b/engines/ags/engine/ac/gamestate.cpp @@ -0,0 +1,935 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include +#include "ac/draw.h" +#include "ac/game_version.h" +#include "ac/gamestate.h" +#include "ac/gamesetupstruct.h" +#include "ac/timer.h" +#include "ac/dynobj/scriptcamera.h" +#include "ac/dynobj/scriptsystem.h" +#include "ac/dynobj/scriptviewport.h" +#include "debug/debug_log.h" +#include "device/mousew32.h" +#include "game/customproperties.h" +#include "game/roomstruct.h" +#include "game/savegame_internal.h" +#include "main/engine.h" +#include "media/audio/audio_system.h" +#include "util/alignedstream.h" +#include "util/string_utils.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern RoomStruct thisroom; +extern CharacterInfo *playerchar; +extern ScriptSystem scsystem; + +GameState::GameState() +{ + _isAutoRoomViewport = true; + _mainViewportHasChanged = false; +} + +void GameState::Free() +{ + raw_drawing_surface.reset(); + FreeProperties(); +} + +bool GameState::IsAutoRoomViewport() const +{ + return _isAutoRoomViewport; +} + +void GameState::SetAutoRoomViewport(bool on) +{ + _isAutoRoomViewport = on; +} + +void GameState::SetMainViewport(const Rect &viewport) +{ + _mainViewport.SetRect(viewport); + Mouse::SetGraphicArea(); + scsystem.viewport_width = game_to_data_coord(_mainViewport.GetRect().GetWidth()); + scsystem.viewport_height = game_to_data_coord(_mainViewport.GetRect().GetHeight()); + _mainViewportHasChanged = true; +} + +const Rect &GameState::GetMainViewport() const +{ + return _mainViewport.GetRect(); +} + +const Rect &GameState::GetUIViewport() const +{ + return _uiViewport.GetRect(); +} + +PViewport GameState::GetRoomViewport(int index) const +{ + return _roomViewports[index]; +} + +const std::vector &GameState::GetRoomViewportsZOrdered() const +{ + return _roomViewportsSorted; +} + +PViewport GameState::GetRoomViewportAt(int x, int y) const +{ + // We iterate backwards, because in AGS low z-order means bottom + for (auto it = _roomViewportsSorted.rbegin(); it != _roomViewportsSorted.rend(); ++it) + if ((*it)->IsVisible() && (*it)->GetRect().IsInside(x, y)) + return *it; + return nullptr; +} + +Rect GameState::GetUIViewportAbs() const +{ + return Rect::MoveBy(_uiViewport.GetRect(), _mainViewport.GetRect().Left, _mainViewport.GetRect().Top); +} + +Rect GameState::GetRoomViewportAbs(int index) const +{ + return Rect::MoveBy(_roomViewports[index]->GetRect(), _mainViewport.GetRect().Left, _mainViewport.GetRect().Top); +} + +void GameState::SetUIViewport(const Rect &viewport) +{ + _uiViewport.SetRect(viewport); +} + +static bool ViewportZOrder(const PViewport e1, const PViewport e2) +{ + return e1->GetZOrder() < e2->GetZOrder(); +} + +void GameState::UpdateViewports() +{ + if (_mainViewportHasChanged) + { + on_mainviewport_changed(); + _mainViewportHasChanged = false; + } + if (_roomViewportZOrderChanged) + { + auto old_sort = _roomViewportsSorted; + _roomViewportsSorted = _roomViewports; + std::sort(_roomViewportsSorted.begin(), _roomViewportsSorted.end(), ViewportZOrder); + for (size_t i = 0; i < _roomViewportsSorted.size(); ++i) + { + if (i >= old_sort.size() || _roomViewportsSorted[i] != old_sort[i]) + _roomViewportsSorted[i]->SetChangedVisible(); + } + _roomViewportZOrderChanged = false; + } + size_t vp_changed = -1; + for (size_t i = _roomViewportsSorted.size(); i-- > 0;) + { + auto vp = _roomViewportsSorted[i]; + if (vp->HasChangedSize() || vp->HasChangedPosition() || vp->HasChangedVisible()) + { + vp_changed = i; + on_roomviewport_changed(vp.get()); + vp->ClearChangedFlags(); + } + } + if (vp_changed != -1) + detect_roomviewport_overlaps(vp_changed); + for (auto cam : _roomCameras) + { + if (cam->HasChangedSize() || cam->HasChangedPosition()) + { + on_roomcamera_changed(cam.get()); + cam->ClearChangedFlags(); + } + } +} + +void GameState::InvalidateViewportZOrder() +{ + _roomViewportZOrderChanged = true; +} + +PCamera GameState::GetRoomCamera(int index) const +{ + return _roomCameras[index]; +} + +void GameState::UpdateRoomCameras() +{ + for (size_t i = 0; i < _roomCameras.size(); ++i) + UpdateRoomCamera(i); +} + +void GameState::UpdateRoomCamera(int index) +{ + auto cam = _roomCameras[index]; + const Rect &rc = cam->GetRect(); + const Size real_room_sz = Size(data_to_game_coord(thisroom.Width), data_to_game_coord(thisroom.Height)); + if ((real_room_sz.Width > rc.GetWidth()) || (real_room_sz.Height > rc.GetHeight())) + { + // TODO: split out into Camera Behavior + if (!cam->IsLocked()) + { + int x = data_to_game_coord(playerchar->x) - rc.GetWidth() / 2; + int y = data_to_game_coord(playerchar->y) - rc.GetHeight() / 2; + cam->SetAt(x, y); + } + } + else + { + cam->SetAt(0, 0); + } +} + +Point GameState::RoomToScreen(int roomx, int roomy) +{ + return _roomViewports[0]->RoomToScreen(roomx, roomy, false).first; +} + +int GameState::RoomToScreenX(int roomx) +{ + return _roomViewports[0]->RoomToScreen(roomx, 0, false).first.X; +} + +int GameState::RoomToScreenY(int roomy) +{ + return _roomViewports[0]->RoomToScreen(0, roomy, false).first.Y; +} + +VpPoint GameState::ScreenToRoomImpl(int scrx, int scry, int view_index, bool clip_viewport, bool convert_cam_to_data) +{ + PViewport view; + if (view_index < 0) + { + view = GetRoomViewportAt(scrx, scry); + if (!view) + return std::make_pair(Point(), -1); + } + else + { + view = _roomViewports[view_index]; + } + return view->ScreenToRoom(scrx, scry, clip_viewport, convert_cam_to_data); +} + +VpPoint GameState::ScreenToRoom(int scrx, int scry) +{ + if (game.options[OPT_BASESCRIPTAPI] >= kScriptAPI_v3507) + return ScreenToRoomImpl(scrx, scry, -1, true, false); + return ScreenToRoomImpl(scrx, scry, 0, false, false); +} + +VpPoint GameState::ScreenToRoomDivDown(int scrx, int scry) +{ + if (game.options[OPT_BASESCRIPTAPI] >= kScriptAPI_v3507) + return ScreenToRoomImpl(scrx, scry, -1, true, true); + return ScreenToRoomImpl(scrx, scry, 0, false, true); +} + +void GameState::CreatePrimaryViewportAndCamera() +{ + if (_roomViewports.size() == 0) + { + play.CreateRoomViewport(); + play.RegisterRoomViewport(0); + } + if (_roomCameras.size() == 0) + { + play.CreateRoomCamera(); + play.RegisterRoomCamera(0); + } + _roomViewports[0]->LinkCamera(_roomCameras[0]); + _roomCameras[0]->LinkToViewport(_roomViewports[0]); +} + +PViewport GameState::CreateRoomViewport() +{ + int index = (int)_roomViewports.size(); + PViewport viewport(new Viewport()); + viewport->SetID(index); + viewport->SetRect(_mainViewport.GetRect()); + ScriptViewport *scv = new ScriptViewport(index); + _roomViewports.push_back(viewport); + _scViewportRefs.push_back(std::make_pair(scv, 0)); + _roomViewportsSorted.push_back(viewport); + _roomViewportZOrderChanged = true; + on_roomviewport_created(index); + return viewport; +} + +ScriptViewport *GameState::RegisterRoomViewport(int index, int32_t handle) +{ + if (index < 0 || (size_t)index >= _roomViewports.size()) + return nullptr; + auto &scobj = _scViewportRefs[index]; + if (handle == 0) + { + handle = ccRegisterManagedObject(scobj.first, scobj.first); + ccAddObjectReference(handle); // one reference for the GameState + } + else + { + ccRegisterUnserializedObject(handle, scobj.first, scobj.first); + } + scobj.second = handle; + return scobj.first; +} + +void GameState::DeleteRoomViewport(int index) +{ + // NOTE: viewport 0 can not be deleted + if (index <= 0 || (size_t)index >= _roomViewports.size()) + return; + auto scobj = _scViewportRefs[index]; + scobj.first->Invalidate(); + ccReleaseObjectReference(scobj.second); + auto cam = _roomViewports[index]->GetCamera(); + if (cam) + cam->UnlinkFromViewport(index); + _roomViewports.erase(_roomViewports.begin() + index); + _scViewportRefs.erase(_scViewportRefs.begin() + index); + for (size_t i = index; i < _roomViewports.size(); ++i) + { + _roomViewports[i]->SetID(i); + _scViewportRefs[i].first->SetID(i); + } + for (size_t i = 0; i < _roomViewportsSorted.size(); ++i) + { + if (_roomViewportsSorted[i]->GetID() == index) + { + _roomViewportsSorted.erase(_roomViewportsSorted.begin() + i); + break; + } + } + on_roomviewport_deleted(index); +} + +int GameState::GetRoomViewportCount() const +{ + return (int)_roomViewports.size(); +} + +PCamera GameState::CreateRoomCamera() +{ + int index = (int)_roomCameras.size(); + PCamera camera(new Camera()); + camera->SetID(index); + camera->SetAt(0, 0); + camera->SetSize(_mainViewport.GetRect().GetSize()); + ScriptCamera *scam = new ScriptCamera(index); + _scCameraRefs.push_back(std::make_pair(scam, 0)); + _roomCameras.push_back(camera); + return camera; +} + +ScriptCamera *GameState::RegisterRoomCamera(int index, int32_t handle) +{ + if (index < 0 || (size_t)index >= _roomCameras.size()) + return nullptr; + auto &scobj = _scCameraRefs[index]; + if (handle == 0) + { + handle = ccRegisterManagedObject(scobj.first, scobj.first); + ccAddObjectReference(handle); // one reference for the GameState + } + else + { + ccRegisterUnserializedObject(handle, scobj.first, scobj.first); + } + scobj.second = handle; + return scobj.first; +} + +void GameState::DeleteRoomCamera(int index) +{ + // NOTE: camera 0 can not be deleted + if (index <= 0 || (size_t)index >= _roomCameras.size()) + return; + auto scobj = _scCameraRefs[index]; + scobj.first->Invalidate(); + ccReleaseObjectReference(scobj.second); + for (auto& viewref : _roomCameras[index]->GetLinkedViewports()) + { + auto view = viewref.lock(); + if (view) + view->LinkCamera(nullptr); + } + _roomCameras.erase(_roomCameras.begin() + index); + _scCameraRefs.erase(_scCameraRefs.begin() + index); + for (size_t i = index; i < _roomCameras.size(); ++i) + { + _roomCameras[i]->SetID(i); + _scCameraRefs[i].first->SetID(i); + } +} + +int GameState::GetRoomCameraCount() const +{ + return (int)_roomCameras.size(); +} + +ScriptViewport *GameState::GetScriptViewport(int index) +{ + if (index < 0 || (size_t)index >= _roomViewports.size()) + return NULL; + return _scViewportRefs[index].first; +} + +ScriptCamera *GameState::GetScriptCamera(int index) +{ + if (index < 0 || (size_t)index >= _roomCameras.size()) + return NULL; + return _scCameraRefs[index].first; +} + +bool GameState::IsIgnoringInput() const +{ + return AGS_Clock::now() < _ignoreUserInputUntilTime; +} + +void GameState::SetIgnoreInput(int timeout_ms) +{ + if (AGS_Clock::now() + std::chrono::milliseconds(timeout_ms) > _ignoreUserInputUntilTime) + _ignoreUserInputUntilTime = AGS_Clock::now() + std::chrono::milliseconds(timeout_ms); +} + +void GameState::ClearIgnoreInput() +{ + _ignoreUserInputUntilTime = AGS_Clock::now(); +} + +bool GameState::IsBlockingVoiceSpeech() const +{ + return speech_has_voice && speech_voice_blocking; +} + +bool GameState::IsNonBlockingVoiceSpeech() const +{ + return speech_has_voice && !speech_voice_blocking; +} + +bool GameState::ShouldPlayVoiceSpeech() const +{ + return !play.fast_forward && + (play.want_speech >= 1) && (!ResPaths.SpeechPak.Name.IsEmpty()); +} + +void GameState::ReadFromSavegame(Common::Stream *in, GameStateSvgVersion svg_ver, RestoredData &r_data) +{ + const bool old_save = svg_ver < kGSSvgVersion_Initial; + score = in->ReadInt32(); + usedmode = in->ReadInt32(); + disabled_user_interface = in->ReadInt32(); + gscript_timer = in->ReadInt32(); + debug_mode = in->ReadInt32(); + in->ReadArrayOfInt32(globalvars, MAXGLOBALVARS); + messagetime = in->ReadInt32(); + usedinv = in->ReadInt32(); + inv_top = in->ReadInt32(); + inv_numdisp = in->ReadInt32(); + obsolete_inv_numorder = in->ReadInt32(); + inv_numinline = in->ReadInt32(); + text_speed = in->ReadInt32(); + sierra_inv_color = in->ReadInt32(); + talkanim_speed = in->ReadInt32(); + inv_item_wid = in->ReadInt32(); + inv_item_hit = in->ReadInt32(); + speech_text_shadow = in->ReadInt32(); + swap_portrait_side = in->ReadInt32(); + speech_textwindow_gui = in->ReadInt32(); + follow_change_room_timer = in->ReadInt32(); + totalscore = in->ReadInt32(); + skip_display = in->ReadInt32(); + no_multiloop_repeat = in->ReadInt32(); + roomscript_finished = in->ReadInt32(); + used_inv_on = in->ReadInt32(); + no_textbg_when_voice = in->ReadInt32(); + max_dialogoption_width = in->ReadInt32(); + no_hicolor_fadein = in->ReadInt32(); + bgspeech_game_speed = in->ReadInt32(); + bgspeech_stay_on_display = in->ReadInt32(); + unfactor_speech_from_textlength = in->ReadInt32(); + mp3_loop_before_end = in->ReadInt32(); + speech_music_drop = in->ReadInt32(); + in_cutscene = in->ReadInt32(); + fast_forward = in->ReadInt32(); + room_width = in->ReadInt32(); + room_height = in->ReadInt32(); + game_speed_modifier = in->ReadInt32(); + score_sound = in->ReadInt32(); + takeover_data = in->ReadInt32(); + replay_hotkey_unused = in->ReadInt32(); + dialog_options_x = in->ReadInt32(); + dialog_options_y = in->ReadInt32(); + narrator_speech = in->ReadInt32(); + ambient_sounds_persist = in->ReadInt32(); + lipsync_speed = in->ReadInt32(); + close_mouth_speech_time = in->ReadInt32(); + disable_antialiasing = in->ReadInt32(); + text_speed_modifier = in->ReadInt32(); + if (svg_ver < kGSSvgVersion_350) + text_align = ConvertLegacyScriptAlignment((LegacyScriptAlignment)in->ReadInt32()); + else + text_align = (HorAlignment)in->ReadInt32(); + speech_bubble_width = in->ReadInt32(); + min_dialogoption_width = in->ReadInt32(); + disable_dialog_parser = in->ReadInt32(); + anim_background_speed = in->ReadInt32(); // the setting for this room + top_bar_backcolor= in->ReadInt32(); + top_bar_textcolor = in->ReadInt32(); + top_bar_bordercolor = in->ReadInt32(); + top_bar_borderwidth = in->ReadInt32(); + top_bar_ypos = in->ReadInt32(); + screenshot_width = in->ReadInt32(); + screenshot_height = in->ReadInt32(); + top_bar_font = in->ReadInt32(); + if (svg_ver < kGSSvgVersion_350) + speech_text_align = ConvertLegacyScriptAlignment((LegacyScriptAlignment)in->ReadInt32()); + else + speech_text_align = (HorAlignment)in->ReadInt32(); + auto_use_walkto_points = in->ReadInt32(); + inventory_greys_out = in->ReadInt32(); + skip_speech_specific_key = in->ReadInt32(); + abort_key = in->ReadInt32(); + fade_to_red = in->ReadInt32(); + fade_to_green = in->ReadInt32(); + fade_to_blue = in->ReadInt32(); + show_single_dialog_option = in->ReadInt32(); + keep_screen_during_instant_transition = in->ReadInt32(); + read_dialog_option_colour = in->ReadInt32(); + stop_dialog_at_end = in->ReadInt32(); + speech_portrait_placement = in->ReadInt32(); + speech_portrait_x = in->ReadInt32(); + speech_portrait_y = in->ReadInt32(); + speech_display_post_time_ms = in->ReadInt32(); + dialog_options_highlight_color = in->ReadInt32(); + if (old_save) + in->ReadArrayOfInt32(reserved, GAME_STATE_RESERVED_INTS); + // ** up to here is referenced in the script "game." object + if (old_save) + { + in->ReadInt32(); // recording + in->ReadInt32(); // playback + in->ReadInt16(); // gamestep + } + randseed = in->ReadInt32(); // random seed + player_on_region = in->ReadInt32(); // player's current region + if (old_save) + in->ReadInt32(); // screen_is_faded_out + check_interaction_only = in->ReadInt32(); + bg_frame = in->ReadInt32(); + bg_anim_delay = in->ReadInt32(); // for animating backgrounds + music_vol_was = in->ReadInt32(); // before the volume drop + wait_counter = in->ReadInt16(); + mboundx1 = in->ReadInt16(); + mboundx2 = in->ReadInt16(); + mboundy1 = in->ReadInt16(); + mboundy2 = in->ReadInt16(); + fade_effect = in->ReadInt32(); + bg_frame_locked = in->ReadInt32(); + in->ReadArrayOfInt32(globalscriptvars, MAXGSVALUES); + cur_music_number = in->ReadInt32(); + music_repeat = in->ReadInt32(); + music_master_volume = in->ReadInt32(); + digital_master_volume = in->ReadInt32(); + in->Read(walkable_areas_on, MAX_WALK_AREAS+1); + screen_flipped = in->ReadInt16(); + if (svg_ver < kGSSvgVersion_3510) + { + short offsets_locked = in->ReadInt16(); + if (offsets_locked != 0) + r_data.Camera0_Flags = kSvgCamPosLocked; + } + entered_at_x = in->ReadInt32(); + entered_at_y = in->ReadInt32(); + entered_edge = in->ReadInt32(); + want_speech = in->ReadInt32(); + cant_skip_speech = in->ReadInt32(); + in->ReadArrayOfInt32(script_timers, MAX_TIMERS); + sound_volume = in->ReadInt32(); + speech_volume = in->ReadInt32(); + normal_font = in->ReadInt32(); + speech_font = in->ReadInt32(); + key_skip_wait = in->ReadInt8(); + swap_portrait_lastchar = in->ReadInt32(); + separate_music_lib = in->ReadInt32(); + in_conversation = in->ReadInt32(); + screen_tint = in->ReadInt32(); + num_parsed_words = in->ReadInt32(); + in->ReadArrayOfInt16( parsed_words, MAX_PARSED_WORDS); + in->Read( bad_parsed_word, 100); + raw_color = in->ReadInt32(); + if (old_save) + in->ReadArrayOfInt32(raw_modified, MAX_ROOM_BGFRAMES); + in->ReadArrayOfInt16( filenumbers, MAXSAVEGAMES); + if (old_save) + in->ReadInt32(); // room_changes + mouse_cursor_hidden = in->ReadInt32(); + silent_midi = in->ReadInt32(); + silent_midi_channel = in->ReadInt32(); + current_music_repeating = in->ReadInt32(); + shakesc_delay = in->ReadInt32(); + shakesc_amount = in->ReadInt32(); + shakesc_length = in->ReadInt32(); + rtint_red = in->ReadInt32(); + rtint_green = in->ReadInt32(); + rtint_blue = in->ReadInt32(); + rtint_level = in->ReadInt32(); + rtint_light = in->ReadInt32(); + if (!old_save || loaded_game_file_version >= kGameVersion_340_4) + rtint_enabled = in->ReadBool(); + else + rtint_enabled = rtint_level > 0; + end_cutscene_music = in->ReadInt32(); + skip_until_char_stops = in->ReadInt32(); + get_loc_name_last_time = in->ReadInt32(); + get_loc_name_save_cursor = in->ReadInt32(); + restore_cursor_mode_to = in->ReadInt32(); + restore_cursor_image_to = in->ReadInt32(); + music_queue_size = in->ReadInt16(); + in->ReadArrayOfInt16( music_queue, MAX_QUEUED_MUSIC); + new_music_queue_size = in->ReadInt16(); + if (!old_save) + { + for (int i = 0; i < MAX_QUEUED_MUSIC; ++i) + { + new_music_queue[i].ReadFromFile(in); + } + } + + crossfading_out_channel = in->ReadInt16(); + crossfade_step = in->ReadInt16(); + crossfade_out_volume_per_step = in->ReadInt16(); + crossfade_initial_volume_out = in->ReadInt16(); + crossfading_in_channel = in->ReadInt16(); + crossfade_in_volume_per_step = in->ReadInt16(); + crossfade_final_volume_in = in->ReadInt16(); + + if (old_save) + ReadQueuedAudioItems_Aligned(in); + + in->Read(takeover_from, 50); + in->Read(playmp3file_name, PLAYMP3FILE_MAX_FILENAME_LEN); + in->Read(globalstrings, MAXGLOBALSTRINGS * MAX_MAXSTRLEN); + in->Read(lastParserEntry, MAX_MAXSTRLEN); + in->Read(game_name, 100); + ground_level_areas_disabled = in->ReadInt32(); + next_screen_transition = in->ReadInt32(); + in->ReadInt32(); // gamma_adjustment -- do not apply gamma level from savegame + temporarily_turned_off_character = in->ReadInt16(); + inv_backwards_compatibility = in->ReadInt16(); + if (old_save) + { + in->ReadInt32(); // gui_draw_order + in->ReadInt32(); // do_once_tokens; + } + int num_do_once_tokens = in->ReadInt32(); + do_once_tokens.resize(num_do_once_tokens); + if (!old_save) + { + for (int i = 0; i < num_do_once_tokens; ++i) + { + StrUtil::ReadString(do_once_tokens[i], in); + } + } + text_min_display_time_ms = in->ReadInt32(); + ignore_user_input_after_text_timeout_ms = in->ReadInt32(); + if (svg_ver < kGSSvgVersion_3509) + in->ReadInt32(); // ignore_user_input_until_time -- do not apply from savegame + if (old_save) + in->ReadArrayOfInt32(default_audio_type_volumes, MAX_AUDIO_TYPES); + if (svg_ver >= kGSSvgVersion_3509) + { + int voice_speech_flags = in->ReadInt32(); + speech_has_voice = voice_speech_flags != 0; + speech_voice_blocking = (voice_speech_flags & 0x02) != 0; + } +} + +void GameState::WriteForSavegame(Common::Stream *out) const +{ + // NOTE: following parameters are never saved: + // recording, playback, gamestep, screen_is_faded_out, room_changes + out->WriteInt32(score); + out->WriteInt32(usedmode); + out->WriteInt32(disabled_user_interface); + out->WriteInt32(gscript_timer); + out->WriteInt32(debug_mode); + out->WriteArrayOfInt32(globalvars, MAXGLOBALVARS); + out->WriteInt32(messagetime); + out->WriteInt32(usedinv); + out->WriteInt32(inv_top); + out->WriteInt32(inv_numdisp); + out->WriteInt32(obsolete_inv_numorder); + out->WriteInt32(inv_numinline); + out->WriteInt32(text_speed); + out->WriteInt32(sierra_inv_color); + out->WriteInt32(talkanim_speed); + out->WriteInt32(inv_item_wid); + out->WriteInt32(inv_item_hit); + out->WriteInt32(speech_text_shadow); + out->WriteInt32(swap_portrait_side); + out->WriteInt32(speech_textwindow_gui); + out->WriteInt32(follow_change_room_timer); + out->WriteInt32(totalscore); + out->WriteInt32(skip_display); + out->WriteInt32(no_multiloop_repeat); + out->WriteInt32(roomscript_finished); + out->WriteInt32(used_inv_on); + out->WriteInt32(no_textbg_when_voice); + out->WriteInt32(max_dialogoption_width); + out->WriteInt32(no_hicolor_fadein); + out->WriteInt32(bgspeech_game_speed); + out->WriteInt32(bgspeech_stay_on_display); + out->WriteInt32(unfactor_speech_from_textlength); + out->WriteInt32(mp3_loop_before_end); + out->WriteInt32(speech_music_drop); + out->WriteInt32(in_cutscene); + out->WriteInt32(fast_forward); + out->WriteInt32(room_width); + out->WriteInt32(room_height); + out->WriteInt32(game_speed_modifier); + out->WriteInt32(score_sound); + out->WriteInt32(takeover_data); + out->WriteInt32(replay_hotkey_unused); // StartRecording: not supported + out->WriteInt32(dialog_options_x); + out->WriteInt32(dialog_options_y); + out->WriteInt32(narrator_speech); + out->WriteInt32(ambient_sounds_persist); + out->WriteInt32(lipsync_speed); + out->WriteInt32(close_mouth_speech_time); + out->WriteInt32(disable_antialiasing); + out->WriteInt32(text_speed_modifier); + out->WriteInt32(text_align); + out->WriteInt32(speech_bubble_width); + out->WriteInt32(min_dialogoption_width); + out->WriteInt32(disable_dialog_parser); + out->WriteInt32(anim_background_speed); // the setting for this room + out->WriteInt32(top_bar_backcolor); + out->WriteInt32(top_bar_textcolor); + out->WriteInt32(top_bar_bordercolor); + out->WriteInt32(top_bar_borderwidth); + out->WriteInt32(top_bar_ypos); + out->WriteInt32(screenshot_width); + out->WriteInt32(screenshot_height); + out->WriteInt32(top_bar_font); + out->WriteInt32(speech_text_align); + out->WriteInt32(auto_use_walkto_points); + out->WriteInt32(inventory_greys_out); + out->WriteInt32(skip_speech_specific_key); + out->WriteInt32(abort_key); + out->WriteInt32(fade_to_red); + out->WriteInt32(fade_to_green); + out->WriteInt32(fade_to_blue); + out->WriteInt32(show_single_dialog_option); + out->WriteInt32(keep_screen_during_instant_transition); + out->WriteInt32(read_dialog_option_colour); + out->WriteInt32(stop_dialog_at_end); + out->WriteInt32(speech_portrait_placement); + out->WriteInt32(speech_portrait_x); + out->WriteInt32(speech_portrait_y); + out->WriteInt32(speech_display_post_time_ms); + out->WriteInt32(dialog_options_highlight_color); + // ** up to here is referenced in the script "game." object + out->WriteInt32(randseed); // random seed + out->WriteInt32( player_on_region); // player's current region + out->WriteInt32( check_interaction_only); + out->WriteInt32( bg_frame); + out->WriteInt32( bg_anim_delay); // for animating backgrounds + out->WriteInt32( music_vol_was); // before the volume drop + out->WriteInt16(wait_counter); + out->WriteInt16(mboundx1); + out->WriteInt16(mboundx2); + out->WriteInt16(mboundy1); + out->WriteInt16(mboundy2); + out->WriteInt32( fade_effect); + out->WriteInt32( bg_frame_locked); + out->WriteArrayOfInt32(globalscriptvars, MAXGSVALUES); + out->WriteInt32( cur_music_number); + out->WriteInt32( music_repeat); + out->WriteInt32( music_master_volume); + out->WriteInt32( digital_master_volume); + out->Write(walkable_areas_on, MAX_WALK_AREAS+1); + out->WriteInt16( screen_flipped); + out->WriteInt32( entered_at_x); + out->WriteInt32( entered_at_y); + out->WriteInt32( entered_edge); + out->WriteInt32( want_speech); + out->WriteInt32( cant_skip_speech); + out->WriteArrayOfInt32(script_timers, MAX_TIMERS); + out->WriteInt32( sound_volume); + out->WriteInt32( speech_volume); + out->WriteInt32( normal_font); + out->WriteInt32( speech_font); + out->WriteInt8( key_skip_wait); + out->WriteInt32( swap_portrait_lastchar); + out->WriteInt32( separate_music_lib); + out->WriteInt32( in_conversation); + out->WriteInt32( screen_tint); + out->WriteInt32( num_parsed_words); + out->WriteArrayOfInt16( parsed_words, MAX_PARSED_WORDS); + out->Write( bad_parsed_word, 100); + out->WriteInt32( raw_color); + out->WriteArrayOfInt16( filenumbers, MAXSAVEGAMES); + out->WriteInt32( mouse_cursor_hidden); + out->WriteInt32( silent_midi); + out->WriteInt32( silent_midi_channel); + out->WriteInt32( current_music_repeating); + out->WriteInt32( shakesc_delay); + out->WriteInt32( shakesc_amount); + out->WriteInt32( shakesc_length); + out->WriteInt32( rtint_red); + out->WriteInt32( rtint_green); + out->WriteInt32( rtint_blue); + out->WriteInt32( rtint_level); + out->WriteInt32( rtint_light); + out->WriteBool(rtint_enabled); + out->WriteInt32( end_cutscene_music); + out->WriteInt32( skip_until_char_stops); + out->WriteInt32( get_loc_name_last_time); + out->WriteInt32( get_loc_name_save_cursor); + out->WriteInt32( restore_cursor_mode_to); + out->WriteInt32( restore_cursor_image_to); + out->WriteInt16( music_queue_size); + out->WriteArrayOfInt16( music_queue, MAX_QUEUED_MUSIC); + out->WriteInt16(new_music_queue_size); + for (int i = 0; i < MAX_QUEUED_MUSIC; ++i) + { + new_music_queue[i].WriteToFile(out); + } + + out->WriteInt16( crossfading_out_channel); + out->WriteInt16( crossfade_step); + out->WriteInt16( crossfade_out_volume_per_step); + out->WriteInt16( crossfade_initial_volume_out); + out->WriteInt16( crossfading_in_channel); + out->WriteInt16( crossfade_in_volume_per_step); + out->WriteInt16( crossfade_final_volume_in); + + out->Write(takeover_from, 50); + out->Write(playmp3file_name, PLAYMP3FILE_MAX_FILENAME_LEN); + out->Write(globalstrings, MAXGLOBALSTRINGS * MAX_MAXSTRLEN); + out->Write(lastParserEntry, MAX_MAXSTRLEN); + out->Write(game_name, 100); + out->WriteInt32( ground_level_areas_disabled); + out->WriteInt32( next_screen_transition); + out->WriteInt32( gamma_adjustment); + out->WriteInt16(temporarily_turned_off_character); + out->WriteInt16(inv_backwards_compatibility); + out->WriteInt32(do_once_tokens.size()); + for (int i = 0; i < (int)do_once_tokens.size(); ++i) + { + StrUtil::WriteString(do_once_tokens[i], out); + } + out->WriteInt32( text_min_display_time_ms); + out->WriteInt32( ignore_user_input_after_text_timeout_ms); + + int voice_speech_flags = speech_has_voice ? 0x01 : 0; + if (speech_voice_blocking) + voice_speech_flags |= 0x02; + out->WriteInt32(voice_speech_flags); +} + +void GameState::ReadQueuedAudioItems_Aligned(Common::Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int i = 0; i < MAX_QUEUED_MUSIC; ++i) + { + new_music_queue[i].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +void GameState::FreeProperties() +{ + for (auto &p : charProps) + p.clear(); + for (auto &p : invProps) + p.clear(); +} + +void GameState::FreeViewportsAndCameras() +{ + _roomViewports.clear(); + _roomViewportsSorted.clear(); + for (auto &scobj : _scViewportRefs) + { + scobj.first->Invalidate(); + ccReleaseObjectReference(scobj.second); + } + _scViewportRefs.clear(); + _roomCameras.clear(); + for (auto &scobj : _scCameraRefs) + { + scobj.first->Invalidate(); + ccReleaseObjectReference(scobj.second); + } + _scCameraRefs.clear(); +} + +void GameState::ReadCustomProperties_v340(Common::Stream *in) +{ + if (loaded_game_file_version >= kGameVersion_340_4) + { + // After runtime property values were read we also copy missing default, + // because we do not keep defaults in the saved game, and also in case + // this save is made by an older game version which had different + // properties. + for (int i = 0; i < game.numcharacters; ++i) + Properties::ReadValues(charProps[i], in); + for (int i = 0; i < game.numinvitems; ++i) + Properties::ReadValues(invProps[i], in); + } +} + +void GameState::WriteCustomProperties_v340(Common::Stream *out) const +{ + if (loaded_game_file_version >= kGameVersion_340_4) + { + // We temporarily remove properties that kept default values + // just for the saving data time to avoid getting lots of + // redundant data into saved games + for (int i = 0; i < game.numcharacters; ++i) + Properties::WriteValues(charProps[i], out); + for (int i = 0; i < game.numinvitems; ++i) + Properties::WriteValues(invProps[i], out); + } +} + +// Converts legacy alignment type used in script API +HorAlignment ConvertLegacyScriptAlignment(LegacyScriptAlignment align) +{ + switch (align) + { + case kLegacyScAlignLeft: return kHAlignLeft; + case kLegacyScAlignCentre: return kHAlignCenter; + case kLegacyScAlignRight: return kHAlignRight; + } + return kHAlignNone; +} + +// Reads legacy alignment type from the value set in script depending on the +// current Script API level. This is made to make it possible to change +// Alignment constants in the Script API and still support old version. +HorAlignment ReadScriptAlignment(int32_t align) +{ + return game.options[OPT_BASESCRIPTAPI] < kScriptAPI_v350 ? + ConvertLegacyScriptAlignment((LegacyScriptAlignment)align) : + (HorAlignment)align; +} diff --git a/engines/ags/engine/ac/gamestate.h b/engines/ags/engine/ac/gamestate.h new file mode 100644 index 00000000000..b5843c130ce --- /dev/null +++ b/engines/ags/engine/ac/gamestate.h @@ -0,0 +1,396 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GAMESTATE_H +#define __AC_GAMESTATE_H + + +#include +#include + +#include "ac/characterinfo.h" +#include "ac/runtime_defines.h" +#include "game/roomstruct.h" +#include "game/viewport.h" +#include "media/audio/queuedaudioitem.h" +#include "util/geometry.h" +#include "util/string_types.h" +#include "util/string.h" +#include "ac/timer.h" + +// Forward declaration +namespace AGS +{ + namespace Common + { + class Bitmap; class Stream; + typedef std::shared_ptr PBitmap; + } + namespace Engine { struct RestoredData; } +} +using namespace AGS; // FIXME later +struct ScriptViewport; +struct ScriptCamera; + +#define GAME_STATE_RESERVED_INTS 5 + +// Savegame data format +enum GameStateSvgVersion +{ + kGSSvgVersion_OldFormat = -1, // TODO: remove after old save support is dropped + kGSSvgVersion_Initial = 0, + kGSSvgVersion_350 = 1, + kGSSvgVersion_3509 = 2, + kGSSvgVersion_3510 = 3, +}; + + + +// Adding to this might need to modify AGSDEFNS.SH and AGSPLUGIN.H +struct GameState { + int score; // player's current score + int usedmode; // set by ProcessClick to last cursor mode used + int disabled_user_interface; // >0 while in cutscene/etc + int gscript_timer; // obsolete + int debug_mode; // whether we're in debug mode + int globalvars[MAXGLOBALVARS]; // obsolete + int messagetime; // time left for auto-remove messages + int usedinv; // inventory item last used + int inv_top,inv_numdisp,obsolete_inv_numorder,inv_numinline; + int text_speed; // how quickly text is removed + int sierra_inv_color; // background used to paint defualt inv window + int talkanim_speed; // animation speed of talking anims + int inv_item_wid,inv_item_hit; // set by SetInvDimensions + int speech_text_shadow; // colour of outline fonts (default black) + int swap_portrait_side; // sierra-style speech swap sides + int speech_textwindow_gui; // textwindow used for sierra-style speech + int follow_change_room_timer; // delay before moving following characters into new room + int totalscore; // maximum possible score + int skip_display; // how the user can skip normal Display windows + int no_multiloop_repeat; // for backwards compatibility + int roomscript_finished; // on_call finished in room + int used_inv_on; // inv item they clicked on + int no_textbg_when_voice; // no textwindow bgrnd when voice speech is used + int max_dialogoption_width; // max width of dialog options text window + int no_hicolor_fadein; // fade out but instant in for hi-color + int bgspeech_game_speed; // is background speech relative to game speed + int bgspeech_stay_on_display; // whether to remove bg speech when DisplaySpeech is used + int unfactor_speech_from_textlength; // remove "&10" when calculating time for text to stay + int mp3_loop_before_end; // (UNUSED!) loop this time before end of track (ms) + int speech_music_drop; // how much to drop music volume by when speech is played + int in_cutscene; // we are between a StartCutscene and EndCutscene + int fast_forward; // player has elected to skip cutscene + int room_width; // width of current room + int room_height; // height of current room + // ** up to here is referenced in the plugin interface + int game_speed_modifier; + int score_sound; + int takeover_data; // value passed to RunAGSGame in previous game + int replay_hotkey_unused; // (UNUSED!) StartRecording: not supported + int dialog_options_x; + int dialog_options_y; + int narrator_speech; + int ambient_sounds_persist; + int lipsync_speed; + int close_mouth_speech_time; // stop speech animation at (messagetime - close_mouth_speech_time) + // (this is designed to work in text-only mode) + int disable_antialiasing; + int text_speed_modifier; + HorAlignment text_align; + int speech_bubble_width; + int min_dialogoption_width; + int disable_dialog_parser; + int anim_background_speed; // the setting for this room + int top_bar_backcolor; + int top_bar_textcolor; + int top_bar_bordercolor; + int top_bar_borderwidth; + int top_bar_ypos; + int screenshot_width; + int screenshot_height; + int top_bar_font; + HorAlignment speech_text_align; + int auto_use_walkto_points; + int inventory_greys_out; + int skip_speech_specific_key; + int abort_key; + int fade_to_red; + int fade_to_green; + int fade_to_blue; + int show_single_dialog_option; + int keep_screen_during_instant_transition; + int read_dialog_option_colour; + int stop_dialog_at_end; + int speech_portrait_placement; // speech portrait placement mode (automatic/custom) + int speech_portrait_x; // a speech portrait x offset from corresponding screen side + int speech_portrait_y; // a speech portrait y offset + int speech_display_post_time_ms; // keep speech text/portrait on screen after text/voice has finished playing; + // no speech animation is supposed to be played at this time + int dialog_options_highlight_color; // The colour used for highlighted (hovered over) text in dialog options + int reserved[GAME_STATE_RESERVED_INTS]; // make sure if a future version adds a var, it doesn't mess anything up + // ** up to here is referenced in the script "game." object + long randseed; // random seed + int player_on_region; // player's current region + int screen_is_faded_out; // the screen is currently black + int check_interaction_only; + int bg_frame,bg_anim_delay; // for animating backgrounds + int music_vol_was; // before the volume drop + short wait_counter; + char wait_skipped_by; // tells how last wait was skipped [not serialized] + int wait_skipped_by_data; // extended data telling how last wait was skipped [not serialized] + short mboundx1,mboundx2,mboundy1,mboundy2; + int fade_effect; + int bg_frame_locked; + int globalscriptvars[MAXGSVALUES]; + int cur_music_number,music_repeat; + int music_master_volume; + int digital_master_volume; + char walkable_areas_on[MAX_WALK_AREAS+1]; + short screen_flipped; + int entered_at_x,entered_at_y, entered_edge; + int want_speech; + int cant_skip_speech; + int script_timers[MAX_TIMERS]; + int sound_volume,speech_volume; + int normal_font, speech_font; + char key_skip_wait; + int swap_portrait_lastchar; + int swap_portrait_lastlastchar; + int separate_music_lib; + int in_conversation; + int screen_tint; + int num_parsed_words; + short parsed_words[MAX_PARSED_WORDS]; + char bad_parsed_word[100]; + int raw_color; + int raw_modified[MAX_ROOM_BGFRAMES]; + Common::PBitmap raw_drawing_surface; + short filenumbers[MAXSAVEGAMES]; + int room_changes; + int mouse_cursor_hidden; + int silent_midi; + int silent_midi_channel; + int current_music_repeating; // remember what the loop flag was when this music started + unsigned long shakesc_delay; // unsigned long to match loopcounter + int shakesc_amount, shakesc_length; + int rtint_red, rtint_green, rtint_blue, rtint_level, rtint_light; + bool rtint_enabled; + int end_cutscene_music; + int skip_until_char_stops; + int get_loc_name_last_time; + int get_loc_name_save_cursor; + int restore_cursor_mode_to; + int restore_cursor_image_to; + short music_queue_size; + short music_queue[MAX_QUEUED_MUSIC]; + short new_music_queue_size; + short crossfading_out_channel; + short crossfade_step; + short crossfade_out_volume_per_step; + short crossfade_initial_volume_out; + short crossfading_in_channel; + short crossfade_in_volume_per_step; + short crossfade_final_volume_in; + QueuedAudioItem new_music_queue[MAX_QUEUED_MUSIC]; + char takeover_from[50]; + char playmp3file_name[PLAYMP3FILE_MAX_FILENAME_LEN]; + char globalstrings[MAXGLOBALSTRINGS][MAX_MAXSTRLEN]; + char lastParserEntry[MAX_MAXSTRLEN]; + char game_name[100]; + int ground_level_areas_disabled; + int next_screen_transition; + int gamma_adjustment; + short temporarily_turned_off_character; // Hide Player Charactr ticked + short inv_backwards_compatibility; + int *gui_draw_order; + std::vector do_once_tokens; + int text_min_display_time_ms; + int ignore_user_input_after_text_timeout_ms; + int default_audio_type_volumes[MAX_AUDIO_TYPES]; + + // Dynamic custom property values for characters and items + std::vector charProps; + AGS::Common::StringIMap invProps[MAX_INV]; + + // Dynamic speech state + // + // Tells whether there is a voice-over played during current speech + bool speech_has_voice; + // Tells whether the voice was played in blocking mode; + // atm blocking speech handles itself, and we only need to finalize + // non-blocking voice speech during game update; speech refactor would be + // required to get rid of this rule. + bool speech_voice_blocking; + // Tells whether character speech stays on screen not animated for additional time + bool speech_in_post_state; + + int shake_screen_yoff; // y offset of the shaking screen + + + GameState(); + // Free game resources + void Free(); + + // + // Viewport and camera control. + // Viewports are positioned in game screen coordinates, related to the "game size", + // while cameras are positioned in room coordinates. + // + // Tells if the room viewport should be adjusted automatically each time a new room is loaded + bool IsAutoRoomViewport() const; + // Returns main viewport position on screen, this is the overall game view + const Rect &GetMainViewport() const; + // Returns UI viewport position on screen, this is the GUI layer + const Rect &GetUIViewport() const; + // Returns Room viewport object by it's main index + PViewport GetRoomViewport(int index) const; + // Returns Room viewport object by index in z-order + const std::vector &GetRoomViewportsZOrdered() const; + // Finds room viewport at the given screen coordinates; returns nullptr if non found + PViewport GetRoomViewportAt(int x, int y) const; + // Returns UI viewport position in absolute coordinates (with main viewport offset) + Rect GetUIViewportAbs() const; + // Returns Room viewport position in absolute coordinates (with main viewport offset) + Rect GetRoomViewportAbs(int index) const; + // Sets if the room viewport should be adjusted automatically each time a new room is loaded + void SetAutoRoomViewport(bool on); + // Main viewport defines the location of all things drawn and interactable on the game screen. + // Other viewports are defined relative to the main viewports. + void SetMainViewport(const Rect &viewport); + // UI viewport is a formal dummy viewport for GUI and Overlays (like speech). + void SetUIViewport(const Rect &viewport); + // Applies all the pending changes to viewports and cameras; + // NOTE: this function may be slow, thus recommended to be called only once + // and during the main game update. + void UpdateViewports(); + // Notifies game state that viewports need z-order resorting upon next update. + void InvalidateViewportZOrder(); + // Returns room camera object chosen by index + PCamera GetRoomCamera(int index) const; + // Runs cameras behaviors + void UpdateRoomCameras(); + // Converts room coordinates to the game screen coordinates through the room viewport + // This group of functions always tries to pass a point through the **primary** room viewport + // TODO: also support using arbitrary viewport (for multiple viewports). + Point RoomToScreen(int roomx, int roomy); + int RoomToScreenX(int roomx); + int RoomToScreenY(int roomy); + // Converts game screen coordinates to the room coordinates through the room viewport + // This pair of functions tries to find if there is any viewport at the given coords and results + // in failure if there is none. + // TODO: find out if possible to refactor and get rid of "variadic" variants; + // usually this depends on how the arguments are created (whether they are in "variadic" or true coords) + VpPoint ScreenToRoom(int scrx, int scry); + VpPoint ScreenToRoomDivDown(int scrx, int scry); // native "variadic" coords variant + + // Makes sure primary viewport and camera are created and linked together + void CreatePrimaryViewportAndCamera(); + // Creates new room viewport + PViewport CreateRoomViewport(); + // Register camera in the managed system; optionally links to existing handle + ScriptViewport *RegisterRoomViewport(int index, int32_t handle = 0); + // Deletes existing room viewport + void DeleteRoomViewport(int index); + // Get number of room viewports + int GetRoomViewportCount() const; + // Creates new room camera + PCamera CreateRoomCamera(); + // Register camera in the managed system; optionally links to existing handle + ScriptCamera *RegisterRoomCamera(int index, int32_t handle = 0); + // Deletes existing room camera + void DeleteRoomCamera(int index); + // Get number of room cameras + int GetRoomCameraCount() const; + // Gets script viewport reference; does NOT increment refcount + // because script interpreter does this when acquiring managed pointer. + ScriptViewport *GetScriptViewport(int index); + // Gets script camera reference; does NOT increment refcount + // because script interpreter does this when acquiring managed pointer. + ScriptCamera *GetScriptCamera(int index); + + // + // User input management + // + // Tells if game should ignore user input right now. Note that some of the parent states + // may not ignore it at the same time, such as cutscene state, which may still be skipped + // with a key press or a mouse button. + bool IsIgnoringInput() const; + // Sets ignore input state, for the given time; if there's one already, chooses max timeout + void SetIgnoreInput(int timeout_ms); + // Clears ignore input state + void ClearIgnoreInput(); + + // + // Voice speech management + // + // Tells if there's a blocking voice speech playing right now + bool IsBlockingVoiceSpeech() const; + // Tells whether we have to finalize voice speech when stopping or reusing the channel + bool IsNonBlockingVoiceSpeech() const; + // Speech helpers + bool ShouldPlayVoiceSpeech() const; + + // + // Serialization + // + void ReadQueuedAudioItems_Aligned(Common::Stream *in); + void ReadCustomProperties_v340(Common::Stream *in); + void WriteCustomProperties_v340(Common::Stream *out) const; + void ReadFromSavegame(Common::Stream *in, GameStateSvgVersion svg_ver, AGS::Engine::RestoredData &r_data); + void WriteForSavegame(Common::Stream *out) const; + void FreeProperties(); + void FreeViewportsAndCameras(); + +private: + VpPoint ScreenToRoomImpl(int scrx, int scry, int view_index, bool clip_viewport, bool convert_cam_to_data); + void UpdateRoomCamera(int index); + + // Defines if the room viewport should be adjusted to the room size automatically. + bool _isAutoRoomViewport; + // Main viewport defines the rectangle of the drawn and interactable area + // in the most basic case it will be equal to the game size. + Viewport _mainViewport; + // UI viewport defines the render and interaction rectangle of game's UI. + Viewport _uiViewport; + // Room viewports define place on screen where the room camera's + // contents are drawn. + std::vector _roomViewports; + // Vector of viewports sorted in z-order. + std::vector _roomViewportsSorted; + // Cameras defines the position of a "looking eye" inside the room. + std::vector _roomCameras; + // Script viewports and cameras are references to real data export to + // user script. They became invalidated as the actual object gets + // destroyed, but are kept in memory to prevent script errors. + std::vector> _scViewportRefs; + std::vector> _scCameraRefs; + + // Tells that the main viewport's position has changed since last game update + bool _mainViewportHasChanged; + // Tells that room viewports need z-order resort + bool _roomViewportZOrderChanged; + + AGS_Clock::time_point _ignoreUserInputUntilTime; +}; + +// Converts legacy alignment type used in script API +HorAlignment ConvertLegacyScriptAlignment(LegacyScriptAlignment align); +// Reads legacy alignment type from the value set in script depending on the +// current Script API level. This is made to make it possible to change +// Alignment constants in the Script API and still support old version. +HorAlignment ReadScriptAlignment(int32_t align); + +extern GameState play; + +#endif // __AC_GAMESTATE_H diff --git a/engines/ags/engine/ac/global_api.cpp b/engines/ags/engine/ac/global_api.cpp new file mode 100644 index 00000000000..f7e6d4474d3 --- /dev/null +++ b/engines/ags/engine/ac/global_api.cpp @@ -0,0 +1,3041 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +#include "ac/cdaudio.h" +#include "ac/display.h" +#include "ac/dynamicsprite.h" +#include "ac/event.h" +#include "ac/game.h" +#include "ac/global_audio.h" +#include "ac/global_button.h" +#include "ac/global_character.h" +#include "ac/global_datetime.h" +#include "ac/global_debug.h" +#include "ac/global_dialog.h" +#include "ac/global_display.h" +#include "ac/global_drawingsurface.h" +#include "ac/global_dynamicsprite.h" +#include "ac/global_file.h" +#include "ac/global_game.h" +#include "ac/global_gui.h" +#include "ac/global_hotspot.h" +#include "ac/global_inventoryitem.h" +#include "ac/global_invwindow.h" +#include "ac/global_label.h" +#include "ac/global_listbox.h" +#include "ac/global_mouse.h" +#include "ac/global_object.h" +#include "ac/global_overlay.h" +#include "ac/global_palette.h" +#include "ac/global_parser.h" +#include "ac/global_record.h" +#include "ac/global_region.h" +#include "ac/global_room.h" +#include "ac/global_slider.h" +#include "ac/global_screen.h" +#include "ac/global_string.h" +#include "ac/global_textbox.h" +#include "ac/global_timer.h" +#include "ac/global_translation.h" +#include "ac/global_video.h" +#include "ac/global_viewframe.h" +#include "ac/global_viewport.h" +#include "ac/global_walkablearea.h" +#include "ac/global_walkbehind.h" +#include "ac/math.h" +#include "ac/mouse.h" +#include "ac/parser.h" +#include "ac/string.h" +#include "ac/room.h" +#include "media/video/video.h" +#include "util/string_compat.h" +#include "media/audio/audio_system.h" + +#include "ac/dynobj/scriptstring.h" +extern ScriptString myScriptStringImpl; + +// void (char*texx, ...) +RuntimeScriptValue Sc_sc_AbortGame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(_sc_AbortGame, 1); + _sc_AbortGame(scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (int inum) +RuntimeScriptValue Sc_add_inventory(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(add_inventory); +} + +// void (int charid, int inum) +RuntimeScriptValue Sc_AddInventoryToCharacter(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(AddInventoryToCharacter); +} + +// void (int guin, int objn, int view, int loop, int speed, int repeat) +RuntimeScriptValue Sc_AnimateButton(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT6(AnimateButton); +} + +// void (int chh, int loopn, int sppd, int rept) +RuntimeScriptValue Sc_scAnimateCharacter(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(scAnimateCharacter); +} + +// void (int chh, int loopn, int sppd, int rept, int direction, int blocking) +RuntimeScriptValue Sc_AnimateCharacterEx(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT6(AnimateCharacterEx); +} + +// void (int obn,int loopn,int spdd,int rept) +RuntimeScriptValue Sc_AnimateObject(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(AnimateObject); +} + +// void (int obn,int loopn,int spdd,int rept, int direction, int blocking) +RuntimeScriptValue Sc_AnimateObjectEx(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT6(AnimateObjectEx); +} + +// int (int cchar1,int cchar2) +RuntimeScriptValue Sc_AreCharactersColliding(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(AreCharactersColliding); +} + +// int (int charid,int objid) +RuntimeScriptValue Sc_AreCharObjColliding(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(AreCharObjColliding); +} + +// int (int obj1,int obj2) +RuntimeScriptValue Sc_AreObjectsColliding(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(AreObjectsColliding); +} + +// int (int thing1, int thing2) +RuntimeScriptValue Sc_AreThingsOverlapping(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(AreThingsOverlapping); +} + +// void (int value) +RuntimeScriptValue Sc_CallRoomScript(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(CallRoomScript); +} + +// int (int cmdd,int datt) +RuntimeScriptValue Sc_cd_manager(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(cd_manager); +} + +// void (int ifn) +RuntimeScriptValue Sc_CentreGUI(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(CentreGUI); +} + +// void (int chaa,int vii) +RuntimeScriptValue Sc_ChangeCharacterView(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(ChangeCharacterView); +} + +extern RuntimeScriptValue Sc_ChangeCursorGraphic(const RuntimeScriptValue *params, int32_t param_count); + +extern RuntimeScriptValue Sc_ChangeCursorHotspot(const RuntimeScriptValue *params, int32_t param_count); + +// void () +RuntimeScriptValue Sc_ClaimEvent(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(ClaimEvent); +} + +// int (int xx,int yy,int slott,int trans) +RuntimeScriptValue Sc_CreateGraphicOverlay(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT4(CreateGraphicOverlay); +} + +// int (int xx,int yy,int wii,int fontid,int clr,char*texx, ...) +RuntimeScriptValue Sc_CreateTextOverlay(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(CreateTextOverlay, 6); + return RuntimeScriptValue().SetInt32( + CreateTextOverlay(params[0].IValue, params[1].IValue, params[2].IValue, + params[3].IValue, params[4].IValue, scsf_buffer, DISPLAYTEXT_NORMALOVERLAY)); +} + +// void (int strt,int eend) +RuntimeScriptValue Sc_CyclePalette(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(CyclePalette); +} + +// void (int cmdd,int dataa) +RuntimeScriptValue Sc_script_debug(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(script_debug); +} + +// void (int slnum) +RuntimeScriptValue Sc_DeleteSaveSlot(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(DeleteSaveSlot); +} + +// void (int gotSlot) +RuntimeScriptValue Sc_free_dynamic_sprite(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(free_dynamic_sprite); +} + +extern RuntimeScriptValue Sc_disable_cursor_mode(const RuntimeScriptValue *params, int32_t param_count); + +// void (int alsoEffects) +RuntimeScriptValue Sc_DisableGroundLevelAreas(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(DisableGroundLevelAreas); +} + +// void (int hsnum) +RuntimeScriptValue Sc_DisableHotspot(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(DisableHotspot); +} + +// void () +RuntimeScriptValue Sc_DisableInterface(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(DisableInterface); +} + +// void (int hsnum) +RuntimeScriptValue Sc_DisableRegion(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(DisableRegion); +} + +// void (char*texx, ...) +RuntimeScriptValue Sc_Display(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(Display, 1); + DisplaySimple(scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (int xxp,int yyp,int widd,char*texx, ...) +RuntimeScriptValue Sc_DisplayAt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(DisplayAt, 4); + DisplayAt(params[0].IValue, params[1].IValue, params[2].IValue, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (int ypos, char *texx) +RuntimeScriptValue Sc_DisplayAtY(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(DisplayAtY, const char); +} + +// void (int msnum) +RuntimeScriptValue Sc_DisplayMessage(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(DisplayMessage); +} + +// void (int msnum, int ypos) +RuntimeScriptValue Sc_DisplayMessageAtY(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(DisplayMessageAtY); +} + +// void (int ypos, int ttexcol, int backcol, char *title, int msgnum) +RuntimeScriptValue Sc_DisplayMessageBar(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3_POBJ_PINT(DisplayMessageBar, const char); +} + +// void (int chid,char*texx, ...) +RuntimeScriptValue Sc_sc_displayspeech(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(DisplayAt, 2); + __sc_displayspeech(params[0].IValue, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (int xx, int yy, int wii, int aschar, char*spch) +RuntimeScriptValue Sc_DisplaySpeechAt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4_POBJ(DisplaySpeechAt, const char); +} + +// int (int charid,char*speel) +RuntimeScriptValue Sc_DisplaySpeechBackground(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT_POBJ(DisplaySpeechBackground, const char); +} + +// void (int chid, const char*texx, ...) +RuntimeScriptValue Sc_DisplayThought(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(DisplayThought, 2); + DisplayThought(params[0].IValue, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (int ypos, int ttexcol, int backcol, char *title, char*texx, ...) +RuntimeScriptValue Sc_DisplayTopBar(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(DisplayTopBar, 5); + DisplayTopBar(params[0].IValue, params[1].IValue, params[2].IValue, params[3].Ptr, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +extern RuntimeScriptValue Sc_enable_cursor_mode(const RuntimeScriptValue *params, int32_t param_count); + +// void () +RuntimeScriptValue Sc_EnableGroundLevelAreas(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(EnableGroundLevelAreas); +} + +// void (int hsnum) +RuntimeScriptValue Sc_EnableHotspot(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(EnableHotspot); +} + +// void () +RuntimeScriptValue Sc_EnableInterface(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(EnableInterface); +} + +// void (int hsnum) +RuntimeScriptValue Sc_EnableRegion(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(EnableRegion); +} + +// int () +RuntimeScriptValue Sc_EndCutscene(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(EndCutscene); +} + +// void (int cha,int toface) +RuntimeScriptValue Sc_FaceCharacter(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(FaceCharacter); +} + +// void (int cha, int xx, int yy) +RuntimeScriptValue Sc_FaceLocation(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(FaceLocation); +} + +// void (int sppd) +RuntimeScriptValue Sc_FadeIn(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(FadeIn); +} + +// void (int spdd) +RuntimeScriptValue Sc_my_fade_out(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(my_fade_out); +} + +// void (int handle) +RuntimeScriptValue Sc_FileClose(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(FileClose); +} + +// int (int handle) +RuntimeScriptValue Sc_FileIsEOF(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(FileIsEOF); +} + +// int (int handle) +RuntimeScriptValue Sc_FileIsError(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(FileIsError); +} + +// int (const char*fnmm, const char* cmode) +RuntimeScriptValue Sc_FileOpenCMode(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ2(FileOpenCMode, const char, const char); +} + +// void (int handle,char*toread) +RuntimeScriptValue Sc_FileRead(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(FileRead, char); +} + +// int (int handle) +RuntimeScriptValue Sc_FileReadInt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(FileReadInt); +} + +// char (int handle) +RuntimeScriptValue Sc_FileReadRawChar(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(FileReadRawChar); +} + +// int (int handle) +RuntimeScriptValue Sc_FileReadRawInt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(FileReadRawInt); +} + +// void (int handle, const char *towrite) +RuntimeScriptValue Sc_FileWrite(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(FileWrite, const char); +} + +// void (int handle,int into) +RuntimeScriptValue Sc_FileWriteInt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(FileWriteInt); +} + +// void (int handle, int chartoWrite) +RuntimeScriptValue Sc_FileWriteRawChar(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(FileWriteRawChar); +} + +// void (int handle, const char*towrite) +RuntimeScriptValue Sc_FileWriteRawLine(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(FileWriteRawLine, const char); +} + +// int (const char* GUIName) +RuntimeScriptValue Sc_FindGUIID(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(FindGUIID, const char); +} + +// void (int amount) +RuntimeScriptValue Sc_FlipScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(FlipScreen); +} + +// int (SCRIPT_FLOAT(value), int roundDirection) +RuntimeScriptValue Sc_FloatToInt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PFLOAT_PINT(FloatToInt); +} + +// void (int who, int tofollow) +RuntimeScriptValue Sc_FollowCharacter(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(FollowCharacter); +} + +// void (int who, int tofollow, int distaway, int eagerness) +RuntimeScriptValue Sc_FollowCharacterEx(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(FollowCharacterEx); +} + +// int () +RuntimeScriptValue Sc_GetBackgroundFrame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetBackgroundFrame); +} + +// int (int guin, int objn, int ptype) +RuntimeScriptValue Sc_GetButtonPic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT3(GetButtonPic); +} + +// int (int xx, int yy) +RuntimeScriptValue Sc_GetCharIDAtScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetCharIDAtScreen); +} + +// int (int cha, const char *property) +RuntimeScriptValue Sc_GetCharacterProperty(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT_POBJ(GetCharacterProperty, const char); +} + +// void (int item, const char *property, char *bufer) +RuntimeScriptValue Sc_GetCharacterPropertyText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ2(GetCharacterPropertyText, const char, char); +} + +// int () +RuntimeScriptValue Sc_GetCurrentMusic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetCurrentMusic); +} + +extern RuntimeScriptValue Sc_GetCursorMode(const RuntimeScriptValue *params, int32_t param_count); + +// int (int dlg, int opt) +RuntimeScriptValue Sc_GetDialogOption(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetDialogOption); +} + +// int (int opt) +RuntimeScriptValue Sc_GetGameOption(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetGameOption); +} + +// int (int parm, int data1, int data2, int data3) +RuntimeScriptValue Sc_GetGameParameter(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT4(GetGameParameter); +} + +// int () +RuntimeScriptValue Sc_GetGameSpeed(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetGameSpeed); +} + +// int (int index) +RuntimeScriptValue Sc_GetGlobalInt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetGlobalInt); +} + +// void (int index, char *strval) +RuntimeScriptValue Sc_GetGlobalString(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(GetGlobalString, char); +} + +// int (const char *varName) +RuntimeScriptValue Sc_GetGraphicalVariable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(GetGraphicalVariable, const char); +} + +// int (int xx,int yy) +RuntimeScriptValue Sc_GetGUIAt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetGUIAt); +} + +// int (int xx, int yy) +RuntimeScriptValue Sc_GetGUIObjectAt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetGUIObjectAt); +} + +// int (int xxx,int yyy) +RuntimeScriptValue Sc_GetHotspotIDAtScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetHotspotIDAtScreen); +} + +// void (int hotspot, char *buffer) +RuntimeScriptValue Sc_GetHotspotName(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(GetHotspotName, char); +} + +// int (int hotspot) +RuntimeScriptValue Sc_GetHotspotPointX(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetHotspotPointX); +} + +// int (int hotspot) +RuntimeScriptValue Sc_GetHotspotPointY(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetHotspotPointY); +} + +// int (int hss, const char *property) +RuntimeScriptValue Sc_GetHotspotProperty(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT_POBJ(GetHotspotProperty, const char); +} + +// void (int item, const char *property, char *bufer) +RuntimeScriptValue Sc_GetHotspotPropertyText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ2(GetHotspotPropertyText, const char, char); +} + +// int (int xxx, int yyy) +RuntimeScriptValue Sc_GetInvAt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetInvAt); +} + +// int (int indx) +RuntimeScriptValue Sc_GetInvGraphic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetInvGraphic); +} + +// void (int indx,char*buff) +RuntimeScriptValue Sc_GetInvName(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(GetInvName, char); +} + +// int (int item, const char *property) +RuntimeScriptValue Sc_GetInvProperty(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT_POBJ(GetInvProperty, const char); +} + +// void (int item, const char *property, char *bufer) +RuntimeScriptValue Sc_GetInvPropertyText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ2(GetInvPropertyText, const char, char); +} + +// void (int xxx,int yyy,char*tempo) +RuntimeScriptValue Sc_GetLocationName(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2_POBJ(GetLocationName, char); +} + +// int (int xxx,int yyy) +RuntimeScriptValue Sc_GetLocationType(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetLocationType); +} + +// void (int msg, char *buffer) +RuntimeScriptValue Sc_GetMessageText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(GetMessageText, char); +} + +// int () +RuntimeScriptValue Sc_GetMIDIPosition(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetMIDIPosition); +} + +// int () +RuntimeScriptValue Sc_GetMP3PosMillis(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetMP3PosMillis); +} + +// int (int xx,int yy) +RuntimeScriptValue Sc_GetObjectIDAtScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetObjectIDAtScreen); +} + +// int (int obn) +RuntimeScriptValue Sc_GetObjectBaseline(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetObjectBaseline); +} + +// int (int obn) +RuntimeScriptValue Sc_GetObjectGraphic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetObjectGraphic); +} + +// void (int obj, char *buffer) +RuntimeScriptValue Sc_GetObjectName(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(GetObjectName, char); +} + +// int (int hss, const char *property) +RuntimeScriptValue Sc_GetObjectProperty(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT_POBJ(GetObjectProperty, const char); +} + +// void (int item, const char *property, char *bufer) +RuntimeScriptValue Sc_GetObjectPropertyText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ2(GetObjectPropertyText, const char, char); +} + +// int (int objj) +RuntimeScriptValue Sc_GetObjectX(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetObjectX); +} + +// int (int objj) +RuntimeScriptValue Sc_GetObjectY(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetObjectY); +} + +// int () +RuntimeScriptValue Sc_GetPlayerCharacter(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetPlayerCharacter); +} + +// int () +RuntimeScriptValue Sc_GetRawTime(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetRawTime); +} + +// int (int xxx, int yyy) +RuntimeScriptValue Sc_GetRegionIDAtRoom(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetRegionIDAtRoom); +} + +// void (const char *property, char *bufer) +RuntimeScriptValue Sc_GetRoomPropertyText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_POBJ2(GetRoomPropertyText, const char, char); +} + +// int (int slnum,char*desbuf) +RuntimeScriptValue Sc_GetSaveSlotDescription(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT_POBJ(GetSaveSlotDescription, char); +} + +// int (int x, int y) +RuntimeScriptValue Sc_GetScalingAt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetScalingAt); +} + +// int (int guin,int objn) +RuntimeScriptValue Sc_GetSliderValue(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetSliderValue); +} + +// void (int guin, int objn, char*txbuf) +RuntimeScriptValue Sc_GetTextBoxText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2_POBJ(GetTextBoxText, char); +} + +// int (char *text, int fontnum, int width) +RuntimeScriptValue Sc_GetTextHeight(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ_PINT2(GetTextHeight, const char); +} + +// int (char *text, int fontnum) +RuntimeScriptValue Sc_GetTextWidth(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ_PINT(GetTextWidth, const char); +} + +RuntimeScriptValue Sc_GetFontHeight(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetFontHeight); +} + +RuntimeScriptValue Sc_GetFontLineSpacing(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(GetFontLineSpacing); +} + +// int (int whatti) +RuntimeScriptValue Sc_sc_GetTime(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(sc_GetTime); +} + +// char * (const char *text) +RuntimeScriptValue Sc_get_translation(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_POBJ(char, myScriptStringImpl, get_translation, const char); +} + +// int (char* buffer) +RuntimeScriptValue Sc_GetTranslationName(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(GetTranslationName, char); +} + +// int () +RuntimeScriptValue Sc_GetViewportX(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetViewportX); +} + +// int () +RuntimeScriptValue Sc_GetViewportY(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetViewportY); +} + +RuntimeScriptValue Sc_GetWalkableAreaAtRoom(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetWalkableAreaAtRoom); +} + +// int (int xxx,int yyy) +RuntimeScriptValue Sc_GetWalkableAreaAtScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(GetWalkableAreaAtScreen); +} + +RuntimeScriptValue Sc_GetDrawingSurfaceForWalkableArea(const RuntimeScriptValue *params, int32_t param_count) +{ + ScriptDrawingSurface* ret_obj = Room_GetDrawingSurfaceForMask(kRoomAreaWalkable); + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj); +} + +RuntimeScriptValue Sc_GetDrawingSurfaceForWalkbehind(const RuntimeScriptValue *params, int32_t param_count) +{ + ScriptDrawingSurface* ret_obj = Room_GetDrawingSurfaceForMask(kRoomAreaWalkBehind); + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj); +} + +// void (int amnt) +RuntimeScriptValue Sc_GiveScore(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(GiveScore); +} + +// int (int roomnum) +RuntimeScriptValue Sc_HasPlayerBeenInRoom(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(HasPlayerBeenInRoom); +} + +// void () +RuntimeScriptValue Sc_HideMouseCursor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(HideMouseCursor); +} + +// void (const char*msg,char*bufr) +RuntimeScriptValue Sc_sc_inputbox(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_POBJ2(sc_inputbox, const char, char); +} + +// void (int ifn) +RuntimeScriptValue Sc_InterfaceOff(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(InterfaceOff); +} + +// void (int ifn) +RuntimeScriptValue Sc_InterfaceOn(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(InterfaceOn); +} + +// FLOAT_RETURN_TYPE (int value) +RuntimeScriptValue Sc_IntToFloat(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PINT(IntToFloat); +} + +// void () +RuntimeScriptValue Sc_sc_invscreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(sc_invscreen); +} + +extern RuntimeScriptValue Sc_IsButtonDown(const RuntimeScriptValue *params, int32_t param_count); + +// int (int chan) +RuntimeScriptValue Sc_IsChannelPlaying(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsChannelPlaying); +} + +// int () +RuntimeScriptValue Sc_IsGamePaused(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(IsGamePaused); +} + +// int (int guinum) +RuntimeScriptValue Sc_IsGUIOn(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsGUIOn); +} + +// int (int xx,int yy,int mood) +RuntimeScriptValue Sc_IsInteractionAvailable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT3(IsInteractionAvailable); +} + +// int (int item, int mood) +RuntimeScriptValue Sc_IsInventoryInteractionAvailable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(IsInventoryInteractionAvailable); +} + +// int () +RuntimeScriptValue Sc_IsInterfaceEnabled(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(IsInterfaceEnabled); +} + +// int (int keycode) +RuntimeScriptValue Sc_IsKeyPressed(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsKeyPressed); +} + +// int () +RuntimeScriptValue Sc_IsMusicPlaying(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(IsMusicPlaying); +} + +// int () +RuntimeScriptValue Sc_IsMusicVoxAvailable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(IsMusicVoxAvailable); +} + +// int (int objj) +RuntimeScriptValue Sc_IsObjectAnimating(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsObjectAnimating); +} + +// int (int objj) +RuntimeScriptValue Sc_IsObjectMoving(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsObjectMoving); +} + +// int (int objj) +RuntimeScriptValue Sc_IsObjectOn(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsObjectOn); +} + +// int (int ovrid) +RuntimeScriptValue Sc_IsOverlayValid(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsOverlayValid); +} + +// int () +RuntimeScriptValue Sc_IsSoundPlaying(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(IsSoundPlaying); +} + +// int (int tnum) +RuntimeScriptValue Sc_IsTimerExpired(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsTimerExpired); +} + +// int () +RuntimeScriptValue Sc_IsTranslationAvailable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(IsTranslationAvailable); +} + +// int () +RuntimeScriptValue Sc_IsVoxAvailable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(IsVoxAvailable); +} + +// void (int guin, int objn, const char*newitem) +RuntimeScriptValue Sc_ListBoxAdd(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2_POBJ(ListBoxAdd, const char); +} + +// void (int guin, int objn) +RuntimeScriptValue Sc_ListBoxClear(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(ListBoxClear); +} + +// void (int guin, int objn, const char*filemask) +RuntimeScriptValue Sc_ListBoxDirList(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2_POBJ(ListBoxDirList, const char); +} + +// char* (int guin, int objn, int item, char*buffer) +RuntimeScriptValue Sc_ListBoxGetItemText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT3_POBJ(char, myScriptStringImpl, ListBoxGetItemText, char); +} + +// int (int guin, int objn) +RuntimeScriptValue Sc_ListBoxGetNumItems(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(ListBoxGetNumItems); +} + +// int (int guin, int objn) +RuntimeScriptValue Sc_ListBoxGetSelected(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(ListBoxGetSelected); +} + +// void (int guin, int objn, int itemIndex) +RuntimeScriptValue Sc_ListBoxRemove(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(ListBoxRemove); +} + +// int (int guin, int objn) +RuntimeScriptValue Sc_ListBoxSaveGameList(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(ListBoxSaveGameList); +} + +// void (int guin, int objn, int newsel) +RuntimeScriptValue Sc_ListBoxSetSelected(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(ListBoxSetSelected); +} + +// void (int guin, int objn, int item) +RuntimeScriptValue Sc_ListBoxSetTopItem(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(ListBoxSetTopItem); +} + +// int (const char *filename) +RuntimeScriptValue Sc_LoadImageFile(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(LoadImageFile, const char); +} + +// int (int slnum, int width, int height) +RuntimeScriptValue Sc_LoadSaveSlotScreenshot(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT3(LoadSaveSlotScreenshot); +} + +// void (int inum) +RuntimeScriptValue Sc_lose_inventory(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(lose_inventory); +} + +// void (int charid, int inum) +RuntimeScriptValue Sc_LoseInventoryFromCharacter(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(LoseInventoryFromCharacter); +} + +// void (int obn) +RuntimeScriptValue Sc_MergeObject(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(MergeObject); +} + +// void (int cc,int xx,int yy) +RuntimeScriptValue Sc_MoveCharacter(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(MoveCharacter); +} + +// void (int chaa,int xx,int yy,int direct) +RuntimeScriptValue Sc_MoveCharacterBlocking(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(MoveCharacterBlocking); +} + +// void (int cc,int xx, int yy) +RuntimeScriptValue Sc_MoveCharacterDirect(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(MoveCharacterDirect); +} + +// void (int chac, int tox, int toy) +RuntimeScriptValue Sc_MoveCharacterPath(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(MoveCharacterPath); +} + +// void (int cc,int xx, int yy) +RuntimeScriptValue Sc_MoveCharacterStraight(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(MoveCharacterStraight); +} + +// void (int chaa,int hotsp) +RuntimeScriptValue Sc_MoveCharacterToHotspot(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(MoveCharacterToHotspot); +} + +// void (int chaa,int obbj) +RuntimeScriptValue Sc_MoveCharacterToObject(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(MoveCharacterToObject); +} + +// void (int objj,int xx,int yy,int spp) +RuntimeScriptValue Sc_MoveObject(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(MoveObject); +} + +// void (int objj,int xx,int yy,int spp) +RuntimeScriptValue Sc_MoveObjectDirect(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(MoveObjectDirect); +} + +// void (int ovrid, int newx,int newy) +RuntimeScriptValue Sc_MoveOverlay(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(MoveOverlay); +} + +// void (int charid) +RuntimeScriptValue Sc_MoveToWalkableArea(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(MoveToWalkableArea); +} + +// void (int nrnum) +RuntimeScriptValue Sc_NewRoom(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(NewRoom); +} + +// void (int nrnum,int newx,int newy) +RuntimeScriptValue Sc_NewRoomEx(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(NewRoomEx); +} + +// void (int charid, int nrnum, int newx, int newy) +RuntimeScriptValue Sc_NewRoomNPC(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(NewRoomNPC); +} + +// void (int obn) +RuntimeScriptValue Sc_ObjectOff(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(ObjectOff); +} + +// void (int obn) +RuntimeScriptValue Sc_ObjectOn(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(ObjectOn); +} + +extern RuntimeScriptValue Sc_ParseText(const RuntimeScriptValue *params, int32_t param_count); + +// void () +RuntimeScriptValue Sc_PauseGame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(PauseGame); +} + +// void (int channel, int sndnum, int vol, int x, int y) +RuntimeScriptValue Sc_PlayAmbientSound(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT5(PlayAmbientSound); +} + +// void (int numb,int playflags) +RuntimeScriptValue Sc_play_flc_file(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(play_flc_file); +} + +// void (char *filename) +RuntimeScriptValue Sc_PlayMP3File(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_POBJ(PlayMP3File, const char); +} + +// void (int newmus) +RuntimeScriptValue Sc_PlayMusicResetQueue(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(PlayMusicResetQueue); +} + +// int (int musnum) +RuntimeScriptValue Sc_PlayMusicQueued(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(PlayMusicQueued); +} + +// void (int mnum) +RuntimeScriptValue Sc_PlaySilentMIDI(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(PlaySilentMIDI); +} + +// int (int val1) +RuntimeScriptValue Sc_play_sound(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(play_sound); +} + +// int (int val1, int channel) +RuntimeScriptValue Sc_PlaySoundEx(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(PlaySoundEx); +} + +// void (const char* name, int skip, int flags) +RuntimeScriptValue Sc_scrPlayVideo(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_POBJ_PINT2(scrPlayVideo, const char); +} + +// void (int dialog) +RuntimeScriptValue Sc_QuitGame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(QuitGame); +} + +// int (int upto) +RuntimeScriptValue Sc_Rand(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(__Rand); +} + +// void (int clr) +RuntimeScriptValue Sc_RawClear(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(RawClear); +} + +// void (int xx, int yy, int rad) +RuntimeScriptValue Sc_RawDrawCircle(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(RawDrawCircle); +} + +// void (int frame, int translev) +RuntimeScriptValue Sc_RawDrawFrameTransparent(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(RawDrawFrameTransparent); +} + +// void (int xx, int yy, int slot) +RuntimeScriptValue Sc_RawDrawImage(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(RawDrawImage); +} + +// void (int xx, int yy, int slot) +RuntimeScriptValue Sc_RawDrawImageOffset(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(RawDrawImageOffset); +} + +// void (int xx, int yy, int gotSlot, int width, int height) +RuntimeScriptValue Sc_RawDrawImageResized(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT5(RawDrawImageResized); +} + +// void (int xx, int yy, int slot, int trans) +RuntimeScriptValue Sc_RawDrawImageTransparent(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(RawDrawImageTransparent); +} + +// void (int fromx, int fromy, int tox, int toy) +RuntimeScriptValue Sc_RawDrawLine(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(RawDrawLine); +} + +// void (int x1, int y1, int x2, int y2) +RuntimeScriptValue Sc_RawDrawRectangle(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(RawDrawRectangle); +} + +// void (int x1, int y1, int x2, int y2, int x3, int y3) +RuntimeScriptValue Sc_RawDrawTriangle(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT6(RawDrawTriangle); +} + +// void (int xx, int yy, char*texx, ...) +RuntimeScriptValue Sc_RawPrint(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(RawPrint, 3); + RawPrint(params[0].IValue, params[1].IValue, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (int xx, int yy, int wid, int font, int msgm) +RuntimeScriptValue Sc_RawPrintMessageWrapped(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT5(RawPrintMessageWrapped); +} + +// void () +RuntimeScriptValue Sc_RawRestoreScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(RawRestoreScreen); +} + +// void (int red, int green, int blue, int opacity) +RuntimeScriptValue Sc_RawRestoreScreenTinted(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(RawRestoreScreenTinted); +} + +// void () +RuntimeScriptValue Sc_RawSaveScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(RawSaveScreen); +} + +// void (int clr) +RuntimeScriptValue Sc_RawSetColor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(RawSetColor); +} + +// void (int red, int grn, int blu) +RuntimeScriptValue Sc_RawSetColorRGB(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(RawSetColorRGB); +} + +extern RuntimeScriptValue Sc_RefreshMouse(const RuntimeScriptValue *params, int32_t param_count); + +// void (int chat) +RuntimeScriptValue Sc_ReleaseCharacterView(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(ReleaseCharacterView); +} + +// void () +RuntimeScriptValue Sc_ReleaseViewport(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(ReleaseViewport); +} + +// void (int obj) +RuntimeScriptValue Sc_RemoveObjectTint(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(RemoveObjectTint); +} + +// void (int ovrid) +RuntimeScriptValue Sc_RemoveOverlay(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(RemoveOverlay); +} + +// void (int areanum) +RuntimeScriptValue Sc_RemoveWalkableArea(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(RemoveWalkableArea); +} + +// void (int nrnum) +RuntimeScriptValue Sc_ResetRoom(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(ResetRoom); +} + +// void () +RuntimeScriptValue Sc_restart_game(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(restart_game); +} + +// void () +RuntimeScriptValue Sc_restore_game_dialog(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(restore_game_dialog); +} + +// void (int slnum) +RuntimeScriptValue Sc_RestoreGameSlot(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(RestoreGameSlot); +} + +// void (int areanum) +RuntimeScriptValue Sc_RestoreWalkableArea(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(RestoreWalkableArea); +} + +// int (char *newgame, unsigned int mode, int data) +RuntimeScriptValue Sc_RunAGSGame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ_PINT2(RunAGSGame, const char); +} + +// void (int cc, int mood) +RuntimeScriptValue Sc_RunCharacterInteraction(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(RunCharacterInteraction); +} + +// void (int tum) +RuntimeScriptValue Sc_RunDialog(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(RunDialog); +} + +// void (int hotspothere, int mood) +RuntimeScriptValue Sc_RunHotspotInteraction(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(RunHotspotInteraction); +} + +// void (int iit, int modd) +RuntimeScriptValue Sc_RunInventoryInteraction(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(RunInventoryInteraction); +} + +// void (int aa, int mood) +RuntimeScriptValue Sc_RunObjectInteraction(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(RunObjectInteraction); +} + +// void (int regnum, int mood) +RuntimeScriptValue Sc_RunRegionInteraction(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(RunRegionInteraction); +} + +extern RuntimeScriptValue Sc_Said(const RuntimeScriptValue *params, int32_t param_count); + +// int (char*buffer) +RuntimeScriptValue Sc_SaidUnknownWord(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(SaidUnknownWord, char); +} + +extern RuntimeScriptValue Sc_SaveCursorForLocationChange(const RuntimeScriptValue *params, int32_t param_count); + +// void () +RuntimeScriptValue Sc_save_game_dialog(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(save_game_dialog); +} + +// void (int slotn, const char*descript) +RuntimeScriptValue Sc_save_game(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(save_game, const char); +} + +// int (char*namm) +RuntimeScriptValue Sc_SaveScreenShot(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(SaveScreenShot, const char); +} + +// void (int position) +RuntimeScriptValue Sc_SeekMIDIPosition(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SeekMIDIPosition); +} + +// void (int patnum) +RuntimeScriptValue Sc_SeekMODPattern(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SeekMODPattern); +} + +// void (int posn) +RuntimeScriptValue Sc_SeekMP3PosMillis(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SeekMP3PosMillis); +} + +// void (int iit) +RuntimeScriptValue Sc_SetActiveInventory(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetActiveInventory); +} + +// void (int red, int green, int blue, int opacity, int luminance) +RuntimeScriptValue Sc_SetAmbientTint(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT5(SetAmbientTint); +} + +RuntimeScriptValue Sc_SetAmbientLightLevel(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetAmbientLightLevel); +} + +// void (int area, int brightness) +RuntimeScriptValue Sc_SetAreaLightLevel(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetAreaLightLevel); +} + +// void (int area, int min, int max) +RuntimeScriptValue Sc_SetAreaScaling(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetAreaScaling); +} + +// void (int frnum) +RuntimeScriptValue Sc_SetBackgroundFrame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetBackgroundFrame); +} + +// void (int guin,int objn,int ptype,int slotn) +RuntimeScriptValue Sc_SetButtonPic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetButtonPic); +} + +// void (int guin,int objn,char*newtx) +RuntimeScriptValue Sc_SetButtonText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2_POBJ(SetButtonText, const char); +} + +// void (int chan, int newvol) +RuntimeScriptValue Sc_SetChannelVolume(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetChannelVolume); +} + +// void (int obn, int basel) +RuntimeScriptValue Sc_SetCharacterBaseline(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetCharacterBaseline); +} + +// void (int cha, int clik) +RuntimeScriptValue Sc_SetCharacterClickable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetCharacterClickable); +} + +// void (int chaa, int view, int loop, int frame) +RuntimeScriptValue Sc_SetCharacterFrame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetCharacterFrame); +} + +// void (int who, int iview, int itime) +RuntimeScriptValue Sc_SetCharacterIdle(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetCharacterIdle); +} + +// void (int who, int yesorno) +RuntimeScriptValue Sc_SetCharacterIgnoreLight(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetCharacterIgnoreLight); +} + +// void (int cha, int clik) +RuntimeScriptValue Sc_SetCharacterIgnoreWalkbehinds(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetCharacterIgnoreWalkbehinds); +} + +// void (int who, int flag, int yesorno) +RuntimeScriptValue Sc_SetCharacterProperty(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetCharacterProperty); +} + +// void (int chaa, int vii, int intrv) +RuntimeScriptValue Sc_SetCharacterBlinkView(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetCharacterBlinkView); +} + +// void (int chaa, int vii) +RuntimeScriptValue Sc_SetCharacterSpeechView(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetCharacterSpeechView); +} + +// void (int chaa,int nspeed) +RuntimeScriptValue Sc_SetCharacterSpeed(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetCharacterSpeed); +} + +// void (int chaa, int xspeed, int yspeed) +RuntimeScriptValue Sc_SetCharacterSpeedEx(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetCharacterSpeedEx); +} + +// void (int obn,int trans) +RuntimeScriptValue Sc_SetCharacterTransparency(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetCharacterTransparency); +} + +// void (int chaa,int vii) +RuntimeScriptValue Sc_SetCharacterView(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetCharacterView); +} + +// void (int chaa, int vii, int loop, int align) +RuntimeScriptValue Sc_SetCharacterViewEx(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetCharacterViewEx); +} + +// void (int chaa, int vii, int xoffs, int yoffs) +RuntimeScriptValue Sc_SetCharacterViewOffset(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetCharacterViewOffset); +} + +extern RuntimeScriptValue Sc_set_cursor_mode(const RuntimeScriptValue *params, int32_t param_count); +extern RuntimeScriptValue Sc_set_default_cursor(const RuntimeScriptValue *params, int32_t param_count); + +// void (int dlg,int opt,int onoroff) +RuntimeScriptValue Sc_SetDialogOption(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetDialogOption); +} + +// void (int newvol) +RuntimeScriptValue Sc_SetDigitalMasterVolume(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetDigitalMasterVolume); +} + +// void (int red, int green, int blue) +RuntimeScriptValue Sc_SetFadeColor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetFadeColor); +} + +// void (int vii, int loop, int frame, int sound) +RuntimeScriptValue Sc_SetFrameSound(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetFrameSound); +} + +// int (int opt, int setting) +RuntimeScriptValue Sc_SetGameOption(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT2(SetGameOption); +} + +// void (int newspd) +RuntimeScriptValue Sc_SetGameSpeed(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetGameSpeed); +} + +// void (int index,int valu) +RuntimeScriptValue Sc_SetGlobalInt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetGlobalInt); +} + +extern RuntimeScriptValue Sc_SetGlobalString(const RuntimeScriptValue *params, int32_t param_count); + +// void (const char *varName, int p_value) +RuntimeScriptValue Sc_SetGraphicalVariable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_POBJ_PINT(SetGraphicalVariable, const char); +} + +// void (int guin, int slotn) +RuntimeScriptValue Sc_SetGUIBackgroundPic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetGUIBackgroundPic); +} + +// void (int guin, int clickable) +RuntimeScriptValue Sc_SetGUIClickable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetGUIClickable); +} + +// void (int guin, int objn, int enabled) +RuntimeScriptValue Sc_SetGUIObjectEnabled(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetGUIObjectEnabled); +} + +// void (int guin, int objn, int xx, int yy) +RuntimeScriptValue Sc_SetGUIObjectPosition(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetGUIObjectPosition); +} + +// void (int ifn, int objn, int newwid, int newhit) +RuntimeScriptValue Sc_SetGUIObjectSize(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetGUIObjectSize); +} + +// void (int ifn,int xx,int yy) +RuntimeScriptValue Sc_SetGUIPosition(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetGUIPosition); +} + +// void (int ifn, int widd, int hitt) +RuntimeScriptValue Sc_SetGUISize(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetGUISize); +} + +// void (int ifn, int trans) +RuntimeScriptValue Sc_SetGUITransparency(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetGUITransparency); +} + +// void (int guin, int z) +RuntimeScriptValue Sc_SetGUIZOrder(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetGUIZOrder); +} + +// void (int invi, const char *newName) +RuntimeScriptValue Sc_SetInvItemName(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT_POBJ(SetInvItemName, const char); +} + +// void (int invi, int piccy) +RuntimeScriptValue Sc_set_inv_item_pic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(set_inv_item_pic); +} + +// void (int ww,int hh) +RuntimeScriptValue Sc_SetInvDimensions(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetInvDimensions); +} + +// void (int guin,int objn, int colr) +RuntimeScriptValue Sc_SetLabelColor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetLabelColor); +} + +// void (int guin,int objn, int fontnum) +RuntimeScriptValue Sc_SetLabelFont(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetLabelFont); +} + +// void (int guin,int objn,char*newtx) +RuntimeScriptValue Sc_SetLabelText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2_POBJ(SetLabelText, const char); +} + +extern RuntimeScriptValue Sc_SetMouseBounds(const RuntimeScriptValue *params, int32_t param_count); +extern RuntimeScriptValue Sc_set_mouse_cursor(const RuntimeScriptValue *params, int32_t param_count); +extern RuntimeScriptValue Sc_SetMousePosition(const RuntimeScriptValue *params, int32_t param_count); + +// void (int mode) +RuntimeScriptValue Sc_SetMultitasking(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetMultitasking); +} + +// void (int newvol) +RuntimeScriptValue Sc_SetMusicMasterVolume(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetMusicMasterVolume); +} + +// void (int loopflag) +RuntimeScriptValue Sc_SetMusicRepeat(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetMusicRepeat); +} + +// void (int newvol) +RuntimeScriptValue Sc_SetMusicVolume(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetMusicVolume); +} + +extern RuntimeScriptValue Sc_SetNextCursor(const RuntimeScriptValue *params, int32_t param_count); + +// void (int newtrans) +RuntimeScriptValue Sc_SetNextScreenTransition(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetNextScreenTransition); +} + +extern RuntimeScriptValue Sc_SetNormalFont(const RuntimeScriptValue *params, int32_t param_count); + +// void (int obn, int basel) +RuntimeScriptValue Sc_SetObjectBaseline(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetObjectBaseline); +} + +// void (int cha, int clik) +RuntimeScriptValue Sc_SetObjectClickable(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetObjectClickable); +} + +// void (int obn,int viw,int lop,int fra) +RuntimeScriptValue Sc_SetObjectFrame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetObjectFrame); +} + +// void (int obn,int slott) +RuntimeScriptValue Sc_SetObjectGraphic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetObjectGraphic); +} + +// void (int cha, int clik) +RuntimeScriptValue Sc_SetObjectIgnoreWalkbehinds(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetObjectIgnoreWalkbehinds); +} + +// void (int objj, int tox, int toy) +RuntimeScriptValue Sc_SetObjectPosition(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetObjectPosition); +} + +// void (int obj, int red, int green, int blue, int opacity, int luminance) +RuntimeScriptValue Sc_SetObjectTint(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT6(SetObjectTint); +} + +// void (int obn,int trans) +RuntimeScriptValue Sc_SetObjectTransparency(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetObjectTransparency); +} + +// void (int obn,int vii) +RuntimeScriptValue Sc_SetObjectView(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetObjectView); +} + +// void (int inndx,int rr,int gg,int bb) +RuntimeScriptValue Sc_SetPalRGB(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetPalRGB); +} + +// void (int newchar) +RuntimeScriptValue Sc_SetPlayerCharacter(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetPlayerCharacter); +} + +// void (int area, int red, int green, int blue, int amount) +RuntimeScriptValue Sc_SetRegionTint(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT5(SetRegionTint); +} + +// void () +RuntimeScriptValue Sc_SetRestartPoint(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(SetRestartPoint); +} + +// void (int newtrans) +RuntimeScriptValue Sc_SetScreenTransition(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetScreenTransition); +} + +// void (int newval) +RuntimeScriptValue Sc_SetSkipSpeech(const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_PARAM_COUNT(SetSkipSpeech, 1); + SetSkipSpeech((SkipSpeechStyle)params[0].IValue); + return RuntimeScriptValue((int32_t)0); +} + +// void (int guin,int objn, int valn) +RuntimeScriptValue Sc_SetSliderValue(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetSliderValue); +} + +// void (int newvol) +RuntimeScriptValue Sc_SetSoundVolume(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetSoundVolume); +} + +extern RuntimeScriptValue Sc_SetSpeechFont(const RuntimeScriptValue *params, int32_t param_count); + +// void (int newstyle) +RuntimeScriptValue Sc_SetSpeechStyle(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetSpeechStyle); +} + +// void (int newvol) +RuntimeScriptValue Sc_SetSpeechVolume(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetSpeechVolume); +} + +// void (int chaa,int ncol) +RuntimeScriptValue Sc_SetTalkingColor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetTalkingColor); +} + +// void (int guin,int objn, int fontnum) +RuntimeScriptValue Sc_SetTextBoxFont(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(SetTextBoxFont); +} + +// void (int guin, int objn, char*txbuf) +RuntimeScriptValue Sc_SetTextBoxText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2_POBJ(SetTextBoxText, const char); +} + +// void (int ovrid,int xx,int yy,int wii,int fontid,int clr,char*texx,...) +RuntimeScriptValue Sc_SetTextOverlay(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(SetTextOverlay, 7); + SetTextOverlay(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, + params[4].IValue, params[5].IValue, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (int guinum) +RuntimeScriptValue Sc_SetTextWindowGUI(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetTextWindowGUI); +} + +// void (int tnum,int timeout) +RuntimeScriptValue Sc_script_SetTimer(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(script_SetTimer); +} + +// void (int offsx,int offsy) +RuntimeScriptValue Sc_SetViewport(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetViewport); +} + +// void (int newmod) +RuntimeScriptValue Sc_SetVoiceMode(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SetVoiceMode); +} + +// void (int wa,int bl) +RuntimeScriptValue Sc_SetWalkBehindBase(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetWalkBehindBase); +} + +// void (int severe) +RuntimeScriptValue Sc_ShakeScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(ShakeScreen); +} + +// void (int delay, int amount, int length) +RuntimeScriptValue Sc_ShakeScreenBackground(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(ShakeScreenBackground); +} + +// void () +RuntimeScriptValue Sc_ShowMouseCursor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(ShowMouseCursor); +} + +RuntimeScriptValue Sc_SkipCutscene(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(SkipCutscene); +} + +// void (int cc) +RuntimeScriptValue Sc_SkipUntilCharacterStops(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(SkipUntilCharacterStops); +} + +// void (int skipwith) +RuntimeScriptValue Sc_StartCutscene(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(StartCutscene); +} + +// void (int keyToStop) +RuntimeScriptValue Sc_scStartRecording(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(scStartRecording); +} + +// void (int channel) +RuntimeScriptValue Sc_StopAmbientSound(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(StopAmbientSound); +} + +// void (int chid) +RuntimeScriptValue Sc_stop_and_destroy_channel(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(stop_and_destroy_channel); +} + +// void () +RuntimeScriptValue Sc_StopDialog(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(StopDialog); +} + +// void (int chaa) +RuntimeScriptValue Sc_StopMoving(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(StopMoving); +} + +// void () +RuntimeScriptValue Sc_scr_StopMusic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(scr_StopMusic); +} + +// void (int objj) +RuntimeScriptValue Sc_StopObjectMoving(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(StopObjectMoving); +} + +// void (char*s1,char*s2) +RuntimeScriptValue Sc_sc_strcat(const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_PARAM_COUNT(_sc_strcat, 2); + _sc_strcat((char*)params[0].Ptr, (const char*)params[1].Ptr); + // NOTE: tests with old (<= 2.60) AGS show that StrCat returned the second string + // (could be result of UB, but we are doing this for more accurate emulation) + return params[1]; +} + +RuntimeScriptValue Sc_stricmp(const RuntimeScriptValue *params, int32_t param_count) +{ + // Calling C stdlib function ags_stricmp + API_SCALL_INT_POBJ2(ags_stricmp, const char, const char); +} + +RuntimeScriptValue Sc_strcmp(const RuntimeScriptValue *params, int32_t param_count) +{ + // Calling C stdlib function strcmp + API_SCALL_INT_POBJ2(strcmp, const char, const char); +} + +// int (const char *s1, const char *s2) +RuntimeScriptValue Sc_StrContains(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ2(StrContains, const char, const char); +} + +// void (char*s1, const char*s2); +RuntimeScriptValue Sc_sc_strcpy(const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_PARAM_COUNT(_sc_strcpy, 2); + _sc_strcpy((char*)params[0].Ptr, (const char*)params[1].Ptr); + return params[0]; +} + +// void (char*destt, const char*texx, ...); +RuntimeScriptValue Sc_sc_sprintf(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(_sc_sprintf, 2); + _sc_strcpy(params[0].Ptr, scsf_buffer); + return params[0]; +} + +// int (char *strin, int posn) +RuntimeScriptValue Sc_StrGetCharAt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ_PINT(StrGetCharAt, const char); +} + +// int (const char*stino) +RuntimeScriptValue Sc_StringToInt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(StringToInt, const char); +} + +RuntimeScriptValue Sc_strlen(const RuntimeScriptValue *params, int32_t param_count) +{ + // Calling C stdlib function strlen + API_SCALL_INT_POBJ(strlen, const char); +} + +// void (char *strin, int posn, int nchar) +RuntimeScriptValue Sc_StrSetCharAt(const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_PARAM_COUNT(StrSetCharAt, 3); + StrSetCharAt((char*)params[0].Ptr, params[1].IValue, params[2].IValue); + return params[0]; +} + +// void (char *desbuf) +RuntimeScriptValue Sc_sc_strlower(const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_PARAM_COUNT(_sc_strlower, 1); + _sc_strlower((char*)params[0].Ptr); + return params[0]; +} + +// void (char *desbuf) +RuntimeScriptValue Sc_sc_strupper(const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_PARAM_COUNT(_sc_strupper, 1); + _sc_strupper((char*)params[0].Ptr); + return params[0]; +} + +// void (int red, int grn, int blu) +RuntimeScriptValue Sc_TintScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(TintScreen); +} + +// void () +RuntimeScriptValue Sc_UnPauseGame(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(UnPauseGame); +} + +// void () +RuntimeScriptValue Sc_update_invorder(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(update_invorder); +} + +// void () +RuntimeScriptValue Sc_UpdatePalette(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(UpdatePalette); +} + +// void (int nloops) +RuntimeScriptValue Sc_scrWait(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(scrWait); +} + +// int (int nloops) +RuntimeScriptValue Sc_WaitKey(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(WaitKey); +} + +RuntimeScriptValue Sc_WaitMouse(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(WaitMouse); +} + +// int (int nloops) +RuntimeScriptValue Sc_WaitMouseKey(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(WaitMouseKey); +} + +RuntimeScriptValue Sc_SkipWait(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(SkipWait); +} + +//============================================================================= +// +// Exclusive API for Plugins +// +//============================================================================= + +// void (char*texx, ...) +void ScPl_sc_AbortGame(const char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + _sc_AbortGame(scsf_buffer); +} + +// int (int xx,int yy,int wii,int fontid,int clr,char*texx, ...) +int ScPl_CreateTextOverlay(int xx, int yy, int wii, int fontid, int clr, char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + return CreateTextOverlay(xx, yy, wii, fontid, clr, scsf_buffer, DISPLAYTEXT_NORMALOVERLAY); +} + +// void (char*texx, ...) +void ScPl_Display(char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + DisplaySimple(scsf_buffer); +} + +// void (int xxp,int yyp,int widd,char*texx, ...) +void ScPl_DisplayAt(int xxp, int yyp, int widd, char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + DisplayAt(xxp, yyp, widd, scsf_buffer); +} + +// void (int chid,char*texx, ...) +void ScPl_sc_displayspeech(int chid, char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + __sc_displayspeech(chid, scsf_buffer); +} + +// void (int chid, const char*texx, ...) +void ScPl_DisplayThought(int chid, const char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + DisplayThought(chid, scsf_buffer); +} + +// void (int ypos, int ttexcol, int backcol, char *title, char*texx, ...) +void ScPl_DisplayTopBar(int ypos, int ttexcol, int backcol, char *title, char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + DisplayTopBar(ypos, ttexcol, backcol, title, scsf_buffer); +} + +// void (int xx, int yy, char*texx, ...) +void ScPl_RawPrint(int xx, int yy, char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + RawPrint(xx, yy, scsf_buffer); +} + +// void (int ovrid,int xx,int yy,int wii,int fontid,int clr,char*texx,...) +void ScPl_SetTextOverlay(int ovrid, int xx, int yy, int wii, int fontid, int clr, char*texx,...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + SetTextOverlay(ovrid, xx, yy, wii, fontid, clr, scsf_buffer); +} + +// void (char*destt, const char*texx, ...); +void ScPl_sc_sprintf(char *destt, const char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + _sc_strcpy(destt, scsf_buffer); +} + + +void RegisterGlobalAPI() +{ + ccAddExternalStaticFunction("AbortGame", Sc_sc_AbortGame); + ccAddExternalStaticFunction("AddInventory", Sc_add_inventory); + ccAddExternalStaticFunction("AddInventoryToCharacter", Sc_AddInventoryToCharacter); + ccAddExternalStaticFunction("AnimateButton", Sc_AnimateButton); + ccAddExternalStaticFunction("AnimateCharacter", Sc_scAnimateCharacter); + ccAddExternalStaticFunction("AnimateCharacterEx", Sc_AnimateCharacterEx); + ccAddExternalStaticFunction("AnimateObject", Sc_AnimateObject); + ccAddExternalStaticFunction("AnimateObjectEx", Sc_AnimateObjectEx); + ccAddExternalStaticFunction("AreCharactersColliding", Sc_AreCharactersColliding); + ccAddExternalStaticFunction("AreCharObjColliding", Sc_AreCharObjColliding); + ccAddExternalStaticFunction("AreObjectsColliding", Sc_AreObjectsColliding); + ccAddExternalStaticFunction("AreThingsOverlapping", Sc_AreThingsOverlapping); + ccAddExternalStaticFunction("CallRoomScript", Sc_CallRoomScript); + ccAddExternalStaticFunction("CDAudio", Sc_cd_manager); + ccAddExternalStaticFunction("CentreGUI", Sc_CentreGUI); + ccAddExternalStaticFunction("ChangeCharacterView", Sc_ChangeCharacterView); + ccAddExternalStaticFunction("ChangeCursorGraphic", Sc_ChangeCursorGraphic); + ccAddExternalStaticFunction("ChangeCursorHotspot", Sc_ChangeCursorHotspot); + ccAddExternalStaticFunction("ClaimEvent", Sc_ClaimEvent); + ccAddExternalStaticFunction("CreateGraphicOverlay", Sc_CreateGraphicOverlay); + ccAddExternalStaticFunction("CreateTextOverlay", Sc_CreateTextOverlay); + ccAddExternalStaticFunction("CyclePalette", Sc_CyclePalette); + ccAddExternalStaticFunction("Debug", Sc_script_debug); + ccAddExternalStaticFunction("DeleteSaveSlot", Sc_DeleteSaveSlot); + ccAddExternalStaticFunction("DeleteSprite", Sc_free_dynamic_sprite); + ccAddExternalStaticFunction("DisableCursorMode", Sc_disable_cursor_mode); + ccAddExternalStaticFunction("DisableGroundLevelAreas", Sc_DisableGroundLevelAreas); + ccAddExternalStaticFunction("DisableHotspot", Sc_DisableHotspot); + ccAddExternalStaticFunction("DisableInterface", Sc_DisableInterface); + ccAddExternalStaticFunction("DisableRegion", Sc_DisableRegion); + ccAddExternalStaticFunction("Display", Sc_Display); + ccAddExternalStaticFunction("DisplayAt", Sc_DisplayAt); + ccAddExternalStaticFunction("DisplayAtY", Sc_DisplayAtY); + ccAddExternalStaticFunction("DisplayMessage", Sc_DisplayMessage); + ccAddExternalStaticFunction("DisplayMessageAtY", Sc_DisplayMessageAtY); + ccAddExternalStaticFunction("DisplayMessageBar", Sc_DisplayMessageBar); + ccAddExternalStaticFunction("DisplaySpeech", Sc_sc_displayspeech); + ccAddExternalStaticFunction("DisplaySpeechAt", Sc_DisplaySpeechAt); + ccAddExternalStaticFunction("DisplaySpeechBackground", Sc_DisplaySpeechBackground); + ccAddExternalStaticFunction("DisplayThought", Sc_DisplayThought); + ccAddExternalStaticFunction("DisplayTopBar", Sc_DisplayTopBar); + ccAddExternalStaticFunction("EnableCursorMode", Sc_enable_cursor_mode); + ccAddExternalStaticFunction("EnableGroundLevelAreas", Sc_EnableGroundLevelAreas); + ccAddExternalStaticFunction("EnableHotspot", Sc_EnableHotspot); + ccAddExternalStaticFunction("EnableInterface", Sc_EnableInterface); + ccAddExternalStaticFunction("EnableRegion", Sc_EnableRegion); + ccAddExternalStaticFunction("EndCutscene", Sc_EndCutscene); + ccAddExternalStaticFunction("FaceCharacter", Sc_FaceCharacter); + ccAddExternalStaticFunction("FaceLocation", Sc_FaceLocation); + ccAddExternalStaticFunction("FadeIn", Sc_FadeIn); + ccAddExternalStaticFunction("FadeOut", Sc_my_fade_out); + ccAddExternalStaticFunction("FileClose", Sc_FileClose); + ccAddExternalStaticFunction("FileIsEOF", Sc_FileIsEOF); + ccAddExternalStaticFunction("FileIsError", Sc_FileIsError); + // NOTE: FileOpenCMode is a backwards-compatible replacement for old-style global script function FileOpen + ccAddExternalStaticFunction("FileOpen", Sc_FileOpenCMode); + ccAddExternalStaticFunction("FileRead", Sc_FileRead); + ccAddExternalStaticFunction("FileReadInt", Sc_FileReadInt); + ccAddExternalStaticFunction("FileReadRawChar", Sc_FileReadRawChar); + ccAddExternalStaticFunction("FileReadRawInt", Sc_FileReadRawInt); + ccAddExternalStaticFunction("FileWrite", Sc_FileWrite); + ccAddExternalStaticFunction("FileWriteInt", Sc_FileWriteInt); + ccAddExternalStaticFunction("FileWriteRawChar", Sc_FileWriteRawChar); + ccAddExternalStaticFunction("FileWriteRawLine", Sc_FileWriteRawLine); + ccAddExternalStaticFunction("FindGUIID", Sc_FindGUIID); + ccAddExternalStaticFunction("FlipScreen", Sc_FlipScreen); + ccAddExternalStaticFunction("FloatToInt", Sc_FloatToInt); + ccAddExternalStaticFunction("FollowCharacter", Sc_FollowCharacter); + ccAddExternalStaticFunction("FollowCharacterEx", Sc_FollowCharacterEx); + ccAddExternalStaticFunction("GetBackgroundFrame", Sc_GetBackgroundFrame); + ccAddExternalStaticFunction("GetButtonPic", Sc_GetButtonPic); + ccAddExternalStaticFunction("GetCharacterAt", Sc_GetCharIDAtScreen); + ccAddExternalStaticFunction("GetCharacterProperty", Sc_GetCharacterProperty); + ccAddExternalStaticFunction("GetCharacterPropertyText", Sc_GetCharacterPropertyText); + ccAddExternalStaticFunction("GetCurrentMusic", Sc_GetCurrentMusic); + ccAddExternalStaticFunction("GetCursorMode", Sc_GetCursorMode); + ccAddExternalStaticFunction("GetDialogOption", Sc_GetDialogOption); + ccAddExternalStaticFunction("GetGameOption", Sc_GetGameOption); + ccAddExternalStaticFunction("GetGameParameter", Sc_GetGameParameter); + ccAddExternalStaticFunction("GetGameSpeed", Sc_GetGameSpeed); + ccAddExternalStaticFunction("GetGlobalInt", Sc_GetGlobalInt); + ccAddExternalStaticFunction("GetGlobalString", Sc_GetGlobalString); + ccAddExternalStaticFunction("GetGraphicalVariable", Sc_GetGraphicalVariable); + ccAddExternalStaticFunction("GetGUIAt", Sc_GetGUIAt); + ccAddExternalStaticFunction("GetGUIObjectAt", Sc_GetGUIObjectAt); + ccAddExternalStaticFunction("GetHotspotAt", Sc_GetHotspotIDAtScreen); + ccAddExternalStaticFunction("GetHotspotName", Sc_GetHotspotName); + ccAddExternalStaticFunction("GetHotspotPointX", Sc_GetHotspotPointX); + ccAddExternalStaticFunction("GetHotspotPointY", Sc_GetHotspotPointY); + ccAddExternalStaticFunction("GetHotspotProperty", Sc_GetHotspotProperty); + ccAddExternalStaticFunction("GetHotspotPropertyText", Sc_GetHotspotPropertyText); + ccAddExternalStaticFunction("GetInvAt", Sc_GetInvAt); + ccAddExternalStaticFunction("GetInvGraphic", Sc_GetInvGraphic); + ccAddExternalStaticFunction("GetInvName", Sc_GetInvName); + ccAddExternalStaticFunction("GetInvProperty", Sc_GetInvProperty); + ccAddExternalStaticFunction("GetInvPropertyText", Sc_GetInvPropertyText); + //ccAddExternalStaticFunction("GetLanguageString", Sc_GetLanguageString); + ccAddExternalStaticFunction("GetLocationName", Sc_GetLocationName); + ccAddExternalStaticFunction("GetLocationType", Sc_GetLocationType); + ccAddExternalStaticFunction("GetMessageText", Sc_GetMessageText); + ccAddExternalStaticFunction("GetMIDIPosition", Sc_GetMIDIPosition); + ccAddExternalStaticFunction("GetMP3PosMillis", Sc_GetMP3PosMillis); + ccAddExternalStaticFunction("GetObjectAt", Sc_GetObjectIDAtScreen); + ccAddExternalStaticFunction("GetObjectBaseline", Sc_GetObjectBaseline); + ccAddExternalStaticFunction("GetObjectGraphic", Sc_GetObjectGraphic); + ccAddExternalStaticFunction("GetObjectName", Sc_GetObjectName); + ccAddExternalStaticFunction("GetObjectProperty", Sc_GetObjectProperty); + ccAddExternalStaticFunction("GetObjectPropertyText", Sc_GetObjectPropertyText); + ccAddExternalStaticFunction("GetObjectX", Sc_GetObjectX); + ccAddExternalStaticFunction("GetObjectY", Sc_GetObjectY); + // ccAddExternalStaticFunction("GetPalette", Sc_scGetPal); + ccAddExternalStaticFunction("GetPlayerCharacter", Sc_GetPlayerCharacter); + ccAddExternalStaticFunction("GetRawTime", Sc_GetRawTime); + ccAddExternalStaticFunction("GetRegionAt", Sc_GetRegionIDAtRoom); + ccAddExternalStaticFunction("GetRoomProperty", Sc_Room_GetProperty); + ccAddExternalStaticFunction("GetRoomPropertyText", Sc_GetRoomPropertyText); + ccAddExternalStaticFunction("GetSaveSlotDescription", Sc_GetSaveSlotDescription); + ccAddExternalStaticFunction("GetScalingAt", Sc_GetScalingAt); + ccAddExternalStaticFunction("GetSliderValue", Sc_GetSliderValue); + ccAddExternalStaticFunction("GetTextBoxText", Sc_GetTextBoxText); + ccAddExternalStaticFunction("GetTextHeight", Sc_GetTextHeight); + ccAddExternalStaticFunction("GetTextWidth", Sc_GetTextWidth); + ccAddExternalStaticFunction("GetFontHeight", Sc_GetFontHeight); + ccAddExternalStaticFunction("GetFontLineSpacing", Sc_GetFontLineSpacing); + ccAddExternalStaticFunction("GetTime", Sc_sc_GetTime); + ccAddExternalStaticFunction("GetTranslation", Sc_get_translation); + ccAddExternalStaticFunction("GetTranslationName", Sc_GetTranslationName); + ccAddExternalStaticFunction("GetViewportX", Sc_GetViewportX); + ccAddExternalStaticFunction("GetViewportY", Sc_GetViewportY); + ccAddExternalStaticFunction("GetWalkableAreaAtRoom", Sc_GetWalkableAreaAtRoom); + ccAddExternalStaticFunction("GetWalkableAreaAt", Sc_GetWalkableAreaAtScreen); + ccAddExternalStaticFunction("GetWalkableAreaAtScreen", Sc_GetWalkableAreaAtScreen); + ccAddExternalStaticFunction("GetDrawingSurfaceForWalkableArea", Sc_GetDrawingSurfaceForWalkableArea); + ccAddExternalStaticFunction("GetDrawingSurfaceForWalkbehind", Sc_GetDrawingSurfaceForWalkbehind); + ccAddExternalStaticFunction("GiveScore", Sc_GiveScore); + ccAddExternalStaticFunction("HasPlayerBeenInRoom", Sc_HasPlayerBeenInRoom); + ccAddExternalStaticFunction("HideMouseCursor", Sc_HideMouseCursor); + ccAddExternalStaticFunction("InputBox", Sc_sc_inputbox); + ccAddExternalStaticFunction("InterfaceOff", Sc_InterfaceOff); + ccAddExternalStaticFunction("InterfaceOn", Sc_InterfaceOn); + ccAddExternalStaticFunction("IntToFloat", Sc_IntToFloat); + ccAddExternalStaticFunction("InventoryScreen", Sc_sc_invscreen); + ccAddExternalStaticFunction("IsButtonDown", Sc_IsButtonDown); + ccAddExternalStaticFunction("IsChannelPlaying", Sc_IsChannelPlaying); + ccAddExternalStaticFunction("IsGamePaused", Sc_IsGamePaused); + ccAddExternalStaticFunction("IsGUIOn", Sc_IsGUIOn); + ccAddExternalStaticFunction("IsInteractionAvailable", Sc_IsInteractionAvailable); + ccAddExternalStaticFunction("IsInventoryInteractionAvailable", Sc_IsInventoryInteractionAvailable); + ccAddExternalStaticFunction("IsInterfaceEnabled", Sc_IsInterfaceEnabled); + ccAddExternalStaticFunction("IsKeyPressed", Sc_IsKeyPressed); + ccAddExternalStaticFunction("IsMusicPlaying", Sc_IsMusicPlaying); + ccAddExternalStaticFunction("IsMusicVoxAvailable", Sc_IsMusicVoxAvailable); + ccAddExternalStaticFunction("IsObjectAnimating", Sc_IsObjectAnimating); + ccAddExternalStaticFunction("IsObjectMoving", Sc_IsObjectMoving); + ccAddExternalStaticFunction("IsObjectOn", Sc_IsObjectOn); + ccAddExternalStaticFunction("IsOverlayValid", Sc_IsOverlayValid); + ccAddExternalStaticFunction("IsSoundPlaying", Sc_IsSoundPlaying); + ccAddExternalStaticFunction("IsTimerExpired", Sc_IsTimerExpired); + ccAddExternalStaticFunction("IsTranslationAvailable", Sc_IsTranslationAvailable); + ccAddExternalStaticFunction("IsVoxAvailable", Sc_IsVoxAvailable); + ccAddExternalStaticFunction("ListBoxAdd", Sc_ListBoxAdd); + ccAddExternalStaticFunction("ListBoxClear", Sc_ListBoxClear); + ccAddExternalStaticFunction("ListBoxDirList", Sc_ListBoxDirList); + ccAddExternalStaticFunction("ListBoxGetItemText", Sc_ListBoxGetItemText); + ccAddExternalStaticFunction("ListBoxGetNumItems", Sc_ListBoxGetNumItems); + ccAddExternalStaticFunction("ListBoxGetSelected", Sc_ListBoxGetSelected); + ccAddExternalStaticFunction("ListBoxRemove", Sc_ListBoxRemove); + ccAddExternalStaticFunction("ListBoxSaveGameList", Sc_ListBoxSaveGameList); + ccAddExternalStaticFunction("ListBoxSetSelected", Sc_ListBoxSetSelected); + ccAddExternalStaticFunction("ListBoxSetTopItem", Sc_ListBoxSetTopItem); + ccAddExternalStaticFunction("LoadImageFile", Sc_LoadImageFile); + ccAddExternalStaticFunction("LoadSaveSlotScreenshot", Sc_LoadSaveSlotScreenshot); + ccAddExternalStaticFunction("LoseInventory", Sc_lose_inventory); + ccAddExternalStaticFunction("LoseInventoryFromCharacter", Sc_LoseInventoryFromCharacter); + ccAddExternalStaticFunction("MergeObject", Sc_MergeObject); + ccAddExternalStaticFunction("MoveCharacter", Sc_MoveCharacter); + ccAddExternalStaticFunction("MoveCharacterBlocking", Sc_MoveCharacterBlocking); + ccAddExternalStaticFunction("MoveCharacterDirect", Sc_MoveCharacterDirect); + ccAddExternalStaticFunction("MoveCharacterPath", Sc_MoveCharacterPath); + ccAddExternalStaticFunction("MoveCharacterStraight", Sc_MoveCharacterStraight); + ccAddExternalStaticFunction("MoveCharacterToHotspot", Sc_MoveCharacterToHotspot); + ccAddExternalStaticFunction("MoveCharacterToObject", Sc_MoveCharacterToObject); + ccAddExternalStaticFunction("MoveObject", Sc_MoveObject); + ccAddExternalStaticFunction("MoveObjectDirect", Sc_MoveObjectDirect); + ccAddExternalStaticFunction("MoveOverlay", Sc_MoveOverlay); + ccAddExternalStaticFunction("MoveToWalkableArea", Sc_MoveToWalkableArea); + ccAddExternalStaticFunction("NewRoom", Sc_NewRoom); + ccAddExternalStaticFunction("NewRoomEx", Sc_NewRoomEx); + ccAddExternalStaticFunction("NewRoomNPC", Sc_NewRoomNPC); + ccAddExternalStaticFunction("ObjectOff", Sc_ObjectOff); + ccAddExternalStaticFunction("ObjectOn", Sc_ObjectOn); + ccAddExternalStaticFunction("ParseText", Sc_ParseText); + ccAddExternalStaticFunction("PauseGame", Sc_PauseGame); + ccAddExternalStaticFunction("PlayAmbientSound", Sc_PlayAmbientSound); + ccAddExternalStaticFunction("PlayFlic", Sc_play_flc_file); + ccAddExternalStaticFunction("PlayMP3File", Sc_PlayMP3File); + ccAddExternalStaticFunction("PlayMusic", Sc_PlayMusicResetQueue); + ccAddExternalStaticFunction("PlayMusicQueued", Sc_PlayMusicQueued); + ccAddExternalStaticFunction("PlaySilentMIDI", Sc_PlaySilentMIDI); + ccAddExternalStaticFunction("PlaySound", Sc_play_sound); + ccAddExternalStaticFunction("PlaySoundEx", Sc_PlaySoundEx); + ccAddExternalStaticFunction("PlayVideo", Sc_scrPlayVideo); + ccAddExternalStaticFunction("QuitGame", Sc_QuitGame); + ccAddExternalStaticFunction("Random", Sc_Rand); + ccAddExternalStaticFunction("RawClearScreen", Sc_RawClear); + ccAddExternalStaticFunction("RawDrawCircle", Sc_RawDrawCircle); + ccAddExternalStaticFunction("RawDrawFrameTransparent", Sc_RawDrawFrameTransparent); + ccAddExternalStaticFunction("RawDrawImage", Sc_RawDrawImage); + ccAddExternalStaticFunction("RawDrawImageOffset", Sc_RawDrawImageOffset); + ccAddExternalStaticFunction("RawDrawImageResized", Sc_RawDrawImageResized); + ccAddExternalStaticFunction("RawDrawImageTransparent", Sc_RawDrawImageTransparent); + ccAddExternalStaticFunction("RawDrawLine", Sc_RawDrawLine); + ccAddExternalStaticFunction("RawDrawRectangle", Sc_RawDrawRectangle); + ccAddExternalStaticFunction("RawDrawTriangle", Sc_RawDrawTriangle); + ccAddExternalStaticFunction("RawPrint", Sc_RawPrint); + ccAddExternalStaticFunction("RawPrintMessageWrapped", Sc_RawPrintMessageWrapped); + ccAddExternalStaticFunction("RawRestoreScreen", Sc_RawRestoreScreen); + ccAddExternalStaticFunction("RawRestoreScreenTinted", Sc_RawRestoreScreenTinted); + ccAddExternalStaticFunction("RawSaveScreen", Sc_RawSaveScreen); + ccAddExternalStaticFunction("RawSetColor", Sc_RawSetColor); + ccAddExternalStaticFunction("RawSetColorRGB", Sc_RawSetColorRGB); + ccAddExternalStaticFunction("RefreshMouse", Sc_RefreshMouse); + ccAddExternalStaticFunction("ReleaseCharacterView", Sc_ReleaseCharacterView); + ccAddExternalStaticFunction("ReleaseViewport", Sc_ReleaseViewport); + ccAddExternalStaticFunction("RemoveObjectTint", Sc_RemoveObjectTint); + ccAddExternalStaticFunction("RemoveOverlay", Sc_RemoveOverlay); + ccAddExternalStaticFunction("RemoveWalkableArea", Sc_RemoveWalkableArea); + ccAddExternalStaticFunction("ResetRoom", Sc_ResetRoom); + ccAddExternalStaticFunction("RestartGame", Sc_restart_game); + ccAddExternalStaticFunction("RestoreGameDialog", Sc_restore_game_dialog); + ccAddExternalStaticFunction("RestoreGameSlot", Sc_RestoreGameSlot); + ccAddExternalStaticFunction("RestoreWalkableArea", Sc_RestoreWalkableArea); + ccAddExternalStaticFunction("RunAGSGame", Sc_RunAGSGame); + ccAddExternalStaticFunction("RunCharacterInteraction", Sc_RunCharacterInteraction); + ccAddExternalStaticFunction("RunDialog", Sc_RunDialog); + ccAddExternalStaticFunction("RunHotspotInteraction", Sc_RunHotspotInteraction); + ccAddExternalStaticFunction("RunInventoryInteraction", Sc_RunInventoryInteraction); + ccAddExternalStaticFunction("RunObjectInteraction", Sc_RunObjectInteraction); + ccAddExternalStaticFunction("RunRegionInteraction", Sc_RunRegionInteraction); + ccAddExternalStaticFunction("Said", Sc_Said); + ccAddExternalStaticFunction("SaidUnknownWord", Sc_SaidUnknownWord); + ccAddExternalStaticFunction("SaveCursorForLocationChange", Sc_SaveCursorForLocationChange); + ccAddExternalStaticFunction("SaveGameDialog", Sc_save_game_dialog); + ccAddExternalStaticFunction("SaveGameSlot", Sc_save_game); + ccAddExternalStaticFunction("SaveScreenShot", Sc_SaveScreenShot); + ccAddExternalStaticFunction("SeekMIDIPosition", Sc_SeekMIDIPosition); + ccAddExternalStaticFunction("SeekMODPattern", Sc_SeekMODPattern); + ccAddExternalStaticFunction("SeekMP3PosMillis", Sc_SeekMP3PosMillis); + ccAddExternalStaticFunction("SetActiveInventory", Sc_SetActiveInventory); + ccAddExternalStaticFunction("SetAmbientTint", Sc_SetAmbientTint); + ccAddExternalStaticFunction("SetAmbientLightLevel", Sc_SetAmbientLightLevel); + ccAddExternalStaticFunction("SetAreaLightLevel", Sc_SetAreaLightLevel); + ccAddExternalStaticFunction("SetAreaScaling", Sc_SetAreaScaling); + ccAddExternalStaticFunction("SetBackgroundFrame", Sc_SetBackgroundFrame); + ccAddExternalStaticFunction("SetButtonPic", Sc_SetButtonPic); + ccAddExternalStaticFunction("SetButtonText", Sc_SetButtonText); + ccAddExternalStaticFunction("SetChannelVolume", Sc_SetChannelVolume); + ccAddExternalStaticFunction("SetCharacterBaseline", Sc_SetCharacterBaseline); + ccAddExternalStaticFunction("SetCharacterClickable", Sc_SetCharacterClickable); + ccAddExternalStaticFunction("SetCharacterFrame", Sc_SetCharacterFrame); + ccAddExternalStaticFunction("SetCharacterIdle", Sc_SetCharacterIdle); + ccAddExternalStaticFunction("SetCharacterIgnoreLight", Sc_SetCharacterIgnoreLight); + ccAddExternalStaticFunction("SetCharacterIgnoreWalkbehinds", Sc_SetCharacterIgnoreWalkbehinds); + ccAddExternalStaticFunction("SetCharacterProperty", Sc_SetCharacterProperty); + ccAddExternalStaticFunction("SetCharacterBlinkView", Sc_SetCharacterBlinkView); + ccAddExternalStaticFunction("SetCharacterSpeechView", Sc_SetCharacterSpeechView); + ccAddExternalStaticFunction("SetCharacterSpeed", Sc_SetCharacterSpeed); + ccAddExternalStaticFunction("SetCharacterSpeedEx", Sc_SetCharacterSpeedEx); + ccAddExternalStaticFunction("SetCharacterTransparency", Sc_SetCharacterTransparency); + ccAddExternalStaticFunction("SetCharacterView", Sc_SetCharacterView); + ccAddExternalStaticFunction("SetCharacterViewEx", Sc_SetCharacterViewEx); + ccAddExternalStaticFunction("SetCharacterViewOffset", Sc_SetCharacterViewOffset); + ccAddExternalStaticFunction("SetCursorMode", Sc_set_cursor_mode); + ccAddExternalStaticFunction("SetDefaultCursor", Sc_set_default_cursor); + ccAddExternalStaticFunction("SetDialogOption", Sc_SetDialogOption); + ccAddExternalStaticFunction("SetDigitalMasterVolume", Sc_SetDigitalMasterVolume); + ccAddExternalStaticFunction("SetFadeColor", Sc_SetFadeColor); + ccAddExternalStaticFunction("SetFrameSound", Sc_SetFrameSound); + ccAddExternalStaticFunction("SetGameOption", Sc_SetGameOption); + ccAddExternalStaticFunction("SetGameSpeed", Sc_SetGameSpeed); + ccAddExternalStaticFunction("SetGlobalInt", Sc_SetGlobalInt); + ccAddExternalStaticFunction("SetGlobalString", Sc_SetGlobalString); + ccAddExternalStaticFunction("SetGraphicalVariable", Sc_SetGraphicalVariable); + ccAddExternalStaticFunction("SetGUIBackgroundPic", Sc_SetGUIBackgroundPic); + ccAddExternalStaticFunction("SetGUIClickable", Sc_SetGUIClickable); + ccAddExternalStaticFunction("SetGUIObjectEnabled", Sc_SetGUIObjectEnabled); + ccAddExternalStaticFunction("SetGUIObjectPosition", Sc_SetGUIObjectPosition); + ccAddExternalStaticFunction("SetGUIObjectSize", Sc_SetGUIObjectSize); + ccAddExternalStaticFunction("SetGUIPosition", Sc_SetGUIPosition); + ccAddExternalStaticFunction("SetGUISize", Sc_SetGUISize); + ccAddExternalStaticFunction("SetGUITransparency", Sc_SetGUITransparency); + ccAddExternalStaticFunction("SetGUIZOrder", Sc_SetGUIZOrder); + ccAddExternalStaticFunction("SetInvItemName", Sc_SetInvItemName); + ccAddExternalStaticFunction("SetInvItemPic", Sc_set_inv_item_pic); + ccAddExternalStaticFunction("SetInvDimensions", Sc_SetInvDimensions); + ccAddExternalStaticFunction("SetLabelColor", Sc_SetLabelColor); + ccAddExternalStaticFunction("SetLabelFont", Sc_SetLabelFont); + ccAddExternalStaticFunction("SetLabelText", Sc_SetLabelText); + ccAddExternalStaticFunction("SetMouseBounds", Sc_SetMouseBounds); + ccAddExternalStaticFunction("SetMouseCursor", Sc_set_mouse_cursor); + ccAddExternalStaticFunction("SetMousePosition", Sc_SetMousePosition); + ccAddExternalStaticFunction("SetMultitaskingMode", Sc_SetMultitasking); + ccAddExternalStaticFunction("SetMusicMasterVolume", Sc_SetMusicMasterVolume); + ccAddExternalStaticFunction("SetMusicRepeat", Sc_SetMusicRepeat); + ccAddExternalStaticFunction("SetMusicVolume", Sc_SetMusicVolume); + ccAddExternalStaticFunction("SetNextCursorMode", Sc_SetNextCursor); + ccAddExternalStaticFunction("SetNextScreenTransition", Sc_SetNextScreenTransition); + ccAddExternalStaticFunction("SetNormalFont", Sc_SetNormalFont); + ccAddExternalStaticFunction("SetObjectBaseline", Sc_SetObjectBaseline); + ccAddExternalStaticFunction("SetObjectClickable", Sc_SetObjectClickable); + ccAddExternalStaticFunction("SetObjectFrame", Sc_SetObjectFrame); + ccAddExternalStaticFunction("SetObjectGraphic", Sc_SetObjectGraphic); + ccAddExternalStaticFunction("SetObjectIgnoreWalkbehinds", Sc_SetObjectIgnoreWalkbehinds); + ccAddExternalStaticFunction("SetObjectPosition", Sc_SetObjectPosition); + ccAddExternalStaticFunction("SetObjectTint", Sc_SetObjectTint); + ccAddExternalStaticFunction("SetObjectTransparency", Sc_SetObjectTransparency); + ccAddExternalStaticFunction("SetObjectView", Sc_SetObjectView); + // ccAddExternalStaticFunction("SetPalette", scSetPal); + ccAddExternalStaticFunction("SetPalRGB", Sc_SetPalRGB); + ccAddExternalStaticFunction("SetPlayerCharacter", Sc_SetPlayerCharacter); + ccAddExternalStaticFunction("SetRegionTint", Sc_SetRegionTint); + ccAddExternalStaticFunction("SetRestartPoint", Sc_SetRestartPoint); + ccAddExternalStaticFunction("SetScreenTransition", Sc_SetScreenTransition); + ccAddExternalStaticFunction("SetSkipSpeech", Sc_SetSkipSpeech); + ccAddExternalStaticFunction("SetSliderValue", Sc_SetSliderValue); + ccAddExternalStaticFunction("SetSoundVolume", Sc_SetSoundVolume); + ccAddExternalStaticFunction("SetSpeechFont", Sc_SetSpeechFont); + ccAddExternalStaticFunction("SetSpeechStyle", Sc_SetSpeechStyle); + ccAddExternalStaticFunction("SetSpeechVolume", Sc_SetSpeechVolume); + ccAddExternalStaticFunction("SetTalkingColor", Sc_SetTalkingColor); + ccAddExternalStaticFunction("SetTextBoxFont", Sc_SetTextBoxFont); + ccAddExternalStaticFunction("SetTextBoxText", Sc_SetTextBoxText); + ccAddExternalStaticFunction("SetTextOverlay", Sc_SetTextOverlay); + ccAddExternalStaticFunction("SetTextWindowGUI", Sc_SetTextWindowGUI); + ccAddExternalStaticFunction("SetTimer", Sc_script_SetTimer); + ccAddExternalStaticFunction("SetViewport", Sc_SetViewport); + ccAddExternalStaticFunction("SetVoiceMode", Sc_SetVoiceMode); + ccAddExternalStaticFunction("SetWalkBehindBase", Sc_SetWalkBehindBase); + ccAddExternalStaticFunction("ShakeScreen", Sc_ShakeScreen); + ccAddExternalStaticFunction("ShakeScreenBackground", Sc_ShakeScreenBackground); + ccAddExternalStaticFunction("ShowMouseCursor", Sc_ShowMouseCursor); + ccAddExternalStaticFunction("SkipCutscene", Sc_SkipCutscene); + ccAddExternalStaticFunction("SkipUntilCharacterStops", Sc_SkipUntilCharacterStops); + ccAddExternalStaticFunction("StartCutscene", Sc_StartCutscene); + ccAddExternalStaticFunction("StartRecording", Sc_scStartRecording); + ccAddExternalStaticFunction("StopAmbientSound", Sc_StopAmbientSound); + ccAddExternalStaticFunction("StopChannel", Sc_stop_and_destroy_channel); + ccAddExternalStaticFunction("StopDialog", Sc_StopDialog); + ccAddExternalStaticFunction("StopMoving", Sc_StopMoving); + ccAddExternalStaticFunction("StopMusic", Sc_scr_StopMusic); + ccAddExternalStaticFunction("StopObjectMoving", Sc_StopObjectMoving); + ccAddExternalStaticFunction("StrCat", Sc_sc_strcat); + ccAddExternalStaticFunction("StrCaseComp", Sc_stricmp); + ccAddExternalStaticFunction("StrComp", Sc_strcmp); + ccAddExternalStaticFunction("StrContains", Sc_StrContains); + ccAddExternalStaticFunction("StrCopy", Sc_sc_strcpy); + ccAddExternalStaticFunction("StrFormat", Sc_sc_sprintf); + ccAddExternalStaticFunction("StrGetCharAt", Sc_StrGetCharAt); + ccAddExternalStaticFunction("StringToInt", Sc_StringToInt); + ccAddExternalStaticFunction("StrLen", Sc_strlen); + ccAddExternalStaticFunction("StrSetCharAt", Sc_StrSetCharAt); + ccAddExternalStaticFunction("StrToLowerCase", Sc_sc_strlower); + ccAddExternalStaticFunction("StrToUpperCase", Sc_sc_strupper); + ccAddExternalStaticFunction("TintScreen", Sc_TintScreen); + ccAddExternalStaticFunction("UnPauseGame", Sc_UnPauseGame); + ccAddExternalStaticFunction("UpdateInventory", Sc_update_invorder); + ccAddExternalStaticFunction("UpdatePalette", Sc_UpdatePalette); + ccAddExternalStaticFunction("Wait", Sc_scrWait); + ccAddExternalStaticFunction("WaitKey", Sc_WaitKey); + ccAddExternalStaticFunction("WaitMouse", Sc_WaitMouse); + ccAddExternalStaticFunction("WaitMouseKey", Sc_WaitMouseKey); + ccAddExternalStaticFunction("SkipWait", Sc_SkipWait); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("AbortGame", (void*)ScPl_sc_AbortGame); + ccAddExternalFunctionForPlugin("AddInventory", (void*)add_inventory); + ccAddExternalFunctionForPlugin("AddInventoryToCharacter", (void*)AddInventoryToCharacter); + ccAddExternalFunctionForPlugin("AnimateButton", (void*)AnimateButton); + ccAddExternalFunctionForPlugin("AnimateCharacter", (void*)scAnimateCharacter); + ccAddExternalFunctionForPlugin("AnimateCharacterEx", (void*)AnimateCharacterEx); + ccAddExternalFunctionForPlugin("AnimateObject", (void*)AnimateObject); + ccAddExternalFunctionForPlugin("AnimateObjectEx", (void*)AnimateObjectEx); + ccAddExternalFunctionForPlugin("AreCharactersColliding", (void*)AreCharactersColliding); + ccAddExternalFunctionForPlugin("AreCharObjColliding", (void*)AreCharObjColliding); + ccAddExternalFunctionForPlugin("AreObjectsColliding", (void*)AreObjectsColliding); + ccAddExternalFunctionForPlugin("AreThingsOverlapping", (void*)AreThingsOverlapping); + ccAddExternalFunctionForPlugin("CallRoomScript", (void*)CallRoomScript); + ccAddExternalFunctionForPlugin("CDAudio", (void*)cd_manager); + ccAddExternalFunctionForPlugin("CentreGUI", (void*)CentreGUI); + ccAddExternalFunctionForPlugin("ChangeCharacterView", (void*)ChangeCharacterView); + ccAddExternalFunctionForPlugin("ChangeCursorGraphic", (void*)ChangeCursorGraphic); + ccAddExternalFunctionForPlugin("ChangeCursorHotspot", (void*)ChangeCursorHotspot); + ccAddExternalFunctionForPlugin("ClaimEvent", (void*)ClaimEvent); + ccAddExternalFunctionForPlugin("CreateGraphicOverlay", (void*)CreateGraphicOverlay); + ccAddExternalFunctionForPlugin("CreateTextOverlay", (void*)ScPl_CreateTextOverlay); + ccAddExternalFunctionForPlugin("CyclePalette", (void*)CyclePalette); + ccAddExternalFunctionForPlugin("Debug", (void*)script_debug); + ccAddExternalFunctionForPlugin("DeleteSaveSlot", (void*)DeleteSaveSlot); + ccAddExternalFunctionForPlugin("DeleteSprite", (void*)free_dynamic_sprite); + ccAddExternalFunctionForPlugin("DisableCursorMode", (void*)disable_cursor_mode); + ccAddExternalFunctionForPlugin("DisableGroundLevelAreas", (void*)DisableGroundLevelAreas); + ccAddExternalFunctionForPlugin("DisableHotspot", (void*)DisableHotspot); + ccAddExternalFunctionForPlugin("DisableInterface", (void*)DisableInterface); + ccAddExternalFunctionForPlugin("DisableRegion", (void*)DisableRegion); + ccAddExternalFunctionForPlugin("Display", (void*)ScPl_Display); + ccAddExternalFunctionForPlugin("DisplayAt", (void*)ScPl_DisplayAt); + ccAddExternalFunctionForPlugin("DisplayAtY", (void*)DisplayAtY); + ccAddExternalFunctionForPlugin("DisplayMessage", (void*)DisplayMessage); + ccAddExternalFunctionForPlugin("DisplayMessageAtY", (void*)DisplayMessageAtY); + ccAddExternalFunctionForPlugin("DisplayMessageBar", (void*)DisplayMessageBar); + ccAddExternalFunctionForPlugin("DisplaySpeech", (void*)ScPl_sc_displayspeech); + ccAddExternalFunctionForPlugin("DisplaySpeechAt", (void*)DisplaySpeechAt); + ccAddExternalFunctionForPlugin("DisplaySpeechBackground", (void*)DisplaySpeechBackground); + ccAddExternalFunctionForPlugin("DisplayThought", (void*)ScPl_DisplayThought); + ccAddExternalFunctionForPlugin("DisplayTopBar", (void*)ScPl_DisplayTopBar); + ccAddExternalFunctionForPlugin("EnableCursorMode", (void*)enable_cursor_mode); + ccAddExternalFunctionForPlugin("EnableGroundLevelAreas", (void*)EnableGroundLevelAreas); + ccAddExternalFunctionForPlugin("EnableHotspot", (void*)EnableHotspot); + ccAddExternalFunctionForPlugin("EnableInterface", (void*)EnableInterface); + ccAddExternalFunctionForPlugin("EnableRegion", (void*)EnableRegion); + ccAddExternalFunctionForPlugin("EndCutscene", (void*)EndCutscene); + ccAddExternalFunctionForPlugin("FaceCharacter", (void*)FaceCharacter); + ccAddExternalFunctionForPlugin("FaceLocation", (void*)FaceLocation); + ccAddExternalFunctionForPlugin("FadeIn", (void*)FadeIn); + ccAddExternalFunctionForPlugin("FadeOut", (void*)my_fade_out); + ccAddExternalFunctionForPlugin("FileClose", (void*)FileClose); + ccAddExternalFunctionForPlugin("FileIsEOF", (void*)FileIsEOF); + ccAddExternalFunctionForPlugin("FileIsError", (void*)FileIsError); + // NOTE: FileOpenCMode is a backwards-compatible replacement for old-style global script function FileOpen + ccAddExternalFunctionForPlugin("FileOpen", (void*)FileOpenCMode); + ccAddExternalFunctionForPlugin("FileRead", (void*)FileRead); + ccAddExternalFunctionForPlugin("FileReadInt", (void*)FileReadInt); + ccAddExternalFunctionForPlugin("FileReadRawChar", (void*)FileReadRawChar); + ccAddExternalFunctionForPlugin("FileReadRawInt", (void*)FileReadRawInt); + ccAddExternalFunctionForPlugin("FileWrite", (void*)FileWrite); + ccAddExternalFunctionForPlugin("FileWriteInt", (void*)FileWriteInt); + ccAddExternalFunctionForPlugin("FileWriteRawChar", (void*)FileWriteRawChar); + ccAddExternalFunctionForPlugin("FileWriteRawLine", (void*)FileWriteRawLine); + ccAddExternalFunctionForPlugin("FindGUIID", (void*)FindGUIID); + ccAddExternalFunctionForPlugin("FlipScreen", (void*)FlipScreen); + ccAddExternalFunctionForPlugin("FloatToInt", (void*)FloatToInt); + ccAddExternalFunctionForPlugin("FollowCharacter", (void*)FollowCharacter); + ccAddExternalFunctionForPlugin("FollowCharacterEx", (void*)FollowCharacterEx); + ccAddExternalFunctionForPlugin("GetBackgroundFrame", (void*)GetBackgroundFrame); + ccAddExternalFunctionForPlugin("GetButtonPic", (void*)GetButtonPic); + ccAddExternalFunctionForPlugin("GetCharacterAt", (void*)GetCharIDAtScreen); + ccAddExternalFunctionForPlugin("GetCharacterProperty", (void*)GetCharacterProperty); + ccAddExternalFunctionForPlugin("GetCharacterPropertyText", (void*)GetCharacterPropertyText); + ccAddExternalFunctionForPlugin("GetCurrentMusic", (void*)GetCurrentMusic); + ccAddExternalFunctionForPlugin("GetCursorMode", (void*)GetCursorMode); + ccAddExternalFunctionForPlugin("GetDialogOption", (void*)GetDialogOption); + ccAddExternalFunctionForPlugin("GetGameOption", (void*)GetGameOption); + ccAddExternalFunctionForPlugin("GetGameParameter", (void*)GetGameParameter); + ccAddExternalFunctionForPlugin("GetGameSpeed", (void*)GetGameSpeed); + ccAddExternalFunctionForPlugin("GetGlobalInt", (void*)GetGlobalInt); + ccAddExternalFunctionForPlugin("GetGlobalString", (void*)GetGlobalString); + ccAddExternalFunctionForPlugin("GetGraphicalVariable", (void*)GetGraphicalVariable); + ccAddExternalFunctionForPlugin("GetGUIAt", (void*)GetGUIAt); + ccAddExternalFunctionForPlugin("GetGUIObjectAt", (void*)GetGUIObjectAt); + ccAddExternalFunctionForPlugin("GetHotspotAt", (void*)GetHotspotIDAtScreen); + ccAddExternalFunctionForPlugin("GetHotspotName", (void*)GetHotspotName); + ccAddExternalFunctionForPlugin("GetHotspotPointX", (void*)GetHotspotPointX); + ccAddExternalFunctionForPlugin("GetHotspotPointY", (void*)GetHotspotPointY); + ccAddExternalFunctionForPlugin("GetHotspotProperty", (void*)GetHotspotProperty); + ccAddExternalFunctionForPlugin("GetHotspotPropertyText", (void*)GetHotspotPropertyText); + ccAddExternalFunctionForPlugin("GetInvAt", (void*)GetInvAt); + ccAddExternalFunctionForPlugin("GetInvGraphic", (void*)GetInvGraphic); + ccAddExternalFunctionForPlugin("GetInvName", (void*)GetInvName); + ccAddExternalFunctionForPlugin("GetInvProperty", (void*)GetInvProperty); + ccAddExternalFunctionForPlugin("GetInvPropertyText", (void*)GetInvPropertyText); + //ccAddExternalFunctionForPlugin("GetLanguageString", (void*)GetLanguageString); + ccAddExternalFunctionForPlugin("GetLocationName", (void*)GetLocationName); + ccAddExternalFunctionForPlugin("GetLocationType", (void*)GetLocationType); + ccAddExternalFunctionForPlugin("GetMessageText", (void*)GetMessageText); + ccAddExternalFunctionForPlugin("GetMIDIPosition", (void*)GetMIDIPosition); + ccAddExternalFunctionForPlugin("GetMP3PosMillis", (void*)GetMP3PosMillis); + ccAddExternalFunctionForPlugin("GetObjectAt", (void*)GetObjectIDAtScreen); + ccAddExternalFunctionForPlugin("GetObjectBaseline", (void*)GetObjectBaseline); + ccAddExternalFunctionForPlugin("GetObjectGraphic", (void*)GetObjectGraphic); + ccAddExternalFunctionForPlugin("GetObjectName", (void*)GetObjectName); + ccAddExternalFunctionForPlugin("GetObjectProperty", (void*)GetObjectProperty); + ccAddExternalFunctionForPlugin("GetObjectPropertyText", (void*)GetObjectPropertyText); + ccAddExternalFunctionForPlugin("GetObjectX", (void*)GetObjectX); + ccAddExternalFunctionForPlugin("GetObjectY", (void*)GetObjectY); + // ccAddExternalFunctionForPlugin("GetPalette", (void*)scGetPal); + ccAddExternalFunctionForPlugin("GetPlayerCharacter", (void*)GetPlayerCharacter); + ccAddExternalFunctionForPlugin("GetRawTime", (void*)GetRawTime); + ccAddExternalFunctionForPlugin("GetRegionAt", (void*)GetRegionIDAtRoom); + ccAddExternalFunctionForPlugin("GetRoomProperty", (void*)Room_GetProperty); + ccAddExternalFunctionForPlugin("GetRoomPropertyText", (void*)GetRoomPropertyText); + ccAddExternalFunctionForPlugin("GetSaveSlotDescription", (void*)GetSaveSlotDescription); + ccAddExternalFunctionForPlugin("GetScalingAt", (void*)GetScalingAt); + ccAddExternalFunctionForPlugin("GetSliderValue", (void*)GetSliderValue); + ccAddExternalFunctionForPlugin("GetTextBoxText", (void*)GetTextBoxText); + ccAddExternalFunctionForPlugin("GetTextHeight", (void*)GetTextHeight); + ccAddExternalFunctionForPlugin("GetTextWidth", (void*)GetTextWidth); + ccAddExternalFunctionForPlugin("GetTime", (void*)sc_GetTime); + ccAddExternalFunctionForPlugin("GetTranslation", (void*)get_translation); + ccAddExternalFunctionForPlugin("GetTranslationName", (void*)GetTranslationName); + ccAddExternalFunctionForPlugin("GetViewportX", (void*)GetViewportX); + ccAddExternalFunctionForPlugin("GetViewportY", (void*)GetViewportY); + ccAddExternalFunctionForPlugin("GetWalkableAreaAtRoom", (void*)GetWalkableAreaAtRoom); + ccAddExternalFunctionForPlugin("GetWalkableAreaAt", (void*)GetWalkableAreaAtScreen); + ccAddExternalFunctionForPlugin("GetWalkableAreaAtScreen", (void*)GetWalkableAreaAtScreen); + ccAddExternalFunctionForPlugin("GiveScore", (void*)GiveScore); + ccAddExternalFunctionForPlugin("HasPlayerBeenInRoom", (void*)HasPlayerBeenInRoom); + ccAddExternalFunctionForPlugin("HideMouseCursor", (void*)HideMouseCursor); + ccAddExternalFunctionForPlugin("InputBox", (void*)sc_inputbox); + ccAddExternalFunctionForPlugin("InterfaceOff", (void*)InterfaceOff); + ccAddExternalFunctionForPlugin("InterfaceOn", (void*)InterfaceOn); + ccAddExternalFunctionForPlugin("IntToFloat", (void*)IntToFloat); + ccAddExternalFunctionForPlugin("InventoryScreen", (void*)sc_invscreen); + ccAddExternalFunctionForPlugin("IsButtonDown", (void*)IsButtonDown); + ccAddExternalFunctionForPlugin("IsChannelPlaying", (void*)IsChannelPlaying); + ccAddExternalFunctionForPlugin("IsGamePaused", (void*)IsGamePaused); + ccAddExternalFunctionForPlugin("IsGUIOn", (void*)IsGUIOn); + ccAddExternalFunctionForPlugin("IsInteractionAvailable", (void*)IsInteractionAvailable); + ccAddExternalFunctionForPlugin("IsInventoryInteractionAvailable", (void*)IsInventoryInteractionAvailable); + ccAddExternalFunctionForPlugin("IsInterfaceEnabled", (void*)IsInterfaceEnabled); + ccAddExternalFunctionForPlugin("IsKeyPressed", (void*)IsKeyPressed); + ccAddExternalFunctionForPlugin("IsMusicPlaying", (void*)IsMusicPlaying); + ccAddExternalFunctionForPlugin("IsMusicVoxAvailable", (void*)IsMusicVoxAvailable); + ccAddExternalFunctionForPlugin("IsObjectAnimating", (void*)IsObjectAnimating); + ccAddExternalFunctionForPlugin("IsObjectMoving", (void*)IsObjectMoving); + ccAddExternalFunctionForPlugin("IsObjectOn", (void*)IsObjectOn); + ccAddExternalFunctionForPlugin("IsOverlayValid", (void*)IsOverlayValid); + ccAddExternalFunctionForPlugin("IsSoundPlaying", (void*)IsSoundPlaying); + ccAddExternalFunctionForPlugin("IsTimerExpired", (void*)IsTimerExpired); + ccAddExternalFunctionForPlugin("IsTranslationAvailable", (void*)IsTranslationAvailable); + ccAddExternalFunctionForPlugin("IsVoxAvailable", (void*)IsVoxAvailable); + ccAddExternalFunctionForPlugin("ListBoxAdd", (void*)ListBoxAdd); + ccAddExternalFunctionForPlugin("ListBoxClear", (void*)ListBoxClear); + ccAddExternalFunctionForPlugin("ListBoxDirList", (void*)ListBoxDirList); + ccAddExternalFunctionForPlugin("ListBoxGetItemText", (void*)ListBoxGetItemText); + ccAddExternalFunctionForPlugin("ListBoxGetNumItems", (void*)ListBoxGetNumItems); + ccAddExternalFunctionForPlugin("ListBoxGetSelected", (void*)ListBoxGetSelected); + ccAddExternalFunctionForPlugin("ListBoxRemove", (void*)ListBoxRemove); + ccAddExternalFunctionForPlugin("ListBoxSaveGameList", (void*)ListBoxSaveGameList); + ccAddExternalFunctionForPlugin("ListBoxSetSelected", (void*)ListBoxSetSelected); + ccAddExternalFunctionForPlugin("ListBoxSetTopItem", (void*)ListBoxSetTopItem); + ccAddExternalFunctionForPlugin("LoadImageFile", (void*)LoadImageFile); + ccAddExternalFunctionForPlugin("LoadSaveSlotScreenshot", (void*)LoadSaveSlotScreenshot); + ccAddExternalFunctionForPlugin("LoseInventory", (void*)lose_inventory); + ccAddExternalFunctionForPlugin("LoseInventoryFromCharacter", (void*)LoseInventoryFromCharacter); + ccAddExternalFunctionForPlugin("MergeObject", (void*)MergeObject); + ccAddExternalFunctionForPlugin("MoveCharacter", (void*)MoveCharacter); + ccAddExternalFunctionForPlugin("MoveCharacterBlocking", (void*)MoveCharacterBlocking); + ccAddExternalFunctionForPlugin("MoveCharacterDirect", (void*)MoveCharacterDirect); + ccAddExternalFunctionForPlugin("MoveCharacterPath", (void*)MoveCharacterPath); + ccAddExternalFunctionForPlugin("MoveCharacterStraight", (void*)MoveCharacterStraight); + ccAddExternalFunctionForPlugin("MoveCharacterToHotspot", (void*)MoveCharacterToHotspot); + ccAddExternalFunctionForPlugin("MoveCharacterToObject", (void*)MoveCharacterToObject); + ccAddExternalFunctionForPlugin("MoveObject", (void*)MoveObject); + ccAddExternalFunctionForPlugin("MoveObjectDirect", (void*)MoveObjectDirect); + ccAddExternalFunctionForPlugin("MoveOverlay", (void*)MoveOverlay); + ccAddExternalFunctionForPlugin("MoveToWalkableArea", (void*)MoveToWalkableArea); + ccAddExternalFunctionForPlugin("NewRoom", (void*)NewRoom); + ccAddExternalFunctionForPlugin("NewRoomEx", (void*)NewRoomEx); + ccAddExternalFunctionForPlugin("NewRoomNPC", (void*)NewRoomNPC); + ccAddExternalFunctionForPlugin("ObjectOff", (void*)ObjectOff); + ccAddExternalFunctionForPlugin("ObjectOn", (void*)ObjectOn); + ccAddExternalFunctionForPlugin("ParseText", (void*)ParseText); + ccAddExternalFunctionForPlugin("PauseGame", (void*)PauseGame); + ccAddExternalFunctionForPlugin("PlayAmbientSound", (void*)PlayAmbientSound); + ccAddExternalFunctionForPlugin("PlayFlic", (void*)play_flc_file); + ccAddExternalFunctionForPlugin("PlayMP3File", (void*)PlayMP3File); + ccAddExternalFunctionForPlugin("PlayMusic", (void*)PlayMusicResetQueue); + ccAddExternalFunctionForPlugin("PlayMusicQueued", (void*)PlayMusicQueued); + ccAddExternalFunctionForPlugin("PlaySilentMIDI", (void*)PlaySilentMIDI); + ccAddExternalFunctionForPlugin("PlaySound", (void*)play_sound); + ccAddExternalFunctionForPlugin("PlaySoundEx", (void*)PlaySoundEx); + ccAddExternalFunctionForPlugin("PlayVideo", (void*)scrPlayVideo); + ccAddExternalFunctionForPlugin("ProcessClick", (void*)RoomProcessClick); + ccAddExternalFunctionForPlugin("QuitGame", (void*)QuitGame); + ccAddExternalFunctionForPlugin("Random", (void*)__Rand); + ccAddExternalFunctionForPlugin("RawClearScreen", (void*)RawClear); + ccAddExternalFunctionForPlugin("RawDrawCircle", (void*)RawDrawCircle); + ccAddExternalFunctionForPlugin("RawDrawFrameTransparent", (void*)RawDrawFrameTransparent); + ccAddExternalFunctionForPlugin("RawDrawImage", (void*)RawDrawImage); + ccAddExternalFunctionForPlugin("RawDrawImageOffset", (void*)RawDrawImageOffset); + ccAddExternalFunctionForPlugin("RawDrawImageResized", (void*)RawDrawImageResized); + ccAddExternalFunctionForPlugin("RawDrawImageTransparent", (void*)RawDrawImageTransparent); + ccAddExternalFunctionForPlugin("RawDrawLine", (void*)RawDrawLine); + ccAddExternalFunctionForPlugin("RawDrawRectangle", (void*)RawDrawRectangle); + ccAddExternalFunctionForPlugin("RawDrawTriangle", (void*)RawDrawTriangle); + ccAddExternalFunctionForPlugin("RawPrint", (void*)ScPl_RawPrint); + ccAddExternalFunctionForPlugin("RawPrintMessageWrapped", (void*)RawPrintMessageWrapped); + ccAddExternalFunctionForPlugin("RawRestoreScreen", (void*)RawRestoreScreen); + ccAddExternalFunctionForPlugin("RawRestoreScreenTinted", (void*)RawRestoreScreenTinted); + ccAddExternalFunctionForPlugin("RawSaveScreen", (void*)RawSaveScreen); + ccAddExternalFunctionForPlugin("RawSetColor", (void*)RawSetColor); + ccAddExternalFunctionForPlugin("RawSetColorRGB", (void*)RawSetColorRGB); + ccAddExternalFunctionForPlugin("RefreshMouse", (void*)RefreshMouse); + ccAddExternalFunctionForPlugin("ReleaseCharacterView", (void*)ReleaseCharacterView); + ccAddExternalFunctionForPlugin("ReleaseViewport", (void*)ReleaseViewport); + ccAddExternalFunctionForPlugin("RemoveObjectTint", (void*)RemoveObjectTint); + ccAddExternalFunctionForPlugin("RemoveOverlay", (void*)RemoveOverlay); + ccAddExternalFunctionForPlugin("RemoveWalkableArea", (void*)RemoveWalkableArea); + ccAddExternalFunctionForPlugin("ResetRoom", (void*)ResetRoom); + ccAddExternalFunctionForPlugin("RestartGame", (void*)restart_game); + ccAddExternalFunctionForPlugin("RestoreGameDialog", (void*)restore_game_dialog); + ccAddExternalFunctionForPlugin("RestoreGameSlot", (void*)RestoreGameSlot); + ccAddExternalFunctionForPlugin("RestoreWalkableArea", (void*)RestoreWalkableArea); + ccAddExternalFunctionForPlugin("RunAGSGame", (void*)RunAGSGame); + ccAddExternalFunctionForPlugin("RunCharacterInteraction", (void*)RunCharacterInteraction); + ccAddExternalFunctionForPlugin("RunDialog", (void*)RunDialog); + ccAddExternalFunctionForPlugin("RunHotspotInteraction", (void*)RunHotspotInteraction); + ccAddExternalFunctionForPlugin("RunInventoryInteraction", (void*)RunInventoryInteraction); + ccAddExternalFunctionForPlugin("RunObjectInteraction", (void*)RunObjectInteraction); + ccAddExternalFunctionForPlugin("RunRegionInteraction", (void*)RunRegionInteraction); + ccAddExternalFunctionForPlugin("Said", (void*)Said); + ccAddExternalFunctionForPlugin("SaidUnknownWord", (void*)SaidUnknownWord); + ccAddExternalFunctionForPlugin("SaveCursorForLocationChange", (void*)SaveCursorForLocationChange); + ccAddExternalFunctionForPlugin("SaveGameDialog", (void*)save_game_dialog); + ccAddExternalFunctionForPlugin("SaveGameSlot", (void*)save_game); + ccAddExternalFunctionForPlugin("SaveScreenShot", (void*)SaveScreenShot); + ccAddExternalFunctionForPlugin("SeekMIDIPosition", (void*)SeekMIDIPosition); + ccAddExternalFunctionForPlugin("SeekMODPattern", (void*)SeekMODPattern); + ccAddExternalFunctionForPlugin("SeekMP3PosMillis", (void*)SeekMP3PosMillis); + ccAddExternalFunctionForPlugin("SetActiveInventory", (void*)SetActiveInventory); + ccAddExternalFunctionForPlugin("SetAmbientTint", (void*)SetAmbientTint); + ccAddExternalFunctionForPlugin("SetAreaLightLevel", (void*)SetAreaLightLevel); + ccAddExternalFunctionForPlugin("SetAreaScaling", (void*)SetAreaScaling); + ccAddExternalFunctionForPlugin("SetBackgroundFrame", (void*)SetBackgroundFrame); + ccAddExternalFunctionForPlugin("SetButtonPic", (void*)SetButtonPic); + ccAddExternalFunctionForPlugin("SetButtonText", (void*)SetButtonText); + ccAddExternalFunctionForPlugin("SetChannelVolume", (void*)SetChannelVolume); + ccAddExternalFunctionForPlugin("SetCharacterBaseline", (void*)SetCharacterBaseline); + ccAddExternalFunctionForPlugin("SetCharacterClickable", (void*)SetCharacterClickable); + ccAddExternalFunctionForPlugin("SetCharacterFrame", (void*)SetCharacterFrame); + ccAddExternalFunctionForPlugin("SetCharacterIdle", (void*)SetCharacterIdle); + ccAddExternalFunctionForPlugin("SetCharacterIgnoreLight", (void*)SetCharacterIgnoreLight); + ccAddExternalFunctionForPlugin("SetCharacterIgnoreWalkbehinds", (void*)SetCharacterIgnoreWalkbehinds); + ccAddExternalFunctionForPlugin("SetCharacterProperty", (void*)SetCharacterProperty); + ccAddExternalFunctionForPlugin("SetCharacterBlinkView", (void*)SetCharacterBlinkView); + ccAddExternalFunctionForPlugin("SetCharacterSpeechView", (void*)SetCharacterSpeechView); + ccAddExternalFunctionForPlugin("SetCharacterSpeed", (void*)SetCharacterSpeed); + ccAddExternalFunctionForPlugin("SetCharacterSpeedEx", (void*)SetCharacterSpeedEx); + ccAddExternalFunctionForPlugin("SetCharacterTransparency", (void*)SetCharacterTransparency); + ccAddExternalFunctionForPlugin("SetCharacterView", (void*)SetCharacterView); + ccAddExternalFunctionForPlugin("SetCharacterViewEx", (void*)SetCharacterViewEx); + ccAddExternalFunctionForPlugin("SetCharacterViewOffset", (void*)SetCharacterViewOffset); + ccAddExternalFunctionForPlugin("SetCursorMode", (void*)set_cursor_mode); + ccAddExternalFunctionForPlugin("SetDefaultCursor", (void*)set_default_cursor); + ccAddExternalFunctionForPlugin("SetDialogOption", (void*)SetDialogOption); + ccAddExternalFunctionForPlugin("SetDigitalMasterVolume", (void*)SetDigitalMasterVolume); + ccAddExternalFunctionForPlugin("SetFadeColor", (void*)SetFadeColor); + ccAddExternalFunctionForPlugin("SetFrameSound", (void*)SetFrameSound); + ccAddExternalFunctionForPlugin("SetGameOption", (void*)SetGameOption); + ccAddExternalFunctionForPlugin("SetGameSpeed", (void*)SetGameSpeed); + ccAddExternalFunctionForPlugin("SetGlobalInt", (void*)SetGlobalInt); + ccAddExternalFunctionForPlugin("SetGlobalString", (void*)SetGlobalString); + ccAddExternalFunctionForPlugin("SetGraphicalVariable", (void*)SetGraphicalVariable); + ccAddExternalFunctionForPlugin("SetGUIBackgroundPic", (void*)SetGUIBackgroundPic); + ccAddExternalFunctionForPlugin("SetGUIClickable", (void*)SetGUIClickable); + ccAddExternalFunctionForPlugin("SetGUIObjectEnabled", (void*)SetGUIObjectEnabled); + ccAddExternalFunctionForPlugin("SetGUIObjectPosition", (void*)SetGUIObjectPosition); + ccAddExternalFunctionForPlugin("SetGUIObjectSize", (void*)SetGUIObjectSize); + ccAddExternalFunctionForPlugin("SetGUIPosition", (void*)SetGUIPosition); + ccAddExternalFunctionForPlugin("SetGUISize", (void*)SetGUISize); + ccAddExternalFunctionForPlugin("SetGUITransparency", (void*)SetGUITransparency); + ccAddExternalFunctionForPlugin("SetGUIZOrder", (void*)SetGUIZOrder); + ccAddExternalFunctionForPlugin("SetInvItemName", (void*)SetInvItemName); + ccAddExternalFunctionForPlugin("SetInvItemPic", (void*)set_inv_item_pic); + ccAddExternalFunctionForPlugin("SetInvDimensions", (void*)SetInvDimensions); + ccAddExternalFunctionForPlugin("SetLabelColor", (void*)SetLabelColor); + ccAddExternalFunctionForPlugin("SetLabelFont", (void*)SetLabelFont); + ccAddExternalFunctionForPlugin("SetLabelText", (void*)SetLabelText); + ccAddExternalFunctionForPlugin("SetMouseBounds", (void*)SetMouseBounds); + ccAddExternalFunctionForPlugin("SetMouseCursor", (void*)set_mouse_cursor); + ccAddExternalFunctionForPlugin("SetMousePosition", (void*)SetMousePosition); + ccAddExternalFunctionForPlugin("SetMultitaskingMode", (void*)SetMultitasking); + ccAddExternalFunctionForPlugin("SetMusicMasterVolume", (void*)SetMusicMasterVolume); + ccAddExternalFunctionForPlugin("SetMusicRepeat", (void*)SetMusicRepeat); + ccAddExternalFunctionForPlugin("SetMusicVolume", (void*)SetMusicVolume); + ccAddExternalFunctionForPlugin("SetNextCursorMode", (void*)SetNextCursor); + ccAddExternalFunctionForPlugin("SetNextScreenTransition", (void*)SetNextScreenTransition); + ccAddExternalFunctionForPlugin("SetNormalFont", (void*)SetNormalFont); + ccAddExternalFunctionForPlugin("SetObjectBaseline", (void*)SetObjectBaseline); + ccAddExternalFunctionForPlugin("SetObjectClickable", (void*)SetObjectClickable); + ccAddExternalFunctionForPlugin("SetObjectFrame", (void*)SetObjectFrame); + ccAddExternalFunctionForPlugin("SetObjectGraphic", (void*)SetObjectGraphic); + ccAddExternalFunctionForPlugin("SetObjectIgnoreWalkbehinds", (void*)SetObjectIgnoreWalkbehinds); + ccAddExternalFunctionForPlugin("SetObjectPosition", (void*)SetObjectPosition); + ccAddExternalFunctionForPlugin("SetObjectTint", (void*)SetObjectTint); + ccAddExternalFunctionForPlugin("SetObjectTransparency", (void*)SetObjectTransparency); + ccAddExternalFunctionForPlugin("SetObjectView", (void*)SetObjectView); + // ccAddExternalFunctionForPlugin("SetPalette", (void*)scSetPal); + ccAddExternalFunctionForPlugin("SetPalRGB", (void*)SetPalRGB); + ccAddExternalFunctionForPlugin("SetPlayerCharacter", (void*)SetPlayerCharacter); + ccAddExternalFunctionForPlugin("SetRegionTint", (void*)SetRegionTint); + ccAddExternalFunctionForPlugin("SetRestartPoint", (void*)SetRestartPoint); + ccAddExternalFunctionForPlugin("SetScreenTransition", (void*)SetScreenTransition); + ccAddExternalFunctionForPlugin("SetSkipSpeech", (void*)SetSkipSpeech); + ccAddExternalFunctionForPlugin("SetSliderValue", (void*)SetSliderValue); + ccAddExternalFunctionForPlugin("SetSoundVolume", (void*)SetSoundVolume); + ccAddExternalFunctionForPlugin("SetSpeechFont", (void*)SetSpeechFont); + ccAddExternalFunctionForPlugin("SetSpeechStyle", (void*)SetSpeechStyle); + ccAddExternalFunctionForPlugin("SetSpeechVolume", (void*)SetSpeechVolume); + ccAddExternalFunctionForPlugin("SetTalkingColor", (void*)SetTalkingColor); + ccAddExternalFunctionForPlugin("SetTextBoxFont", (void*)SetTextBoxFont); + ccAddExternalFunctionForPlugin("SetTextBoxText", (void*)SetTextBoxText); + ccAddExternalFunctionForPlugin("SetTextOverlay", (void*)ScPl_SetTextOverlay); + ccAddExternalFunctionForPlugin("SetTextWindowGUI", (void*)SetTextWindowGUI); + ccAddExternalFunctionForPlugin("SetTimer", (void*)script_SetTimer); + ccAddExternalFunctionForPlugin("SetViewport", (void*)SetViewport); + ccAddExternalFunctionForPlugin("SetVoiceMode", (void*)SetVoiceMode); + ccAddExternalFunctionForPlugin("SetWalkBehindBase", (void*)SetWalkBehindBase); + ccAddExternalFunctionForPlugin("ShakeScreen", (void*)ShakeScreen); + ccAddExternalFunctionForPlugin("ShakeScreenBackground", (void*)ShakeScreenBackground); + ccAddExternalFunctionForPlugin("ShowMouseCursor", (void*)ShowMouseCursor); + ccAddExternalFunctionForPlugin("SkipUntilCharacterStops", (void*)SkipUntilCharacterStops); + ccAddExternalFunctionForPlugin("StartCutscene", (void*)StartCutscene); + ccAddExternalFunctionForPlugin("StartRecording", (void*)scStartRecording); + ccAddExternalFunctionForPlugin("StopAmbientSound", (void*)StopAmbientSound); + ccAddExternalFunctionForPlugin("StopChannel", (void*)stop_and_destroy_channel); + ccAddExternalFunctionForPlugin("StopDialog", (void*)StopDialog); + ccAddExternalFunctionForPlugin("StopMoving", (void*)StopMoving); + ccAddExternalFunctionForPlugin("StopMusic", (void*)scr_StopMusic); + ccAddExternalFunctionForPlugin("StopObjectMoving", (void*)StopObjectMoving); + ccAddExternalFunctionForPlugin("StrCat", (void*)_sc_strcat); + ccAddExternalFunctionForPlugin("StrCaseComp", (void*)ags_stricmp); + ccAddExternalFunctionForPlugin("StrComp", (void*)strcmp); + ccAddExternalFunctionForPlugin("StrContains", (void*)StrContains); + ccAddExternalFunctionForPlugin("StrCopy", (void*)_sc_strcpy); + ccAddExternalFunctionForPlugin("StrFormat", (void*)ScPl_sc_sprintf); + ccAddExternalFunctionForPlugin("StrGetCharAt", (void*)StrGetCharAt); + ccAddExternalFunctionForPlugin("StringToInt", (void*)StringToInt); + ccAddExternalFunctionForPlugin("StrLen", (void*)strlen); + ccAddExternalFunctionForPlugin("StrSetCharAt", (void*)StrSetCharAt); + ccAddExternalFunctionForPlugin("StrToLowerCase", (void*)_sc_strlower); + ccAddExternalFunctionForPlugin("StrToUpperCase", (void*)_sc_strupper); + ccAddExternalFunctionForPlugin("TintScreen", (void*)TintScreen); + ccAddExternalFunctionForPlugin("UnPauseGame", (void*)UnPauseGame); + ccAddExternalFunctionForPlugin("UpdateInventory", (void*)update_invorder); + ccAddExternalFunctionForPlugin("UpdatePalette", (void*)UpdatePalette); + ccAddExternalFunctionForPlugin("Wait", (void*)scrWait); + ccAddExternalFunctionForPlugin("WaitKey", (void*)WaitKey); + ccAddExternalFunctionForPlugin("WaitMouseKey", (void*)WaitMouseKey); +} diff --git a/engines/ags/engine/ac/global_audio.cpp b/engines/ags/engine/ac/global_audio.cpp new file mode 100644 index 00000000000..cf232e91f55 --- /dev/null +++ b/engines/ags/engine/ac/global_audio.cpp @@ -0,0 +1,680 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/game.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_audio.h" +#include "ac/lipsync.h" +#include "ac/path_helper.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "game/roomstruct.h" +#include "main/engine.h" +#include "media/audio/audio_system.h" +#include "ac/timer.h" +#include "util/string_compat.h" + +using namespace AGS::Common; + +extern GameSetup usetup; +extern GameState play; +extern GameSetupStruct game; +extern RoomStruct thisroom; +extern SpeechLipSyncLine *splipsync; +extern int numLipLines, curLipLine, curLipLinePhoneme; + +void StopAmbientSound (int channel) { + if ((channel < 0) || (channel >= MAX_SOUND_CHANNELS)) + quit("!StopAmbientSound: invalid channel"); + + if (ambient[channel].channel == 0) + return; + + stop_and_destroy_channel(channel); + ambient[channel].channel = 0; +} + +void PlayAmbientSound (int channel, int sndnum, int vol, int x, int y) { + // the channel parameter is to allow multiple ambient sounds in future + if ((channel < 1) || (channel == SCHAN_SPEECH) || (channel >= MAX_SOUND_CHANNELS)) + quit("!PlayAmbientSound: invalid channel number"); + if ((vol < 1) || (vol > 255)) + quit("!PlayAmbientSound: volume must be 1 to 255"); + + ScriptAudioClip *aclip = GetAudioClipForOldStyleNumber(game, false, sndnum); + if (aclip && !is_audiotype_allowed_to_play((AudioFileType)aclip->fileType)) + return; + + // only play the sound if it's not already playing + if ((ambient[channel].channel < 1) || (!channel_is_playing(ambient[channel].channel)) || + (ambient[channel].num != sndnum)) { + + StopAmbientSound(channel); + // in case a normal non-ambient sound was playing, stop it too + stop_and_destroy_channel(channel); + + SOUNDCLIP *asound = aclip ? load_sound_and_play(aclip, true) : nullptr; + if (asound == nullptr) { + debug_script_warn ("Cannot load ambient sound %d", sndnum); + debug_script_log("FAILED to load ambient sound %d", sndnum); + return; + } + + debug_script_log("Playing ambient sound %d on channel %d", sndnum, channel); + ambient[channel].channel = channel; + asound->priority = 15; // ambient sound higher priority than normal sfx + set_clip_to_channel(channel, asound); + } + // calculate the maximum distance away the player can be, using X + // only (since X centred is still more-or-less total Y) + ambient[channel].maxdist = ((x > thisroom.Width / 2) ? x : (thisroom.Width - x)) - AMBIENCE_FULL_DIST; + ambient[channel].num = sndnum; + ambient[channel].x = x; + ambient[channel].y = y; + ambient[channel].vol = vol; + update_ambient_sound_vol(); +} + +int IsChannelPlaying(int chan) { + if (play.fast_forward) + return 0; + + if ((chan < 0) || (chan >= MAX_SOUND_CHANNELS)) + quit("!IsChannelPlaying: invalid sound channel"); + + if (channel_is_playing(chan)) + return 1; + + return 0; +} + +int IsSoundPlaying() { + if (play.fast_forward) + return 0; + + // find if there's a sound playing + AudioChannelsLock lock; + for (int i = SCHAN_NORMAL; i < MAX_SOUND_CHANNELS; i++) { + if (lock.GetChannelIfPlaying(i)) + return 1; + } + + return 0; +} + +// returns -1 on failure, channel number on success +int PlaySoundEx(int val1, int channel) { + + if (debug_flags & DBG_NOSFX) + return -1; + + ScriptAudioClip *aclip = GetAudioClipForOldStyleNumber(game, false, val1); + if (aclip && !is_audiotype_allowed_to_play((AudioFileType)aclip->fileType)) + return -1; // if sound is off, ignore it + + if ((channel < SCHAN_NORMAL) || (channel >= MAX_SOUND_CHANNELS)) + quit("!PlaySoundEx: invalid channel specified, must be 3-7"); + + // if an ambient sound is playing on this channel, abort it + StopAmbientSound(channel); + + if (val1 < 0) { + stop_and_destroy_channel (channel); + return -1; + } + // if skipping a cutscene, don't try and play the sound + if (play.fast_forward) + return -1; + + // free the old sound + stop_and_destroy_channel (channel); + debug_script_log("Playing sound %d on channel %d", val1, channel); + + SOUNDCLIP *soundfx = aclip ? load_sound_and_play(aclip, false) : nullptr; + if (soundfx == nullptr) { + debug_script_warn("Sound sample load failure: cannot load sound %d", val1); + debug_script_log("FAILED to load sound %d", val1); + return -1; + } + + soundfx->priority = 10; + soundfx->set_volume (play.sound_volume); + set_clip_to_channel(channel,soundfx); + return channel; +} + +void StopAllSounds(int evenAmbient) { + // backwards-compatible hack -- stop Type 3 (default Sound Type) + Game_StopAudio(3); + + if (evenAmbient) + Game_StopAudio(1); +} + +void PlayMusicResetQueue(int newmus) { + play.music_queue_size = 0; + newmusic(newmus); +} + +void SeekMIDIPosition (int position) { + if (play.silent_midi == 0 && current_music_type != MUS_MIDI) + return; + + AudioChannelsLock lock; + auto *ch = lock.GetChannel(SCHAN_MUSIC); + ch->seek(position); + debug_script_log("Seek MIDI position to %d", position); +} + +int GetMIDIPosition () { + if (play.fast_forward) + return 99999; + if (play.silent_midi == 0 && current_music_type != MUS_MIDI) + return -1; // returns -1 on failure according to old manuals + + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(SCHAN_MUSIC); + if (ch) { + return ch->get_pos(); + } + + return -1; +} + +int IsMusicPlaying() { + // in case they have a "while (IsMusicPlaying())" loop + if ((play.fast_forward) && (play.skip_until_char_stops < 0)) + return 0; + + // This only returns positive if there was a music started by old audio API + if (current_music_type == 0) + return 0; + + AudioChannelsLock lock; + auto *ch = lock.GetChannel(SCHAN_MUSIC); + if (ch == nullptr) + { // This was probably a hacky fix in case it was not reset by game update; TODO: find out if needed + current_music_type = 0; + return 0; + } + + bool result = (ch->is_playing()) || (crossFading > 0 && (lock.GetChannelIfPlaying(crossFading) != nullptr)); + return result ? 1 : 0; +} + +int PlayMusicQueued(int musnum) { + + // Just get the queue size + if (musnum < 0) + return play.music_queue_size; + + if ((IsMusicPlaying() == 0) && (play.music_queue_size == 0)) { + newmusic(musnum); + return 0; + } + + if (play.music_queue_size >= MAX_QUEUED_MUSIC) { + debug_script_log("Too many queued music, cannot add %d", musnum); + return 0; + } + + if ((play.music_queue_size > 0) && + (play.music_queue[play.music_queue_size - 1] >= QUEUED_MUSIC_REPEAT)) { + debug_script_warn("PlayMusicQueued: cannot queue music after a repeating tune has been queued"); + return 0; + } + + if (play.music_repeat) { + debug_script_log("Queuing music %d to loop", musnum); + musnum += QUEUED_MUSIC_REPEAT; + } + else { + debug_script_log("Queuing music %d", musnum); + } + + play.music_queue[play.music_queue_size] = musnum; + play.music_queue_size++; + + if (play.music_queue_size == 1) { + + clear_music_cache(); + + cachedQueuedMusic = load_music_from_disk(musnum, (play.music_repeat > 0)); + } + + return play.music_queue_size; +} + +void scr_StopMusic() { + play.music_queue_size = 0; + stopmusic(); +} + +void SeekMODPattern(int patnum) { + if (current_music_type != MUS_MOD) + return; + + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(SCHAN_MUSIC); + if (ch) { + ch->seek (patnum); + debug_script_log("Seek MOD/XM to pattern %d", patnum); + } +} + +void SeekMP3PosMillis (int posn) { + if (current_music_type != MUS_MP3 && current_music_type != MUS_OGG) + return; + + AudioChannelsLock lock; + auto *mus_ch = lock.GetChannel(SCHAN_MUSIC); + auto *cf_ch = (crossFading > 0) ? lock.GetChannel(crossFading) : nullptr; + if (cf_ch) + cf_ch->seek(posn); + else if (mus_ch) + mus_ch->seek(posn); +} + +int GetMP3PosMillis () { + // in case they have "while (GetMP3PosMillis() < 5000) " + if (play.fast_forward) + return 999999; + if (current_music_type != MUS_MP3 && current_music_type != MUS_OGG) + return 0; // returns 0 on failure according to old manuals + + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(SCHAN_MUSIC); + if (ch) { + int result = ch->get_pos_ms(); + if (result >= 0) + return result; + + return ch->get_pos (); + } + + return 0; +} + +void SetMusicVolume(int newvol) { + if ((newvol < kRoomVolumeMin) || (newvol > kRoomVolumeMax)) + quitprintf("!SetMusicVolume: invalid volume number. Must be from %d to %d.", kRoomVolumeMin, kRoomVolumeMax); + thisroom.Options.MusicVolume=(RoomVolumeMod)newvol; + update_music_volume(); +} + +void SetMusicMasterVolume(int newvol) { + const int min_volume = loaded_game_file_version < kGameVersion_330 ? 0 : + -LegacyMusicMasterVolumeAdjustment - (kRoomVolumeMax * LegacyRoomVolumeFactor); + if ((newvol < min_volume) | (newvol>100)) + quitprintf("!SetMusicMasterVolume: invalid volume - must be from %d to %d", min_volume, 100); + play.music_master_volume=newvol+LegacyMusicMasterVolumeAdjustment; + update_music_volume(); +} + +void SetSoundVolume(int newvol) { + if ((newvol<0) | (newvol>255)) + quit("!SetSoundVolume: invalid volume - must be from 0-255"); + play.sound_volume = newvol; + Game_SetAudioTypeVolume(AUDIOTYPE_LEGACY_AMBIENT_SOUND, (newvol * 100) / 255, VOL_BOTH); + Game_SetAudioTypeVolume(AUDIOTYPE_LEGACY_SOUND, (newvol * 100) / 255, VOL_BOTH); + update_ambient_sound_vol (); +} + +void SetChannelVolume(int chan, int newvol) { + if ((newvol<0) || (newvol>255)) + quit("!SetChannelVolume: invalid volume - must be from 0-255"); + if ((chan < 0) || (chan >= MAX_SOUND_CHANNELS)) + quit("!SetChannelVolume: invalid channel id"); + + AudioChannelsLock lock; + auto* ch = lock.GetChannelIfPlaying(chan); + + if (ch) { + if (chan == ambient[chan].channel) { + ambient[chan].vol = newvol; + update_ambient_sound_vol(); + } + else + ch->set_volume (newvol); + } +} + +void SetDigitalMasterVolume (int newvol) { + if ((newvol<0) | (newvol>100)) + quit("!SetDigitalMasterVolume: invalid volume - must be from 0-100"); + play.digital_master_volume = newvol; + set_volume ((newvol * 255) / 100, -1); +} + +int GetCurrentMusic() { + return play.cur_music_number; +} + +void SetMusicRepeat(int loopflag) { + play.music_repeat=loopflag; +} + +void PlayMP3File (const char *filename) { + if (strlen(filename) >= PLAYMP3FILE_MAX_FILENAME_LEN) + quit("!PlayMP3File: filename too long"); + + debug_script_log("PlayMP3File %s", filename); + + AssetPath asset_name("", filename); + + int useChan = prepare_for_new_music (); + bool doLoop = (play.music_repeat > 0); + + SOUNDCLIP *clip = nullptr; + + if (!clip) { + clip = my_load_static_ogg(asset_name, 150, doLoop); + if (clip) { + if (clip->play()) { + set_clip_to_channel(useChan, clip); + current_music_type = MUS_OGG; + play.cur_music_number = 1000; + // save the filename (if it's not what we were supplied with) + if (filename != &play.playmp3file_name[0]) + strcpy (play.playmp3file_name, filename); + } else { + clip->destroy(); + delete clip; + clip = nullptr; + } + } + } + + if (!clip) + { + clip = my_load_static_mp3(asset_name, 150, doLoop); + if (clip) { + if (clip->play()) { + set_clip_to_channel(useChan, clip); + current_music_type = MUS_MP3; + play.cur_music_number = 1000; + // save the filename (if it's not what we were supplied with) + if (filename != &play.playmp3file_name[0]) + strcpy(play.playmp3file_name, filename); + } else { + clip->destroy(); + delete clip; + clip = nullptr; + } + } + } + + if (!clip) { + set_clip_to_channel(useChan, nullptr); + debug_script_warn ("PlayMP3File: file '%s' not found or cannot play", filename); + } + + post_new_music_check(useChan); + + update_music_volume(); +} + +void PlaySilentMIDI (int mnum) { + if (current_music_type == MUS_MIDI) + quit("!PlaySilentMIDI: proper midi music is in progress"); + + set_volume (-1, 0); + play.silent_midi = mnum; + play.silent_midi_channel = SCHAN_SPEECH; + stop_and_destroy_channel(play.silent_midi_channel); + // No idea why it uses speech voice channel, but since it does (and until this is changed) + // we have to correctly reset speech voice in case there was a nonblocking speech + if (play.IsNonBlockingVoiceSpeech()) + stop_voice_nonblocking(); + + SOUNDCLIP *clip = load_sound_clip_from_old_style_number(true, mnum, false); + if (clip == nullptr) + { + quitprintf("!PlaySilentMIDI: failed to load aMusic%d", mnum); + } + AudioChannelsLock lock; + lock.SetChannel(play.silent_midi_channel, clip); + if (!clip->play()) { + clip->destroy(); + delete clip; + clip = nullptr; + quitprintf("!PlaySilentMIDI: failed to play aMusic%d", mnum); + } + clip->set_volume_percent(0); +} + +void SetSpeechVolume(int newvol) { + if ((newvol<0) | (newvol>255)) + quit("!SetSpeechVolume: invalid volume - must be from 0-255"); + + AudioChannelsLock lock; + auto* ch = lock.GetChannel(SCHAN_SPEECH); + if (ch) + ch->set_volume (newvol); + play.speech_volume = newvol; +} + +// 0 = text only +// 1 = voice & text +// 2 = voice only +void SetVoiceMode (int newmod) { + if ((newmod < 0) | (newmod > 2)) + quit("!SetVoiceMode: invalid mode number (must be 0,1,2)"); + // If speech is turned off, store the mode anyway in case the + // user adds the VOX file later + if (play.want_speech < 0) + play.want_speech = (-newmod) - 1; + else + play.want_speech = newmod; +} + +int GetVoiceMode() +{ + return play.want_speech >= 0 ? play.want_speech : -(play.want_speech + 1); +} + +int IsVoxAvailable() { + if (play.want_speech < 0) + return 0; + return 1; +} + +int IsMusicVoxAvailable () { + return play.separate_music_lib; +} + +extern ScriptAudioChannel scrAudioChannel[MAX_SOUND_CHANNELS + 1]; + +ScriptAudioChannel *PlayVoiceClip(CharacterInfo *ch, int sndid, bool as_speech) +{ + if (!play_voice_nonblocking(ch->index_id, sndid, as_speech)) + return NULL; + return &scrAudioChannel[SCHAN_SPEECH]; +} + +// Construct an asset name for the voice-over clip for the given character and cue id +String get_cue_filename(int charid, int sndid) +{ + String script_name; + if (charid >= 0) + { + // append the first 4 characters of the script name to the filename + if (game.chars[charid].scrname[0] == 'c') + script_name.SetString(&game.chars[charid].scrname[1], 4); + else + script_name.SetString(game.chars[charid].scrname, 4); + } + else + { + script_name = "NARR"; + } + return String::FromFormat("%s%d", script_name.GetCStr(), sndid); +} + +// Play voice-over clip on the common channel; +// voice_name should be bare clip name without extension +static bool play_voice_clip_on_channel(const String &voice_name) +{ + stop_and_destroy_channel(SCHAN_SPEECH); + + String asset_name = voice_name; + asset_name.Append(".wav"); + SOUNDCLIP *speechmp3 = my_load_wave(get_voice_over_assetpath(asset_name), play.speech_volume, 0); + + if (speechmp3 == nullptr) { + asset_name.ReplaceMid(asset_name.GetLength() - 3, 3, "ogg"); + speechmp3 = my_load_ogg(get_voice_over_assetpath(asset_name), play.speech_volume); + } + + if (speechmp3 == nullptr) { + asset_name.ReplaceMid(asset_name.GetLength() - 3, 3, "mp3"); + speechmp3 = my_load_mp3(get_voice_over_assetpath(asset_name), play.speech_volume); + } + + if (speechmp3 != nullptr) { + if (!speechmp3->play()) { + // not assigned to a channel, so clean up manually. + speechmp3->destroy(); + delete speechmp3; + speechmp3 = nullptr; + } + } + + if (speechmp3 == nullptr) { + debug_script_warn("Speech load failure: '%s'", voice_name.GetCStr()); + return false; + } + + set_clip_to_channel(SCHAN_SPEECH,speechmp3); + return true; +} + +// Play voice-over clip and adjust audio volumes; +// voice_name should be bare clip name without extension +static bool play_voice_clip_impl(const String &voice_name, bool as_speech, bool is_blocking) +{ + if (!play_voice_clip_on_channel(voice_name)) + return false; + if (!as_speech) + return true; + + play.speech_has_voice = true; + play.speech_voice_blocking = is_blocking; + + cancel_scheduled_music_update(); + play.music_vol_was = play.music_master_volume; + // Negative value means set exactly; positive means drop that amount + if (play.speech_music_drop < 0) + play.music_master_volume = -play.speech_music_drop; + else + play.music_master_volume -= play.speech_music_drop; + apply_volume_drop_modifier(true); + update_music_volume(); + update_ambient_sound_vol(); + return true; +} + +// Stop voice-over clip and schedule audio volume reset +static void stop_voice_clip_impl() +{ + play.music_master_volume = play.music_vol_was; + // update the music in a bit (fixes two speeches follow each other + // and music going up-then-down) + schedule_music_update_at(AGS_Clock::now() + std::chrono::milliseconds(500)); + stop_and_destroy_channel(SCHAN_SPEECH); +} + +bool play_voice_speech(int charid, int sndid) +{ + // don't play speech if we're skipping a cutscene + if (!play.ShouldPlayVoiceSpeech()) + return false; + + String voice_file = get_cue_filename(charid, sndid); + if (!play_voice_clip_impl(voice_file, true, true)) + return false; + + int ii; // Compare the base file name to the .pam file name + curLipLine = -1; // See if we have voice lip sync for this line + curLipLinePhoneme = -1; + for (ii = 0; ii < numLipLines; ii++) { + if (ags_stricmp(splipsync[ii].filename, voice_file) == 0) { + curLipLine = ii; + break; + } + } + // if the lip-sync is being used for voice sync, disable + // the text-related lipsync + if (numLipLines > 0) + game.options[OPT_LIPSYNCTEXT] = 0; + + // change Sierra w/bgrnd to Sierra without background when voice + // is available (for Tierra) + if ((game.options[OPT_SPEECHTYPE] == 2) && (play.no_textbg_when_voice > 0)) { + game.options[OPT_SPEECHTYPE] = 1; + play.no_textbg_when_voice = 2; + } + return true; +} + +bool play_voice_nonblocking(int charid, int sndid, bool as_speech) +{ + // don't play voice if we're skipping a cutscene + if (!play.ShouldPlayVoiceSpeech()) + return false; + // don't play voice if there's a blocking speech with voice-over already + if (play.IsBlockingVoiceSpeech()) + return false; + + String voice_file = get_cue_filename(charid, sndid); + return play_voice_clip_impl(voice_file, as_speech, false); +} + +void stop_voice_speech() +{ + if (!play.speech_has_voice) + return; + + stop_voice_clip_impl(); + + // Reset lipsync + curLipLine = -1; + // Set back to Sierra w/bgrnd + if (play.no_textbg_when_voice == 2) + { + play.no_textbg_when_voice = 1; + game.options[OPT_SPEECHTYPE] = 2; + } + play.speech_has_voice = false; + play.speech_voice_blocking = false; +} + +void stop_voice_nonblocking() +{ + if (!play.speech_has_voice) + return; + stop_voice_clip_impl(); + // Only reset speech flags if we are truly playing a non-blocking voice; + // otherwise we might be inside blocking speech function and should let + // it keep these flags to be able to finalize properly. + // This is an imperfection of current speech implementation. + if (!play.speech_voice_blocking) + { + play.speech_has_voice = false; + play.speech_voice_blocking = false; + } +} diff --git a/engines/ags/engine/ac/global_audio.h b/engines/ags/engine/ac/global_audio.h new file mode 100644 index 00000000000..9c9b5576780 --- /dev/null +++ b/engines/ags/engine/ac/global_audio.h @@ -0,0 +1,71 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALAUDIO_H +#define __AGS_EE_AC__GLOBALAUDIO_H + +void StopAmbientSound (int channel); +void PlayAmbientSound (int channel, int sndnum, int vol, int x, int y); +int IsChannelPlaying(int chan); +int IsSoundPlaying(); +// returns -1 on failure, channel number on success +int PlaySoundEx(int val1, int channel); +void StopAllSounds(int evenAmbient); + +void PlayMusicResetQueue(int newmus); +void SeekMIDIPosition (int position); +int GetMIDIPosition (); +int IsMusicPlaying(); +int PlayMusicQueued(int musnum); +void scr_StopMusic(); +void SeekMODPattern(int patnum); +void SeekMP3PosMillis (int posn); +int GetMP3PosMillis (); +void SetMusicVolume(int newvol); +void SetMusicMasterVolume(int newvol); +void SetSoundVolume(int newvol); +void SetChannelVolume(int chan, int newvol); +void SetDigitalMasterVolume (int newvol); +int GetCurrentMusic(); +void SetMusicRepeat(int loopflag); +void PlayMP3File (const char *filename); +void PlaySilentMIDI (int mnum); + +void SetSpeechVolume(int newvol); +void SetVoiceMode (int newmod); +int GetVoiceMode (); +int IsVoxAvailable(); +int IsMusicVoxAvailable (); + +struct CharacterInfo; +struct ScriptAudioChannel; +// Starts voice-over playback and returns audio channel it is played on; +// as_speech flag determines whether engine should apply speech-related logic +// as well, such as temporary volume reduction. +ScriptAudioChannel *PlayVoiceClip(CharacterInfo *ch, int sndid, bool as_speech); + +//============================================================================= +// Play voice-over for the active blocking speech and initialize relevant data +bool play_voice_speech(int charid, int sndid); +// Play voice-over clip in non-blocking manner +bool play_voice_nonblocking(int charid, int sndid, bool as_speech); +// Stop voice-over for the active speech and reset relevant data +void stop_voice_speech(); +// Stop non-blocking voice-over and revert audio volumes if necessary +void stop_voice_nonblocking(); + +#endif // __AGS_EE_AC__GLOBALAUDIO_H diff --git a/engines/ags/engine/ac/global_button.cpp b/engines/ags/engine/ac/global_button.cpp new file mode 100644 index 00000000000..f2d66f585a2 --- /dev/null +++ b/engines/ags/engine/ac/global_button.cpp @@ -0,0 +1,99 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_button.h" +#include "ac/common.h" +#include "ac/button.h" +#include "ac/gamesetupstruct.h" +#include "ac/string.h" +#include "gui/guimain.h" +#include "gui/guibutton.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; + +void SetButtonText(int guin,int objn, const char*newtx) { + VALIDATE_STRING(newtx); + if ((guin<0) | (guin>=game.numgui)) + quit("!SetButtonText: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) + quit("!SetButtonText: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUIButton) + quit("!SetButtonText: specified control is not a button"); + + GUIButton*guil=(GUIButton*)guis[guin].GetControl(objn); + Button_SetText(guil, newtx); +} + + +void AnimateButton(int guin, int objn, int view, int loop, int speed, int repeat) { + if ((guin<0) | (guin>=game.numgui)) quit("!AnimateButton: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) quit("!AnimateButton: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUIButton) + quit("!AnimateButton: specified control is not a button"); + + Button_Animate((GUIButton*)guis[guin].GetControl(objn), view, loop, speed, repeat); +} + + +int GetButtonPic(int guin, int objn, int ptype) { + if ((guin<0) | (guin>=game.numgui)) quit("!GetButtonPic: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) quit("!GetButtonPic: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUIButton) + quit("!GetButtonPic: specified control is not a button"); + if ((ptype < 0) | (ptype > 3)) quit("!GetButtonPic: invalid pic type"); + + GUIButton*guil=(GUIButton*)guis[guin].GetControl(objn); + + if (ptype == 0) { + // currently displayed pic + if (guil->CurrentImage < 0) + return guil->Image; + return guil->CurrentImage; + } + else if (ptype==1) { + // nomal pic + return guil->Image; + } + else if (ptype==2) { + // mouseover pic + return guil->MouseOverImage; + } + else { // pushed pic + return guil->PushedImage; + } + + quit("internal error in getbuttonpic"); +} + +void SetButtonPic(int guin,int objn,int ptype,int slotn) { + if ((guin<0) | (guin>=game.numgui)) quit("!SetButtonPic: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) quit("!SetButtonPic: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUIButton) + quit("!SetButtonPic: specified control is not a button"); + if ((ptype<1) | (ptype>3)) quit("!SetButtonPic: invalid pic type"); + + GUIButton*guil=(GUIButton*)guis[guin].GetControl(objn); + if (ptype==1) { + Button_SetNormalGraphic(guil, slotn); + } + else if (ptype==2) { + // mouseover pic + Button_SetMouseOverGraphic(guil, slotn); + } + else { // pushed pic + Button_SetPushedGraphic(guil, slotn); + } +} diff --git a/engines/ags/engine/ac/global_button.h b/engines/ags/engine/ac/global_button.h new file mode 100644 index 00000000000..cb98cb53e29 --- /dev/null +++ b/engines/ags/engine/ac/global_button.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALBUTTON_H +#define __AGS_EE_AC__GLOBALBUTTON_H + +void SetButtonText(int guin,int objn, const char*newtx); +void AnimateButton(int guin, int objn, int view, int loop, int speed, int repeat); +int GetButtonPic(int guin, int objn, int ptype); +void SetButtonPic(int guin,int objn,int ptype,int slotn); + +#endif // __AGS_EE_AC__GLOBALBUTTON_H diff --git a/engines/ags/engine/ac/global_character.cpp b/engines/ags/engine/ac/global_character.cpp new file mode 100644 index 00000000000..39f018ce68b --- /dev/null +++ b/engines/ags/engine/ac/global_character.cpp @@ -0,0 +1,566 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AGS Character functions +// +//============================================================================= + +#include "ac/global_character.h" +#include "ac/common.h" +#include "ac/view.h" +#include "ac/character.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/event.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_overlay.h" +#include "ac/global_translation.h" +#include "ac/object.h" +#include "ac/overlay.h" +#include "ac/properties.h" +#include "ac/screenoverlay.h" +#include "ac/string.h" +#include "debug/debug_log.h" +#include "game/roomstruct.h" +#include "main/game_run.h" +#include "script/script.h" + +using namespace AGS::Common; + + +extern GameSetupStruct game; +extern ViewStruct*views; +extern RoomObject*objs; +extern RoomStruct thisroom; +extern GameState play; +extern ScriptObject scrObj[MAX_ROOM_OBJECTS]; +extern ScriptInvItem scrInv[MAX_INV]; + +// defined in character unit +extern CharacterExtras *charextra; +extern CharacterInfo*playerchar; +extern int32_t _sc_PlayerCharPtr; +extern CharacterInfo*playerchar; + + +void StopMoving(int chaa) { + + Character_StopMoving(&game.chars[chaa]); +} + +void ReleaseCharacterView(int chat) { + if (!is_valid_character(chat)) + quit("!ReleaseCahracterView: invalid character supplied"); + + Character_UnlockView(&game.chars[chat]); +} + +void MoveToWalkableArea(int charid) { + if (!is_valid_character(charid)) + quit("!MoveToWalkableArea: invalid character specified"); + + Character_PlaceOnWalkableArea(&game.chars[charid]); +} + +void FaceLocation(int cha, int xx, int yy) { + if (!is_valid_character(cha)) + quit("!FaceLocation: Invalid character specified"); + + Character_FaceLocation(&game.chars[cha], xx, yy, BLOCKING); +} + +void FaceCharacter(int cha,int toface) { + if (!is_valid_character(cha)) + quit("!FaceCharacter: Invalid character specified"); + if (!is_valid_character(toface)) + quit("!FaceCharacter: invalid character specified"); + + Character_FaceCharacter(&game.chars[cha], &game.chars[toface], BLOCKING); +} + + +void SetCharacterIdle(int who, int iview, int itime) { + if (!is_valid_character(who)) + quit("!SetCharacterIdle: Invalid character specified"); + + Character_SetIdleView(&game.chars[who], iview, itime); +} + + + +int GetCharacterWidth(int ww) { + CharacterInfo *char1 = &game.chars[ww]; + + if (charextra[ww].width < 1) + { + if ((char1->view < 0) || + (char1->loop >= views[char1->view].numLoops) || + (char1->frame >= views[char1->view].loops[char1->loop].numFrames)) + { + debug_script_warn("GetCharacterWidth: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname, char1->view + 1, char1->loop, char1->frame); + return data_to_game_coord(4); + } + + return game.SpriteInfos[views[char1->view].loops[char1->loop].frames[char1->frame].pic].Width; + } + else + return charextra[ww].width; +} + +int GetCharacterHeight(int charid) { + CharacterInfo *char1 = &game.chars[charid]; + + if (charextra[charid].height < 1) + { + if ((char1->view < 0) || + (char1->loop >= views[char1->view].numLoops) || + (char1->frame >= views[char1->view].loops[char1->loop].numFrames)) + { + debug_script_warn("GetCharacterHeight: Character %s has invalid frame: view %d, loop %d, frame %d", char1->scrname, char1->view + 1, char1->loop, char1->frame); + return data_to_game_coord(2); + } + + return game.SpriteInfos[views[char1->view].loops[char1->loop].frames[char1->frame].pic].Height; + } + else + return charextra[charid].height; +} + + + +void SetCharacterBaseline (int obn, int basel) { + if (!is_valid_character(obn)) quit("!SetCharacterBaseline: invalid object number specified"); + + Character_SetBaseline(&game.chars[obn], basel); +} + +// pass trans=0 for fully solid, trans=100 for fully transparent +void SetCharacterTransparency(int obn,int trans) { + if (!is_valid_character(obn)) + quit("!SetCharTransparent: invalid character number specified"); + + Character_SetTransparency(&game.chars[obn], trans); +} + +void scAnimateCharacter (int chh, int loopn, int sppd, int rept) { + if (!is_valid_character(chh)) + quit("AnimateCharacter: invalid character"); + + animate_character(&game.chars[chh], loopn, sppd, rept); +} + +void AnimateCharacterEx(int chh, int loopn, int sppd, int rept, int direction, int blocking) { + if ((direction < 0) || (direction > 1)) + quit("!AnimateCharacterEx: invalid direction"); + if (!is_valid_character(chh)) + quit("AnimateCharacter: invalid character"); + + if (direction) + direction = BACKWARDS; + else + direction = FORWARDS; + + if (blocking) + blocking = BLOCKING; + else + blocking = IN_BACKGROUND; + + Character_Animate(&game.chars[chh], loopn, sppd, rept, blocking, direction); + +} + + +void SetPlayerCharacter(int newchar) { + if (!is_valid_character(newchar)) + quit("!SetPlayerCharacter: Invalid character specified"); + + Character_SetAsPlayer(&game.chars[newchar]); +} + +void FollowCharacterEx(int who, int tofollow, int distaway, int eagerness) { + if (!is_valid_character(who)) + quit("!FollowCharacter: Invalid character specified"); + CharacterInfo *chtofollow = nullptr; + if (tofollow != -1) + { + if (!is_valid_character(tofollow)) + quit("!FollowCharacterEx: invalid character to follow"); + else + chtofollow = &game.chars[tofollow]; + } + + Character_FollowCharacter(&game.chars[who], chtofollow, distaway, eagerness); +} + +void FollowCharacter(int who, int tofollow) { + FollowCharacterEx(who,tofollow,10,97); +} + +void SetCharacterIgnoreLight (int who, int yesorno) { + if (!is_valid_character(who)) + quit("!SetCharacterIgnoreLight: Invalid character specified"); + + Character_SetIgnoreLighting(&game.chars[who], yesorno); +} + + + + +void MoveCharacter(int cc,int xx,int yy) { + walk_character(cc,xx,yy,0, true); +} +void MoveCharacterDirect(int cc,int xx, int yy) { + walk_character(cc,xx,yy,1, true); +} +void MoveCharacterStraight(int cc,int xx, int yy) { + if (!is_valid_character(cc)) + quit("!MoveCharacterStraight: invalid character specified"); + + Character_WalkStraight(&game.chars[cc], xx, yy, IN_BACKGROUND); +} + +// Append to character path +void MoveCharacterPath (int chac, int tox, int toy) { + if (!is_valid_character(chac)) + quit("!MoveCharacterPath: invalid character specified"); + + Character_AddWaypoint(&game.chars[chac], tox, toy); +} + + +int GetPlayerCharacter() { + return game.playercharacter; +} + +void SetCharacterSpeedEx(int chaa, int xspeed, int yspeed) { + if (!is_valid_character(chaa)) + quit("!SetCharacterSpeedEx: invalid character"); + + Character_SetSpeed(&game.chars[chaa], xspeed, yspeed); + +} + +void SetCharacterSpeed(int chaa,int nspeed) { + SetCharacterSpeedEx(chaa, nspeed, nspeed); +} + +void SetTalkingColor(int chaa,int ncol) { + if (!is_valid_character(chaa)) quit("!SetTalkingColor: invalid character"); + + Character_SetSpeechColor(&game.chars[chaa], ncol); +} + +void SetCharacterSpeechView (int chaa, int vii) { + if (!is_valid_character(chaa)) + quit("!SetCharacterSpeechView: invalid character specified"); + + Character_SetSpeechView(&game.chars[chaa], vii); +} + +void SetCharacterBlinkView (int chaa, int vii, int intrv) { + if (!is_valid_character(chaa)) + quit("!SetCharacterBlinkView: invalid character specified"); + + Character_SetBlinkView(&game.chars[chaa], vii); + Character_SetBlinkInterval(&game.chars[chaa], intrv); +} + +void SetCharacterView(int chaa,int vii) { + if (!is_valid_character(chaa)) + quit("!SetCharacterView: invalid character specified"); + + Character_LockView(&game.chars[chaa], vii); +} + +void SetCharacterFrame(int chaa, int view, int loop, int frame) { + + Character_LockViewFrame(&game.chars[chaa], view, loop, frame); +} + +// similar to SetCharView, but aligns the frame to make it line up +void SetCharacterViewEx (int chaa, int vii, int loop, int align) { + + Character_LockViewAligned(&game.chars[chaa], vii, loop, align); +} + +void SetCharacterViewOffset (int chaa, int vii, int xoffs, int yoffs) { + + Character_LockViewOffset(&game.chars[chaa], vii, xoffs, yoffs); +} + + +void ChangeCharacterView(int chaa,int vii) { + if (!is_valid_character(chaa)) + quit("!ChangeCharacterView: invalid character specified"); + + Character_ChangeView(&game.chars[chaa], vii); +} + +void SetCharacterClickable (int cha, int clik) { + if (!is_valid_character(cha)) + quit("!SetCharacterClickable: Invalid character specified"); + // make the character clicklabe (reset "No interaction" bit) + game.chars[cha].flags&=~CHF_NOINTERACT; + // if they don't want it clickable, set the relevant bit + if (clik == 0) + game.chars[cha].flags|=CHF_NOINTERACT; +} + +void SetCharacterIgnoreWalkbehinds (int cha, int clik) { + if (!is_valid_character(cha)) + quit("!SetCharacterIgnoreWalkbehinds: Invalid character specified"); + + Character_SetIgnoreWalkbehinds(&game.chars[cha], clik); +} + + +void MoveCharacterToObject(int chaa,int obbj) { + // invalid object, do nothing + // this allows MoveCharacterToObject(EGO, GetObjectAt(...)); + if (!is_valid_object(obbj)) + return; + + walk_character(chaa,objs[obbj].x+5,objs[obbj].y+6,0, true); + + GameLoopUntilNotMoving(&game.chars[chaa].walking); +} + +void MoveCharacterToHotspot(int chaa,int hotsp) { + if ((hotsp<0) || (hotsp>=MAX_ROOM_HOTSPOTS)) + quit("!MovecharacterToHotspot: invalid hotspot"); + if (thisroom.Hotspots[hotsp].WalkTo.X<1) return; + walk_character(chaa,thisroom.Hotspots[hotsp].WalkTo.X,thisroom.Hotspots[hotsp].WalkTo.Y,0, true); + + GameLoopUntilNotMoving(&game.chars[chaa].walking); +} + +void MoveCharacterBlocking(int chaa,int xx,int yy,int direct) { + if (!is_valid_character (chaa)) + quit("!MoveCharacterBlocking: invalid character"); + + // check if they try to move the player when Hide Player Char is + // ticked -- otherwise this will hang the game + if (game.chars[chaa].on != 1) + { + debug_script_warn("MoveCharacterBlocking: character is turned off (is Hide Player Character selected?) and cannot be moved"); + return; + } + + if (direct) + MoveCharacterDirect(chaa,xx,yy); + else + MoveCharacter(chaa,xx,yy); + + GameLoopUntilNotMoving(&game.chars[chaa].walking); +} + +int GetCharacterSpeechAnimationDelay(CharacterInfo *cha) +{ + if ((loaded_game_file_version < kGameVersion_312) && (game.options[OPT_SPEECHTYPE] != 0)) + { + // legacy versions of AGS assigned a fixed delay to Sierra-style speech only + return 5; + } + if (game.options[OPT_GLOBALTALKANIMSPD] != 0) + return play.talkanim_speed; + else + return cha->speech_anim_speed; +} + +void RunCharacterInteraction (int cc, int mood) { + if (!is_valid_character(cc)) + quit("!RunCharacterInteraction: invalid character"); + + int passon=-1,cdata=-1; + if (mood==MODE_LOOK) passon=0; + else if (mood==MODE_HAND) passon=1; + else if (mood==MODE_TALK) passon=2; + else if (mood==MODE_USE) { passon=3; + cdata=playerchar->activeinv; + play.usedinv=cdata; + } + else if (mood==MODE_PICKUP) passon = 5; + else if (mood==MODE_CUSTOM1) passon = 6; + else if (mood==MODE_CUSTOM2) passon = 7; + + evblockbasename="character%d"; evblocknum=cc; + if (loaded_game_file_version > kGameVersion_272) + { + if (passon>=0) + run_interaction_script(game.charScripts[cc].get(), passon, 4, (passon == 3)); + run_interaction_script(game.charScripts[cc].get(), 4); // any click on char + } + else + { + if (passon>=0) + run_interaction_event(game.intrChar[cc].get(),passon, 4, (passon == 3)); + run_interaction_event(game.intrChar[cc].get(),4); // any click on char + } +} + +int AreCharObjColliding(int charid,int objid) { + if (!is_valid_character(charid)) + quit("!AreCharObjColliding: invalid character"); + if (!is_valid_object(objid)) + quit("!AreCharObjColliding: invalid object number"); + + return Character_IsCollidingWithObject(&game.chars[charid], &scrObj[objid]); +} + +int AreCharactersColliding(int cchar1,int cchar2) { + if (!is_valid_character(cchar1)) + quit("!AreCharactersColliding: invalid char1"); + if (!is_valid_character(cchar2)) + quit("!AreCharactersColliding: invalid char2"); + + return Character_IsCollidingWithChar(&game.chars[cchar1], &game.chars[cchar2]); +} + +int GetCharacterProperty (int cha, const char *property) { + if (!is_valid_character(cha)) + quit("!GetCharacterProperty: invalid character"); + return get_int_property (game.charProps[cha], play.charProps[cha], property); +} + +void SetCharacterProperty (int who, int flag, int yesorno) { + if (!is_valid_character(who)) + quit("!SetCharacterProperty: Invalid character specified"); + + Character_SetOption(&game.chars[who], flag, yesorno); +} + +void GetCharacterPropertyText (int item, const char *property, char *bufer) { + get_text_property (game.charProps[item], play.charProps[item], property, bufer); +} + +int GetCharIDAtScreen(int xx, int yy) { + VpPoint vpt = play.ScreenToRoomDivDown(xx, yy); + if (vpt.second < 0) + return -1; + return is_pos_on_character(vpt.first.X, vpt.first.Y); +} + +void SetActiveInventory(int iit) { + + ScriptInvItem *tosend = nullptr; + if ((iit > 0) && (iit < game.numinvitems)) + tosend = &scrInv[iit]; + else if (iit != -1) + quitprintf("!SetActiveInventory: invalid inventory number %d", iit); + + Character_SetActiveInventory(playerchar, tosend); +} + +void update_invorder() { + for (int cc = 0; cc < game.numcharacters; cc++) { + charextra[cc].invorder_count = 0; + int ff, howmany; + // Iterate through all inv items, adding them once (or multiple + // times if requested) to the list. + for (ff=0;ff < game.numinvitems;ff++) { + howmany = game.chars[cc].inv[ff]; + if ((game.options[OPT_DUPLICATEINV] == 0) && (howmany > 1)) + howmany = 1; + + for (int ts = 0; ts < howmany; ts++) { + if (charextra[cc].invorder_count >= MAX_INVORDER) + quit("!Too many inventory items to display: 500 max"); + + charextra[cc].invorder[charextra[cc].invorder_count] = ff; + charextra[cc].invorder_count++; + } + } + } + // backwards compatibility + play.obsolete_inv_numorder = charextra[game.playercharacter].invorder_count; + + guis_need_update = 1; +} + +void add_inventory(int inum) { + if ((inum < 0) || (inum >= MAX_INV)) + quit("!AddInventory: invalid inventory number"); + + Character_AddInventory(playerchar, &scrInv[inum], SCR_NO_VALUE); + + play.obsolete_inv_numorder = charextra[game.playercharacter].invorder_count; +} + +void lose_inventory(int inum) { + if ((inum < 0) || (inum >= MAX_INV)) + quit("!LoseInventory: invalid inventory number"); + + Character_LoseInventory(playerchar, &scrInv[inum]); + + play.obsolete_inv_numorder = charextra[game.playercharacter].invorder_count; +} + +void AddInventoryToCharacter(int charid, int inum) { + if (!is_valid_character(charid)) + quit("!AddInventoryToCharacter: invalid character specified"); + if ((inum < 1) || (inum >= game.numinvitems)) + quit("!AddInventory: invalid inv item specified"); + + Character_AddInventory(&game.chars[charid], &scrInv[inum], SCR_NO_VALUE); +} + +void LoseInventoryFromCharacter(int charid, int inum) { + if (!is_valid_character(charid)) + quit("!LoseInventoryFromCharacter: invalid character specified"); + if ((inum < 1) || (inum >= game.numinvitems)) + quit("!AddInventory: invalid inv item specified"); + + Character_LoseInventory(&game.chars[charid], &scrInv[inum]); +} + +void DisplayThought(int chid, const char *text) { + if ((chid < 0) || (chid >= game.numcharacters)) + quit("!DisplayThought: invalid character specified"); + + _DisplayThoughtCore(chid, text); +} + +void __sc_displayspeech(int chid, const char *text) { + if ((chid<0) || (chid>=game.numcharacters)) + quit("!DisplaySpeech: invalid character specified"); + + _DisplaySpeechCore(chid, text); +} + +// **** THIS IS UNDOCUMENTED BECAUSE IT DOESN'T WORK PROPERLY +// **** AT 640x400 AND DOESN'T USE THE RIGHT SPEECH STYLE +void DisplaySpeechAt (int xx, int yy, int wii, int aschar, const char*spch) { + data_to_game_coords(&xx, &yy); + wii = data_to_game_coord(wii); + _displayspeech (get_translation(spch), aschar, xx, yy, wii, 0); +} + +int DisplaySpeechBackground(int charid, const char*speel) { + // remove any previous background speech for this character + for (size_t i = 0; i < screenover.size();) { + if (screenover[i].bgSpeechForChar == charid) + remove_screen_overlay_index(i); + else + i++; + } + + int ovrl=CreateTextOverlay(OVR_AUTOPLACE,charid,play.GetUIViewport().GetWidth()/2,FONT_SPEECH, + -game.chars[charid].talkcolor, get_translation(speel), DISPLAYTEXT_NORMALOVERLAY); + + int scid = find_overlay_of_type(ovrl); + screenover[scid].bgSpeechForChar = charid; + screenover[scid].timeout = GetTextDisplayTime(speel, 1); + return ovrl; +} diff --git a/engines/ags/engine/ac/global_character.h b/engines/ags/engine/ac/global_character.h new file mode 100644 index 00000000000..ad066c3ad21 --- /dev/null +++ b/engines/ags/engine/ac/global_character.h @@ -0,0 +1,87 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALCHARACTER_H +#define __AGS_EE_AC__GLOBALCHARACTER_H + +#include "ac/characterinfo.h" + +void StopMoving(int chaa); +void ReleaseCharacterView(int chat); +void MoveToWalkableArea(int charid); +void FaceLocation(int cha, int xx, int yy); +void FaceCharacter(int cha,int toface); +void SetCharacterIdle(int who, int iview, int itime); +int GetCharacterWidth(int ww); +int GetCharacterHeight(int charid); +void SetCharacterBaseline (int obn, int basel); +// pass trans=0 for fully solid, trans=100 for fully transparent +void SetCharacterTransparency(int obn,int trans); +void scAnimateCharacter (int chh, int loopn, int sppd, int rept); +void AnimateCharacterEx(int chh, int loopn, int sppd, int rept, int direction, int blocking); +void SetPlayerCharacter(int newchar); +void FollowCharacterEx(int who, int tofollow, int distaway, int eagerness); +void FollowCharacter(int who, int tofollow); +void SetCharacterIgnoreLight (int who, int yesorno); +void MoveCharacter(int cc,int xx,int yy); +void MoveCharacterDirect(int cc,int xx, int yy); +void MoveCharacterStraight(int cc,int xx, int yy); +// Append to character path +void MoveCharacterPath (int chac, int tox, int toy); + +void SetCharacterSpeedEx(int chaa, int xspeed, int yspeed); +void SetCharacterSpeed(int chaa,int nspeed); +void SetTalkingColor(int chaa,int ncol); +void SetCharacterSpeechView (int chaa, int vii); +void SetCharacterBlinkView (int chaa, int vii, int intrv); +void SetCharacterView(int chaa,int vii); +void SetCharacterFrame(int chaa, int view, int loop, int frame); +// similar to SetCharView, but aligns the frame to make it line up +void SetCharacterViewEx (int chaa, int vii, int loop, int align); +void SetCharacterViewOffset (int chaa, int vii, int xoffs, int yoffs); +void ChangeCharacterView(int chaa,int vii); +void SetCharacterClickable (int cha, int clik); +void SetCharacterIgnoreWalkbehinds (int cha, int clik); +void MoveCharacterToObject(int chaa,int obbj); +void MoveCharacterToHotspot(int chaa,int hotsp); +void MoveCharacterBlocking(int chaa,int xx,int yy,int direct); + +void RunCharacterInteraction (int cc, int mood); +int AreCharObjColliding(int charid,int objid); +int AreCharactersColliding(int cchar1,int cchar2); + +int GetCharacterProperty (int cha, const char *property); +void SetCharacterProperty (int who, int flag, int yesorno); +int GetPlayerCharacter(); +void GetCharacterPropertyText (int item, const char *property, char *bufer); + +int GetCharacterSpeechAnimationDelay(CharacterInfo *cha); +int GetCharIDAtScreen(int xx, int yy); + +void SetActiveInventory(int iit); +void AddInventoryToCharacter(int charid, int inum); +void LoseInventoryFromCharacter(int charid, int inum); +void update_invorder(); +void add_inventory(int inum); +void lose_inventory(int inum); + +void DisplayThought(int chid, const char *text); +void __sc_displayspeech(int chid, const char *text); +void DisplaySpeechAt (int xx, int yy, int wii, int aschar, const char*spch); +int DisplaySpeechBackground(int charid, const char*speel); + +#endif // __AGS_EE_AC__CHARACTEREXTRAS_H diff --git a/engines/ags/engine/ac/global_datetime.cpp b/engines/ags/engine/ac/global_datetime.cpp new file mode 100644 index 00000000000..e89311efc00 --- /dev/null +++ b/engines/ags/engine/ac/global_datetime.cpp @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/global_datetime.h" +#include "ac/datetime.h" +#include "ac/common.h" + +int sc_GetTime(int whatti) { + ScriptDateTime *sdt = DateTime_Now_Core(); + int returnVal = 0; + + if (whatti == 1) returnVal = sdt->hour; + else if (whatti == 2) returnVal = sdt->minute; + else if (whatti == 3) returnVal = sdt->second; + else if (whatti == 4) returnVal = sdt->day; + else if (whatti == 5) returnVal = sdt->month; + else if (whatti == 6) returnVal = sdt->year; + else quit("!GetTime: invalid parameter passed"); + + delete sdt; + + return returnVal; +} + +int GetRawTime () { + // TODO: we might need to modify script API to support larger time type + return static_cast(time(nullptr)); +} diff --git a/engines/ags/engine/ac/global_datetime.h b/engines/ags/engine/ac/global_datetime.h new file mode 100644 index 00000000000..59080d1a7a4 --- /dev/null +++ b/engines/ags/engine/ac/global_datetime.h @@ -0,0 +1,24 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALDATETIME_H +#define __AGS_EE_AC__GLOBALDATETIME_H + +int sc_GetTime(int whatti) ; +int GetRawTime (); + +#endif // __AGS_EE_AC__GLOBALDATETIME_H diff --git a/engines/ags/engine/ac/global_debug.cpp b/engines/ags/engine/ac/global_debug.cpp new file mode 100644 index 00000000000..582e1d20094 --- /dev/null +++ b/engines/ags/engine/ac/global_debug.cpp @@ -0,0 +1,190 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_debug.h" +#include "ac/common.h" +#include "ac/characterinfo.h" +#include "ac/draw.h" +#include "ac/game.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_character.h" +#include "ac/global_display.h" +#include "ac/global_room.h" +#include "ac/movelist.h" +#include "ac/properties.h" +#include "ac/sys_events.h" +#include "ac/tree_map.h" +#include "ac/walkablearea.h" +#include "gfx/gfxfilter.h" +#include "gui/guidialog.h" +#include "script/cc_options.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "main/main.h" +#include "ac/spritecache.h" +#include "gfx/bitmap.h" +#include "gfx/graphicsdriver.h" +#include "main/graphics_mode.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern GameSetup usetup; +extern GameState play; +extern RoomStruct thisroom; +extern CharacterInfo*playerchar; + +extern int convert_16bit_bgr; +extern IGraphicsDriver *gfxDriver; +extern SpriteCache spriteset; +extern TreeMap *transtree; +extern int displayed_room, starting_room; +extern MoveList *mls; +extern char transFileName[MAX_PATH]; + +String GetRuntimeInfo() +{ + DisplayMode mode = gfxDriver->GetDisplayMode(); + Rect render_frame = gfxDriver->GetRenderDestination(); + PGfxFilter filter = gfxDriver->GetGraphicsFilter(); + String runtimeInfo = String::FromFormat( + "Adventure Game Studio run-time engine[ACI version %s" + "[Game resolution %d x %d (%d-bit)" + "[Running %d x %d at %d-bit%s%s[GFX: %s; %s[Draw frame %d x %d[" + "Sprite cache size: %d KB (limit %d KB; %d locked)", + EngineVersion.LongString.GetCStr(), game.GetGameRes().Width, game.GetGameRes().Height, game.GetColorDepth(), + mode.Width, mode.Height, mode.ColorDepth, (convert_16bit_bgr) ? " BGR" : "", + mode.Windowed ? " W" : "", + gfxDriver->GetDriverName(), filter->GetInfo().Name.GetCStr(), + render_frame.GetWidth(), render_frame.GetHeight(), + spriteset.GetCacheSize() / 1024, spriteset.GetMaxCacheSize() / 1024, spriteset.GetLockedSize() / 1024); + if (play.separate_music_lib) + runtimeInfo.Append("[AUDIO.VOX enabled"); + if (play.want_speech >= 1) + runtimeInfo.Append("[SPEECH.VOX enabled"); + if (transtree != nullptr) { + runtimeInfo.Append("[Using translation "); + runtimeInfo.Append(transFileName); + } + if (usetup.mod_player == 0) + runtimeInfo.Append("[(mod/xm player discarded)"); + + return runtimeInfo; +} + +void script_debug(int cmdd,int dataa) { + if (play.debug_mode==0) return; + int rr; + if (cmdd==0) { + for (rr=1;rrinv[rr]=1; + update_invorder(); + // Display("invorder decided there are %d items[display %d",play.inv_numorder,play.inv_numdisp); + } + else if (cmdd==1) { + String toDisplay = GetRuntimeInfo(); + Display(toDisplay.GetCStr()); + // Display("shftR: %d shftG: %d shftB: %d", _rgb_r_shift_16, _rgb_g_shift_16, _rgb_b_shift_16); + // Display("Remaining memory: %d kb",_go32_dpmi_remaining_virtual_memory()/1024); + //Display("Play char bcd: %d",->GetColorDepth(spriteset[views[playerchar->view].frames[playerchar->loop][playerchar->frame].pic])); + } + else if (cmdd==2) + { // show walkable areas from here + // TODO: support multiple viewports?! + const int viewport_index = 0; + const int camera_index = 0; + Bitmap *tempw=BitmapHelper::CreateBitmap(thisroom.WalkAreaMask->GetWidth(),thisroom.WalkAreaMask->GetHeight()); + tempw->Blit(prepare_walkable_areas(-1),0,0,0,0,tempw->GetWidth(),tempw->GetHeight()); + const Rect &viewport = play.GetRoomViewport(viewport_index)->GetRect(); + const Rect &camera = play.GetRoomCamera(camera_index)->GetRect(); + Bitmap *view_bmp = BitmapHelper::CreateBitmap(viewport.GetWidth(), viewport.GetHeight()); + Rect mask_src = Rect(camera.Left / thisroom.MaskResolution, camera.Top / thisroom.MaskResolution, camera.Right / thisroom.MaskResolution, camera.Bottom / thisroom.MaskResolution); + view_bmp->StretchBlt(tempw, mask_src, RectWH(0, 0, viewport.GetWidth(), viewport.GetHeight()), Common::kBitmap_Transparency); + + IDriverDependantBitmap *ddb = gfxDriver->CreateDDBFromBitmap(view_bmp, false, true); + render_graphics(ddb, viewport.Left, viewport.Top); + + delete tempw; + delete view_bmp; + gfxDriver->DestroyDDB(ddb); + ags_wait_until_keypress(); + invalidate_screen(); + } + else if (cmdd==3) + { + int goToRoom = -1; + if (game.roomCount == 0) + { + char inroomtex[80]; + sprintf(inroomtex, "!Enter new room: (in room %d)", displayed_room); + setup_for_dialog(); + goToRoom = enternumberwindow(inroomtex); + restore_after_dialog(); + } + else + { + setup_for_dialog(); + goToRoom = roomSelectorWindow(displayed_room, game.roomCount, game.roomNumbers, game.roomNames); + restore_after_dialog(); + } + if (goToRoom >= 0) + NewRoom(goToRoom); + } + else if (cmdd == 4) { + if (display_fps != kFPS_Forced) + display_fps = (FPSDisplayMode)dataa; + } + else if (cmdd == 5) { + if (dataa == 0) dataa = game.playercharacter; + if (game.chars[dataa].walking < 1) { + Display("Not currently moving."); + return; + } + Bitmap *tempw=BitmapHelper::CreateTransparentBitmap(thisroom.WalkAreaMask->GetWidth(),thisroom.WalkAreaMask->GetHeight()); + int mlsnum = game.chars[dataa].walking; + if (game.chars[dataa].walking >= TURNING_AROUND) + mlsnum %= TURNING_AROUND; + MoveList*cmls = &mls[mlsnum]; + for (int i = 0; i < cmls->numstage-1; i++) { + short srcx=short((cmls->pos[i] >> 16) & 0x00ffff); + short srcy=short(cmls->pos[i] & 0x00ffff); + short targetx=short((cmls->pos[i+1] >> 16) & 0x00ffff); + short targety=short(cmls->pos[i+1] & 0x00ffff); + tempw->DrawLine(Line(srcx, srcy, targetx, targety), MakeColor(i+1)); + } + + // TODO: support multiple viewports?! + const int viewport_index = 0; + const int camera_index = 0; + const Rect &viewport = play.GetRoomViewport(viewport_index)->GetRect(); + const Rect &camera = play.GetRoomCamera(camera_index)->GetRect(); + Bitmap *view_bmp = BitmapHelper::CreateBitmap(viewport.GetWidth(), viewport.GetHeight()); + Rect mask_src = Rect(camera.Left / thisroom.MaskResolution, camera.Top / thisroom.MaskResolution, camera.Right / thisroom.MaskResolution, camera.Bottom / thisroom.MaskResolution); + view_bmp->StretchBlt(tempw, mask_src, RectWH(0, 0, viewport.GetWidth(), viewport.GetHeight()), Common::kBitmap_Transparency); + + IDriverDependantBitmap *ddb = gfxDriver->CreateDDBFromBitmap(view_bmp, false, true); + render_graphics(ddb, viewport.Left, viewport.Top); + + delete tempw; + delete view_bmp; + gfxDriver->DestroyDDB(ddb); + ags_wait_until_keypress(); + } + else if (cmdd == 99) + ccSetOption(SCOPT_DEBUGRUN, dataa); + else quit("!Debug: unknown command code"); +} diff --git a/engines/ags/engine/ac/global_debug.h b/engines/ags/engine/ac/global_debug.h new file mode 100644 index 00000000000..a4d50d0d4fd --- /dev/null +++ b/engines/ags/engine/ac/global_debug.h @@ -0,0 +1,27 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALDEBUG_H +#define __AGS_EE_AC__GLOBALDEBUG_H + +#include +#include "util/string.h" + +AGS::Common::String GetRuntimeInfo(); +void script_debug(int cmdd,int dataa); + +#endif // __AGS_EE_AC__GLOBALDEBUG_H diff --git a/engines/ags/engine/ac/global_dialog.cpp b/engines/ags/engine/ac/global_dialog.cpp new file mode 100644 index 00000000000..dbab9333a72 --- /dev/null +++ b/engines/ags/engine/ac/global_dialog.cpp @@ -0,0 +1,103 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_dialog.h" +#include "ac/common.h" +#include "ac/dialog.h" +#include "ac/dialogtopic.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "debug/out.h" +#include "script/script.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; +extern GameState play; +extern DialogTopic *dialog; + +ScriptPosition last_in_dialog_request_script_pos; +void RunDialog(int tum) { + if ((tum<0) | (tum>=game.numdialog)) + quit("!RunDialog: invalid topic number specified"); + + can_run_delayed_command(); + + if (play.stop_dialog_at_end != DIALOG_NONE) { + if (play.stop_dialog_at_end == DIALOG_RUNNING) + play.stop_dialog_at_end = DIALOG_NEWTOPIC + tum; + else + quitprintf("!RunDialog: two NewRoom/RunDialog/StopDialog requests within dialog; last was called in \"%s\", line %d", + last_in_dialog_request_script_pos.Section.GetCStr(), last_in_dialog_request_script_pos.Line); + return; + } + + get_script_position(last_in_dialog_request_script_pos); + + if (inside_script) + curscript->queue_action(ePSARunDialog, tum, "RunDialog"); + else + do_conversation(tum); +} + + +void StopDialog() { + if (play.stop_dialog_at_end == DIALOG_NONE) { + debug_script_warn("StopDialog called, but was not in a dialog"); + debug_script_log("StopDialog called but no dialog"); + return; + } + get_script_position(last_in_dialog_request_script_pos); + play.stop_dialog_at_end = DIALOG_STOP; +} + +void SetDialogOption(int dlg, int opt, int onoroff, bool dlg_script) +{ + if ((dlg<0) | (dlg>=game.numdialog)) + quit("!SetDialogOption: Invalid topic number specified"); + if ((opt<1) | (opt>dialog[dlg].numoptions)) + { + // Pre-3.1.1 games had "dialog scripts" that were written in different language and + // parsed differently; its "option-on/off" commands were more permissive. + if (dlg_script) + { + Debug::Printf(kDbgGroup_Game, kDbgMsg_Error, "SetDialogOption: Invalid option number specified (%d : %d)", dlg, opt); + return; + } + quit("!SetDialogOption: Invalid option number specified"); + } + opt--; + + dialog[dlg].optionflags[opt]&=~DFLG_ON; + if ((onoroff==1) & ((dialog[dlg].optionflags[opt] & DFLG_OFFPERM)==0)) + dialog[dlg].optionflags[opt]|=DFLG_ON; + else if (onoroff==2) + dialog[dlg].optionflags[opt]|=DFLG_OFFPERM; +} + +int GetDialogOption (int dlg, int opt) { + if ((dlg<0) | (dlg>=game.numdialog)) + quit("!GetDialogOption: Invalid topic number specified"); + if ((opt<1) | (opt>dialog[dlg].numoptions)) + quit("!GetDialogOption: Invalid option number specified"); + opt--; + + if (dialog[dlg].optionflags[opt] & DFLG_OFFPERM) + return 2; + if (dialog[dlg].optionflags[opt] & DFLG_ON) + return 1; + return 0; +} diff --git a/engines/ags/engine/ac/global_dialog.h b/engines/ags/engine/ac/global_dialog.h new file mode 100644 index 00000000000..feb16ae10f5 --- /dev/null +++ b/engines/ags/engine/ac/global_dialog.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALDIALOG_H +#define __AGS_EE_AC__GLOBALDIALOG_H + +void RunDialog(int tum); +int GetDialogOption (int dlg, int opt); +void SetDialogOption(int dlg, int opt, int onoroff, bool dlg_script = false); +void StopDialog(); + +#endif // __AGS_EE_AC__GLOBALDIALOG_H diff --git a/engines/ags/engine/ac/global_display.cpp b/engines/ags/engine/ac/global_display.cpp new file mode 100644 index 00000000000..dfcd76bcc40 --- /dev/null +++ b/engines/ags/engine/ac/global_display.cpp @@ -0,0 +1,200 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "ac/common.h" +#include "ac/character.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_character.h" +#include "ac/global_display.h" +#include "ac/global_screen.h" +#include "ac/global_translation.h" +#include "ac/runtime_defines.h" +#include "ac/speech.h" +#include "ac/string.h" +#include "ac/topbarsettings.h" +#include "debug/debug_log.h" +#include "game/roomstruct.h" +#include "main/game_run.h" + +using namespace AGS::Common; + +extern TopBarSettings topBar; +extern GameState play; +extern RoomStruct thisroom; +extern int display_message_aschar; +extern GameSetupStruct game; + +void Display(const char*texx, ...) { + char displbuf[STD_BUFFER_SIZE]; + va_list ap; + va_start(ap,texx); + vsprintf(displbuf, get_translation(texx), ap); + va_end(ap); + DisplayAtY (-1, displbuf); +} + +void DisplaySimple(const char *text) +{ + DisplayAtY (-1, text); +} + +void DisplayTopBar(int ypos, int ttexcol, int backcol, const char *title, const char *text) +{ + // FIXME: refactor source_text_length and get rid of this ugly hack! + const int real_text_sourcelen = source_text_length; + snprintf(topBar.text, sizeof(topBar.text), "%s", get_translation(title)); + source_text_length = real_text_sourcelen; + + if (ypos > 0) + play.top_bar_ypos = ypos; + if (ttexcol > 0) + play.top_bar_textcolor = ttexcol; + if (backcol > 0) + play.top_bar_backcolor = backcol; + + topBar.wantIt = 1; + topBar.font = FONT_NORMAL; + topBar.height = getfontheight_outlined(topBar.font); + topBar.height += data_to_game_coord(play.top_bar_borderwidth) * 2 + get_fixed_pixel_size(1); + + // they want to customize the font + if (play.top_bar_font >= 0) + topBar.font = play.top_bar_font; + + // DisplaySpeech normally sets this up, but since we're not going via it... + if (play.cant_skip_speech & SKIP_AUTOTIMER) + play.messagetime = GetTextDisplayTime(text); + + DisplayAtY(play.top_bar_ypos, text); +} + +// Display a room/global message in the bar +void DisplayMessageBar(int ypos, int ttexcol, int backcol, const char *title, int msgnum) { + char msgbufr[3001]; + get_message_text(msgnum, msgbufr); + DisplayTopBar(ypos, ttexcol, backcol, title, msgbufr); +} + +void DisplayMessageAtY(int msnum, int ypos) { + char msgbufr[3001]; + if (msnum>=500) { + get_message_text (msnum, msgbufr); + if (display_message_aschar > 0) + DisplaySpeech(msgbufr, display_message_aschar); + else + DisplayAtY(ypos, msgbufr); + display_message_aschar=0; + return; + } + + if (display_message_aschar > 0) { + display_message_aschar=0; + quit("!DisplayMessage: data column specified a character for local\n" + "message; use the message editor to select the character for room\n" + "messages.\n"); + } + + int repeatloop=1; + while (repeatloop) { + get_message_text (msnum, msgbufr); + + if (thisroom.MessageInfos[msnum].DisplayAs > 0) { + DisplaySpeech(msgbufr, thisroom.MessageInfos[msnum].DisplayAs - 1); + } + else { + // time out automatically if they have set that + int oldGameSkipDisp = play.skip_display; + if (thisroom.MessageInfos[msnum].Flags & MSG_TIMELIMIT) + play.skip_display = 0; + + DisplayAtY(ypos, msgbufr); + + play.skip_display = oldGameSkipDisp; + } + if (thisroom.MessageInfos[msnum].Flags & MSG_DISPLAYNEXT) { + msnum++; + repeatloop=1; + } + else + repeatloop=0; + } + +} + +void DisplayMessage(int msnum) { + DisplayMessageAtY (msnum, -1); +} + +void DisplayAt(int xxp,int yyp,int widd, const char* text) { + data_to_game_coords(&xxp, &yyp); + widd = data_to_game_coord(widd); + + if (widd<1) widd=play.GetUIViewport().GetWidth()/2; + if (xxp<0) xxp=play.GetUIViewport().GetWidth()/2-widd/2; + _display_at(xxp, yyp, widd, text, DISPLAYTEXT_MESSAGEBOX, 0, 0, 0, false); +} + +void DisplayAtY (int ypos, const char *texx) { + const Rect &ui_view = play.GetUIViewport(); + if ((ypos < -1) || (ypos >= ui_view.GetHeight())) + quitprintf("!DisplayAtY: invalid Y co-ordinate supplied (used: %d; valid: 0..%d)", ypos, ui_view.GetHeight()); + + // Display("") ... a bit of a stupid thing to do, so ignore it + if (texx[0] == 0) + return; + + if (ypos > 0) + ypos = data_to_game_coord(ypos); + + if (game.options[OPT_ALWAYSSPCH]) + DisplaySpeechAt(-1, (ypos > 0) ? game_to_data_coord(ypos) : ypos, -1, game.playercharacter, texx); + else { + // Normal "Display" in text box + + if (is_screen_dirty()) { + // erase any previous DisplaySpeech + play.disabled_user_interface ++; + UpdateGameOnce(); + play.disabled_user_interface --; + } + + _display_at(-1, ypos, ui_view.GetWidth() / 2 + ui_view.GetWidth() / 4, + get_translation(texx), DISPLAYTEXT_MESSAGEBOX, 0, 0, 0, false); + } +} + +void SetSpeechStyle (int newstyle) { + if ((newstyle < 0) || (newstyle > 3)) + quit("!SetSpeechStyle: must use a SPEECH_* constant as parameter"); + game.options[OPT_SPEECHTYPE] = newstyle; +} + +void SetSkipSpeech (SkipSpeechStyle newval) { + if ((newval < kSkipSpeechFirst) || (newval > kSkipSpeechLast)) + quit("!SetSkipSpeech: invalid skip mode specified"); + + debug_script_log("SkipSpeech style set to %d", newval); + play.cant_skip_speech = user_to_internal_skip_speech((SkipSpeechStyle)newval); +} + +SkipSpeechStyle GetSkipSpeech() +{ + return internal_skip_speech_to_user(play.cant_skip_speech); +} diff --git a/engines/ags/engine/ac/global_display.h b/engines/ags/engine/ac/global_display.h new file mode 100644 index 00000000000..538d293924a --- /dev/null +++ b/engines/ags/engine/ac/global_display.h @@ -0,0 +1,37 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALDISPLAY_H +#define __AGS_EE_AC__GLOBALDISPLAY_H + +#include "ac/speech.h" + +void Display(const char*texx, ...); // applies translation +void DisplaySimple(const char* text); // does not apply translation +void DisplayAt(int xxp,int yyp,int widd, const char*text); +void DisplayAtY (int ypos, const char *texx); +void DisplayMessage(int msnum); +void DisplayMessageAtY(int msnum, int ypos); +void DisplayTopBar(int ypos, int ttexcol, int backcol, const char *title, const char *text); +// Display a room/global message in the bar +void DisplayMessageBar(int ypos, int ttexcol, int backcol, const char *title, int msgnum); + +void SetSpeechStyle (int newstyle); +void SetSkipSpeech (SkipSpeechStyle newval); +SkipSpeechStyle GetSkipSpeech(); + +#endif // __AGS_EE_AC__GLOBALDISPLAY_H diff --git a/engines/ags/engine/ac/global_drawingsurface.cpp b/engines/ags/engine/ac/global_drawingsurface.cpp new file mode 100644 index 00000000000..4003c78f654 --- /dev/null +++ b/engines/ags/engine/ac/global_drawingsurface.cpp @@ -0,0 +1,301 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_drawingsurface.h" +#include "ac/global_translation.h" +#include "ac/string.h" +#include "debug/debug_log.h" +#include "font/fonts.h" +#include "game/roomstruct.h" +#include "gui/guidefines.h" +#include "ac/spritecache.h" +#include "gfx/gfx_def.h" +#include "gfx/gfx_util.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern Bitmap *raw_saved_screen; +extern RoomStruct thisroom; +extern GameState play; +extern SpriteCache spriteset; +extern GameSetupStruct game; + +// Raw screen writing routines - similar to old CapturedStuff +#define RAW_START() play.raw_drawing_surface = thisroom.BgFrames[play.bg_frame].Graphic; play.raw_modified[play.bg_frame] = 1 +#define RAW_END() +#define RAW_SURFACE() (play.raw_drawing_surface.get()) + +// RawSaveScreen: copy the current screen to a backup bitmap +void RawSaveScreen () { + if (raw_saved_screen != nullptr) + delete raw_saved_screen; + PBitmap source = thisroom.BgFrames[play.bg_frame].Graphic; + raw_saved_screen = BitmapHelper::CreateBitmapCopy(source.get()); +} +// RawRestoreScreen: copy backup bitmap back to screen; we +// deliberately don't free the Bitmap *cos they can multiple restore +// and it gets freed on room exit anyway +void RawRestoreScreen() { + if (raw_saved_screen == nullptr) { + debug_script_warn("RawRestoreScreen: unable to restore, since the screen hasn't been saved previously."); + return; + } + PBitmap deston = thisroom.BgFrames[play.bg_frame].Graphic; + deston->Blit(raw_saved_screen, 0, 0, 0, 0, deston->GetWidth(), deston->GetHeight()); + invalidate_screen(); + mark_current_background_dirty(); +} +// Restores the backup bitmap, but tints it to the specified level +void RawRestoreScreenTinted(int red, int green, int blue, int opacity) { + if (raw_saved_screen == nullptr) { + debug_script_warn("RawRestoreScreenTinted: unable to restore, since the screen hasn't been saved previously."); + return; + } + if ((red < 0) || (green < 0) || (blue < 0) || + (red > 255) || (green > 255) || (blue > 255) || + (opacity < 1) || (opacity > 100)) + quit("!RawRestoreScreenTinted: invalid parameter. R,G,B must be 0-255, opacity 1-100"); + + debug_script_log("RawRestoreTinted RGB(%d,%d,%d) %d%%", red, green, blue, opacity); + + PBitmap deston = thisroom.BgFrames[play.bg_frame].Graphic; + tint_image(deston.get(), raw_saved_screen, red, green, blue, opacity); + invalidate_screen(); + mark_current_background_dirty(); +} + +void RawDrawFrameTransparent (int frame, int translev) { + if ((frame < 0) || ((size_t)frame >= thisroom.BgFrameCount) || + (translev < 0) || (translev > 99)) + quit("!RawDrawFrameTransparent: invalid parameter (transparency must be 0-99, frame a valid BG frame)"); + + PBitmap bg = thisroom.BgFrames[frame].Graphic; + if (bg->GetColorDepth() <= 8) + quit("!RawDrawFrameTransparent: 256-colour backgrounds not supported"); + + if (frame == play.bg_frame) + quit("!RawDrawFrameTransparent: cannot draw current background onto itself"); + + RAW_START(); + if (translev == 0) + { + // just draw it over the top, no transparency + RAW_SURFACE()->Blit(bg.get(), 0, 0, 0, 0, bg->GetWidth(), bg->GetHeight()); + } + else + { + // Draw it transparently + GfxUtil::DrawSpriteWithTransparency (RAW_SURFACE(), bg.get(), 0, 0, + GfxDef::Trans100ToAlpha255(translev)); + } + invalidate_screen(); + mark_current_background_dirty(); + RAW_END(); +} + +void RawClear (int clr) { + RAW_START(); + clr = RAW_SURFACE()->GetCompatibleColor(clr); + RAW_SURFACE()->Clear (clr); + invalidate_screen(); + mark_current_background_dirty(); +} +void RawSetColor (int clr) { + // set the colour at the appropriate depth for the background + play.raw_color = MakeColor(clr); +} +void RawSetColorRGB(int red, int grn, int blu) { + if ((red < 0) || (red > 255) || (grn < 0) || (grn > 255) || + (blu < 0) || (blu > 255)) + quit("!RawSetColorRGB: colour values must be 0-255"); + + play.raw_color = makecol_depth(thisroom.BgFrames[play.bg_frame].Graphic->GetColorDepth(), red, grn, blu); +} +void RawPrint (int xx, int yy, const char *text) { + RAW_START(); + // don't use wtextcolor because it will do a 16->32 conversion + color_t text_color = play.raw_color; + if ((RAW_SURFACE()->GetColorDepth() <= 8) && (play.raw_color > 255)) { + text_color = RAW_SURFACE()->GetCompatibleColor(1); + debug_script_warn ("RawPrint: Attempted to use hi-color on 256-col background"); + } + data_to_game_coords(&xx, &yy); + wouttext_outline(RAW_SURFACE(), xx, yy, play.normal_font, text_color, text); + // we must invalidate the entire screen because these are room + // co-ordinates, not screen co-ords which it works with + invalidate_screen(); + mark_current_background_dirty(); + RAW_END(); +} +void RawPrintMessageWrapped (int xx, int yy, int wid, int font, int msgm) { + char displbuf[3000]; + int linespacing = getfontspacing_outlined(font); + data_to_game_coords(&xx, &yy); + wid = data_to_game_coord(wid); + + get_message_text (msgm, displbuf); + // it's probably too late but check anyway + if (strlen(displbuf) > 2899) + quit("!RawPrintMessageWrapped: message too long"); + if (break_up_text_into_lines(displbuf, Lines, wid, font) == 0) + return; + + RAW_START(); + color_t text_color = play.raw_color; + for (size_t i = 0; i < Lines.Count(); i++) + wouttext_outline(RAW_SURFACE(), xx, yy + linespacing*i, font, text_color, Lines[i]); + invalidate_screen(); + mark_current_background_dirty(); + RAW_END(); +} + +void RawDrawImageCore(int xx, int yy, int slot, int alpha) { + if ((slot < 0) || (spriteset[slot] == nullptr)) + quit("!RawDrawImage: invalid sprite slot number specified"); + RAW_START(); + + if (spriteset[slot]->GetColorDepth() != RAW_SURFACE()->GetColorDepth()) { + debug_script_warn("RawDrawImage: Sprite %d colour depth %d-bit not same as background depth %d-bit", slot, spriteset[slot]->GetColorDepth(), RAW_SURFACE()->GetColorDepth()); + } + + draw_sprite_slot_support_alpha(RAW_SURFACE(), false, xx, yy, slot, kBlendMode_Alpha, alpha); + invalidate_screen(); + mark_current_background_dirty(); + RAW_END(); +} + +void RawDrawImage(int xx, int yy, int slot) { + data_to_game_coords(&xx, &yy); + RawDrawImageCore(xx, yy, slot); +} + +void RawDrawImageTrans(int xx, int yy, int slot, int alpha) { + data_to_game_coords(&xx, &yy); + RawDrawImageCore(xx, yy, slot, alpha); +} + +void RawDrawImageOffset(int xx, int yy, int slot) { + // This function takes coordinates in real game coordinates as opposed to script coordinates + defgame_to_finalgame_coords(xx, yy); + RawDrawImageCore(xx, yy, slot); +} + +void RawDrawImageTransparent(int xx, int yy, int slot, int legacy_transparency) { + if ((legacy_transparency < 0) || (legacy_transparency > 100)) + quit("!RawDrawImageTransparent: invalid transparency setting"); + + // WARNING: the previous versions of AGS actually had a bug: + // although manual stated that RawDrawImageTransparent takes % of transparency + // as an argument, that value was used improperly when setting up an Allegro's + // trans_blender, which caused it to act about as % of opacity instead, but + // with a twist. + // + // It was converted to 255-ranged "transparency" parameter: + // int transparency = (trans * 255) / 100; + // + // Note by CJ: + // Transparency is a bit counter-intuitive + // 0=not transparent, 255=invisible, 1..254 barely visible .. mostly visible + // + // In order to support this backward-compatible behavior, we convert the + // opacity into proper alpha this way: + // 0 => alpha 255 + // 100 => alpha 0 + // 1 - 99 => alpha 1 - 244 + // + RawDrawImageTrans(xx, yy, slot, GfxDef::LegacyTrans100ToAlpha255(legacy_transparency)); + + update_polled_stuff_if_runtime(); // this operation can be slow so stop music skipping +} +void RawDrawImageResized(int xx, int yy, int gotSlot, int width, int height) { + if ((gotSlot < 0) || (spriteset[gotSlot] == nullptr)) + quit("!RawDrawImageResized: invalid sprite slot number specified"); + // very small, don't draw it + if ((width < 1) || (height < 1)) + return; + + data_to_game_coords(&xx, &yy); + data_to_game_coords(&width, &height); + + // resize the sprite to the requested size + Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, spriteset[gotSlot]->GetColorDepth()); + newPic->StretchBlt(spriteset[gotSlot], + RectWH(0, 0, game.SpriteInfos[gotSlot].Width, game.SpriteInfos[gotSlot].Height), + RectWH(0, 0, width, height)); + + RAW_START(); + if (newPic->GetColorDepth() != RAW_SURFACE()->GetColorDepth()) + quit("!RawDrawImageResized: image colour depth mismatch: the background image must have the same colour depth as the sprite being drawn"); + + GfxUtil::DrawSpriteWithTransparency(RAW_SURFACE(), newPic, xx, yy); + delete newPic; + invalidate_screen(); + mark_current_background_dirty(); + update_polled_stuff_if_runtime(); // this operation can be slow so stop music skipping + RAW_END(); +} +void RawDrawLine (int fromx, int fromy, int tox, int toy) { + data_to_game_coords(&fromx, &fromy); + data_to_game_coords(&tox, &toy); + + play.raw_modified[play.bg_frame] = 1; + int ii,jj; + // draw a line thick enough to look the same at all resolutions + PBitmap bg = thisroom.BgFrames[play.bg_frame].Graphic; + color_t draw_color = play.raw_color; + for (ii = 0; ii < get_fixed_pixel_size(1); ii++) { + for (jj = 0; jj < get_fixed_pixel_size(1); jj++) + bg->DrawLine (Line(fromx+ii, fromy+jj, tox+ii, toy+jj), draw_color); + } + invalidate_screen(); + mark_current_background_dirty(); +} +void RawDrawCircle (int xx, int yy, int rad) { + data_to_game_coords(&xx, &yy); + rad = data_to_game_coord(rad); + + play.raw_modified[play.bg_frame] = 1; + PBitmap bg = thisroom.BgFrames[play.bg_frame].Graphic; + bg->FillCircle(Circle (xx, yy, rad), play.raw_color); + invalidate_screen(); + mark_current_background_dirty(); +} +void RawDrawRectangle(int x1, int y1, int x2, int y2) { + play.raw_modified[play.bg_frame] = 1; + data_to_game_coords(&x1, &y1); + data_to_game_round_up(&x2, &y2); + + PBitmap bg = thisroom.BgFrames[play.bg_frame].Graphic; + bg->FillRect(Rect(x1,y1,x2,y2), play.raw_color); + invalidate_screen(); + mark_current_background_dirty(); +} +void RawDrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3) { + play.raw_modified[play.bg_frame] = 1; + data_to_game_coords(&x1, &y1); + data_to_game_coords(&x2, &y2); + data_to_game_coords(&x3, &y3); + + PBitmap bg = thisroom.BgFrames[play.bg_frame].Graphic; + bg->DrawTriangle(Triangle (x1,y1,x2,y2,x3,y3), play.raw_color); + invalidate_screen(); + mark_current_background_dirty(); +} diff --git a/engines/ags/engine/ac/global_drawingsurface.h b/engines/ags/engine/ac/global_drawingsurface.h new file mode 100644 index 00000000000..d62ea61909e --- /dev/null +++ b/engines/ags/engine/ac/global_drawingsurface.h @@ -0,0 +1,44 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALDRAWINGSURFACE_H +#define __AGS_EE_AC__GLOBALDRAWINGSURFACE_H + +void RawSaveScreen (); +// RawRestoreScreen: copy backup bitmap back to screen; we +// deliberately don't free the Common::Bitmap *cos they can multiple restore +// and it gets freed on room exit anyway +void RawRestoreScreen(); +// Restores the backup bitmap, but tints it to the specified level +void RawRestoreScreenTinted(int red, int green, int blue, int opacity); +void RawDrawFrameTransparent (int frame, int translev); +void RawClear (int clr); +void RawSetColor (int clr); +void RawSetColorRGB(int red, int grn, int blu); +void RawPrint (int xx, int yy, const char *text); +void RawPrintMessageWrapped (int xx, int yy, int wid, int font, int msgm); +void RawDrawImageCore(int xx, int yy, int slot, int alpha = 0xFF); +void RawDrawImage(int xx, int yy, int slot); +void RawDrawImageOffset(int xx, int yy, int slot); +void RawDrawImageTransparent(int xx, int yy, int slot, int opacity); +void RawDrawImageResized(int xx, int yy, int gotSlot, int width, int height); +void RawDrawLine (int fromx, int fromy, int tox, int toy); +void RawDrawCircle (int xx, int yy, int rad); +void RawDrawRectangle(int x1, int y1, int x2, int y2); +void RawDrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3); + +#endif // __AGS_EE_AC__GLOBALDRAWINGSURFACE_H diff --git a/engines/ags/engine/ac/global_dynamicsprite.cpp b/engines/ags/engine/ac/global_dynamicsprite.cpp new file mode 100644 index 00000000000..b77a3f3c7ac --- /dev/null +++ b/engines/ags/engine/ac/global_dynamicsprite.cpp @@ -0,0 +1,50 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_dynamicsprite.h" +#include "util/wgt2allg.h" // Allegro RGB, PALETTE +#include "ac/draw.h" +#include "ac/dynamicsprite.h" +#include "ac/path_helper.h" +#include "ac/spritecache.h" +#include "ac/runtime_defines.h" //MAX_PATH +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern SpriteCache spriteset; +extern IGraphicsDriver *gfxDriver; + +int LoadImageFile(const char *filename) +{ + ResolvedPath rp; + if (!ResolveScriptPath(filename, true, rp)) + return 0; + + Bitmap *loadedFile = BitmapHelper::LoadFromFile(rp.FullPath); + if (!loadedFile && !rp.AltPath.IsEmpty() && rp.AltPath.Compare(rp.FullPath) != 0) + loadedFile = BitmapHelper::LoadFromFile(rp.AltPath); + if (!loadedFile) + return 0; + + int gotSlot = spriteset.GetFreeIndex(); + if (gotSlot <= 0) + return 0; + + add_dynamic_sprite(gotSlot, ReplaceBitmapWithSupportedFormat(loadedFile)); + + return gotSlot; +} diff --git a/engines/ags/engine/ac/global_dynamicsprite.h b/engines/ags/engine/ac/global_dynamicsprite.h new file mode 100644 index 00000000000..a6f3ea8cca6 --- /dev/null +++ b/engines/ags/engine/ac/global_dynamicsprite.h @@ -0,0 +1,23 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALDYNAMICSPRITE_H +#define __AGS_EE_AC__GLOBALDYNAMICSPRITE_H + +int LoadImageFile(const char *filename); + +#endif // __AGS_EE_AC__GLOBALDYNAMICSPRITE_H diff --git a/engines/ags/engine/ac/global_file.cpp b/engines/ags/engine/ac/global_file.cpp new file mode 100644 index 00000000000..d88a295cbbd --- /dev/null +++ b/engines/ags/engine/ac/global_file.cpp @@ -0,0 +1,177 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/global_file.h" +#include "ac/common.h" +#include "ac/file.h" +#include "ac/path_helper.h" +#include "ac/runtime_defines.h" +#include "ac/string.h" +#include "debug/debug_log.h" +#include "util/directory.h" +#include "util/path.h" +#include "util/stream.h" + +using namespace AGS::Common; + +int32_t FileOpenCMode(const char*fnmm, const char* cmode) +{ + Common::FileOpenMode open_mode; + Common::FileWorkMode work_mode; + // NOTE: here we ignore the text-mode flag. AGS 2.62 did not let + // game devs to open files in text mode. The file reading and + // writing logic in AGS makes extra control characters added for + // security reasons, and FileWriteRawLine adds CR/LF to the end + // of string on its own. + if (!Common::File::GetFileModesFromCMode(cmode, open_mode, work_mode)) + { + return 0; + } + return FileOpen(fnmm, open_mode, work_mode); +} + +// Find a free file slot to use +int32_t FindFreeFileSlot() +{ + int useindx = 0; + for (; useindx < num_open_script_files; useindx++) + { + if (valid_handles[useindx].stream == nullptr) + break; + } + + if (useindx >= num_open_script_files && + num_open_script_files >= MAX_OPEN_SCRIPT_FILES) + { + quit("!FileOpen: tried to open more than 10 files simultaneously - close some first"); + return -1; + } + return useindx; +} + +int32_t FileOpen(const char*fnmm, Common::FileOpenMode open_mode, Common::FileWorkMode work_mode) +{ + int32_t useindx = FindFreeFileSlot(); + if (useindx < 0) + return 0; + + ResolvedPath rp; + if (open_mode == kFile_Open && work_mode == kFile_Read) + { + if (!ResolveScriptPath(fnmm, true, rp)) + return 0; + } + else + { + if (!ResolveWritePathAndCreateDirs(fnmm, rp)) + return 0; + } + + Stream *s = File::OpenFile(rp.FullPath, open_mode, work_mode); + if (!s && !rp.AltPath.IsEmpty() && rp.AltPath.Compare(rp.FullPath) != 0) + s = File::OpenFile(rp.AltPath, open_mode, work_mode); + + valid_handles[useindx].stream = s; + if (valid_handles[useindx].stream == nullptr) + return 0; + valid_handles[useindx].handle = useindx + 1; // make handle indexes 1-based + + if (useindx >= num_open_script_files) + num_open_script_files++; + return valid_handles[useindx].handle; +} + +void FileClose(int32_t handle) { + ScriptFileHandle *sc_handle = check_valid_file_handle_int32(handle,"FileClose"); + delete sc_handle->stream; + sc_handle->stream = nullptr; + sc_handle->handle = 0; + } +void FileWrite(int32_t handle, const char *towrite) { + Stream *out = get_valid_file_stream_from_handle(handle,"FileWrite"); + out->WriteInt32(strlen(towrite)+1); + out->Write(towrite,strlen(towrite)+1); + } +void FileWriteRawLine(int32_t handle, const char*towrite) { + Stream *out = get_valid_file_stream_from_handle(handle,"FileWriteRawLine"); + out->Write(towrite,strlen(towrite)); + out->WriteInt8 (13); + out->WriteInt8 (10); + } +void FileRead(int32_t handle,char*toread) { + VALIDATE_STRING(toread); + Stream *in = get_valid_file_stream_from_handle(handle,"FileRead"); + if (in->EOS()) { + toread[0] = 0; + return; + } + int lle=in->ReadInt32(); + if ((lle>=200) | (lle<1)) quit("!FileRead: file was not written by FileWrite"); + in->Read(toread,lle); + } +int FileIsEOF (int32_t handle) { + Stream *stream = get_valid_file_stream_from_handle(handle,"FileIsEOF"); + if (stream->EOS()) + return 1; + + // TODO: stream errors + if (stream->HasErrors()) + return 1; + + if (stream->GetPosition () >= stream->GetLength()) + return 1; + return 0; +} +int FileIsError(int32_t handle) { + Stream *stream = get_valid_file_stream_from_handle(handle,"FileIsError"); + + // TODO: stream errors + if (stream->HasErrors()) + return 1; + + return 0; +} +void FileWriteInt(int32_t handle,int into) { + Stream *out = get_valid_file_stream_from_handle(handle,"FileWriteInt"); + out->WriteInt8('I'); + out->WriteInt32(into); + } +int FileReadInt(int32_t handle) { + Stream *in = get_valid_file_stream_from_handle(handle,"FileReadInt"); + if (in->EOS()) + return -1; + if (in->ReadInt8()!='I') + quit("!FileReadInt: File read back in wrong order"); + return in->ReadInt32(); + } +char FileReadRawChar(int32_t handle) { + Stream *in = get_valid_file_stream_from_handle(handle,"FileReadRawChar"); + if (in->EOS()) + return -1; + return in->ReadInt8(); + } +int FileReadRawInt(int32_t handle) { + Stream *in = get_valid_file_stream_from_handle(handle,"FileReadRawInt"); + if (in->EOS()) + return -1; + return in->ReadInt32(); +} +void FileWriteRawChar(int32_t handle, int chartoWrite) { + Stream *out = get_valid_file_stream_from_handle(handle,"FileWriteRawChar"); + if ((chartoWrite < 0) || (chartoWrite > 255)) + quit("!FileWriteRawChar: can only write values 0-255"); + + out->WriteInt8(chartoWrite); +} diff --git a/engines/ags/engine/ac/global_file.h b/engines/ags/engine/ac/global_file.h new file mode 100644 index 00000000000..1e827088eed --- /dev/null +++ b/engines/ags/engine/ac/global_file.h @@ -0,0 +1,41 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALFILE_H +#define __AGS_EE_AC__GLOBALFILE_H + +#include "util/file.h" + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +int32_t FileOpen(const char*fnmm, Common::FileOpenMode open_mode, Common::FileWorkMode work_mode); +// NOTE: FileOpenCMode is a backwards-compatible replacement for old-style global script function FileOpen +int32_t FileOpenCMode(const char*fnmm, const char* cmode); +void FileClose(int32_t handle); +void FileWrite(int32_t handle, const char *towrite); +void FileWriteRawLine(int32_t handle, const char*towrite); +void FileRead(int32_t handle,char*toread); +int FileIsEOF (int32_t handle); +int FileIsError(int32_t handle); +void FileWriteInt(int32_t handle,int into); +int FileReadInt(int32_t handle); +char FileReadRawChar(int32_t handle); +int FileReadRawInt(int32_t handle); +void FileWriteRawChar(int32_t handle, int chartoWrite); + +#endif // __AGS_EE_AC__GLOBALFILE_H diff --git a/engines/ags/engine/ac/global_game.cpp b/engines/ags/engine/ac/global_game.cpp new file mode 100644 index 00000000000..40e263122c0 --- /dev/null +++ b/engines/ags/engine/ac/global_game.cpp @@ -0,0 +1,955 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include + +#include "core/platform.h" +#include "ac/audiocliptype.h" +#include "ac/global_game.h" +#include "ac/common.h" +#include "ac/view.h" +#include "ac/character.h" +#include "ac/draw.h" +#include "ac/dynamicsprite.h" +#include "ac/event.h" +#include "ac/game.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_character.h" +#include "ac/global_gui.h" +#include "ac/global_hotspot.h" +#include "ac/global_inventoryitem.h" +#include "ac/global_translation.h" +#include "ac/gui.h" +#include "ac/hotspot.h" +#include "ac/keycode.h" +#include "ac/mouse.h" +#include "ac/object.h" +#include "ac/path_helper.h" +#include "ac/sys_events.h" +#include "ac/room.h" +#include "ac/roomstatus.h" +#include "ac/string.h" +#include "ac/system.h" +#include "debug/debugger.h" +#include "debug/debug_log.h" +#include "gui/guidialog.h" +#include "main/engine.h" +#include "main/game_start.h" +#include "main/game_run.h" +#include "main/graphics_mode.h" +#include "script/script.h" +#include "script/script_runtime.h" +#include "ac/spritecache.h" +#include "gfx/bitmap.h" +#include "gfx/graphicsdriver.h" +#include "core/assetmanager.h" +#include "main/config.h" +#include "main/game_file.h" +#include "util/string_utils.h" +#include "media/audio/audio_system.h" + +using namespace AGS::Common; + +#define ALLEGRO_KEYBOARD_HANDLER + +extern GameState play; +extern ExecutingScript*curscript; +extern int displayed_room; +extern int game_paused; +extern SpriteCache spriteset; +extern char gamefilenamebuf[200]; +extern GameSetup usetup; +extern unsigned int load_new_game; +extern int load_new_game_restore; +extern GameSetupStruct game; +extern ViewStruct*views; +extern RoomStatus*croom; +extern int gui_disabled_style; +extern RoomStruct thisroom; +extern int getloctype_index; +extern IGraphicsDriver *gfxDriver; +extern color palette[256]; + +#if AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_ANDROID +extern int psp_gfx_renderer; +#endif + +void GiveScore(int amnt) +{ + guis_need_update = 1; + play.score += amnt; + + if ((amnt > 0) && (play.score_sound >= 0)) + play_audio_clip_by_index(play.score_sound); + + run_on_event (GE_GOT_SCORE, RuntimeScriptValue().SetInt32(amnt)); +} + +void restart_game() { + can_run_delayed_command(); + if (inside_script) { + curscript->queue_action(ePSARestartGame, 0, "RestartGame"); + return; + } + try_restore_save(RESTART_POINT_SAVE_GAME_NUMBER); +} + +void RestoreGameSlot(int slnum) { + if (displayed_room < 0) + quit("!RestoreGameSlot: a game cannot be restored from within game_start"); + + can_run_delayed_command(); + if (inside_script) { + curscript->queue_action(ePSARestoreGame, slnum, "RestoreGameSlot"); + return; + } + try_restore_save(slnum); +} + +void DeleteSaveSlot (int slnum) { + String nametouse; + nametouse = get_save_game_path(slnum); + ::remove (nametouse); + if ((slnum >= 1) && (slnum <= MAXSAVEGAMES)) { + String thisname; + for (int i = MAXSAVEGAMES; i > slnum; i--) { + thisname = get_save_game_path(i); + if (Common::File::TestReadFile(thisname)) { + // Rename the highest save game to fill in the gap + rename (thisname, nametouse); + break; + } + } + + } +} + +void PauseGame() { + game_paused++; + debug_script_log("Game paused"); +} +void UnPauseGame() { + if (game_paused > 0) + game_paused--; + debug_script_log("Game UnPaused, pause level now %d", game_paused); +} + + +int IsGamePaused() { + if (game_paused>0) return 1; + return 0; +} + +int GetSaveSlotDescription(int slnum,char*desbuf) { + VALIDATE_STRING(desbuf); + String description; + if (read_savedgame_description(get_save_game_path(slnum), description)) + { + strcpy(desbuf, description); + return 1; + } + sprintf(desbuf,"INVALID SLOT %d", slnum); + return 0; +} + +int LoadSaveSlotScreenshot(int slnum, int width, int height) { + int gotSlot; + data_to_game_coords(&width, &height); + + if (!read_savedgame_screenshot(get_save_game_path(slnum), gotSlot)) + return 0; + + if (gotSlot == 0) + return 0; + + if ((game.SpriteInfos[gotSlot].Width == width) && (game.SpriteInfos[gotSlot].Height == height)) + return gotSlot; + + // resize the sprite to the requested size + Bitmap *newPic = BitmapHelper::CreateBitmap(width, height, spriteset[gotSlot]->GetColorDepth()); + newPic->StretchBlt(spriteset[gotSlot], + RectWH(0, 0, game.SpriteInfos[gotSlot].Width, game.SpriteInfos[gotSlot].Height), + RectWH(0, 0, width, height)); + + update_polled_stuff_if_runtime(); + + // replace the bitmap in the sprite set + free_dynamic_sprite(gotSlot); + add_dynamic_sprite(gotSlot, newPic); + + return gotSlot; +} + +void SetGlobalInt(int index,int valu) { + if ((index<0) | (index>=MAXGSVALUES)) + quit("!SetGlobalInt: invalid index"); + + if (play.globalscriptvars[index] != valu) { + debug_script_log("GlobalInt %d set to %d", index, valu); + } + + play.globalscriptvars[index]=valu; +} + + +int GetGlobalInt(int index) { + if ((index<0) | (index>=MAXGSVALUES)) + quit("!GetGlobalInt: invalid index"); + return play.globalscriptvars[index]; +} + +void SetGlobalString (int index, const char *newval) { + if ((index<0) | (index >= MAXGLOBALSTRINGS)) + quit("!SetGlobalString: invalid index"); + debug_script_log("GlobalString %d set to '%s'", index, newval); + strncpy(play.globalstrings[index], newval, MAX_MAXSTRLEN); + // truncate it to 200 chars, to be sure + play.globalstrings[index][MAX_MAXSTRLEN - 1] = 0; +} + +void GetGlobalString (int index, char *strval) { + if ((index<0) | (index >= MAXGLOBALSTRINGS)) + quit("!GetGlobalString: invalid index"); + strcpy (strval, play.globalstrings[index]); +} + +// TODO: refactor this method, and use same shared procedure at both normal stop/startup and in RunAGSGame +int RunAGSGame (const char *newgame, unsigned int mode, int data) { + + can_run_delayed_command(); + + int AllowedModes = RAGMODE_PRESERVEGLOBALINT | RAGMODE_LOADNOW; + + if ((mode & (~AllowedModes)) != 0) + quit("!RunAGSGame: mode value unknown"); + + if (editor_debugging_enabled) + { + quit("!RunAGSGame cannot be used while running the game from within the AGS Editor. You must build the game EXE and run it from there to use this function."); + } + + if ((mode & RAGMODE_LOADNOW) == 0) { + // need to copy, since the script gets destroyed + get_install_dir_path(gamefilenamebuf, newgame); + ResPaths.GamePak.Path = gamefilenamebuf; + ResPaths.GamePak.Name = get_filename(gamefilenamebuf); + play.takeover_data = data; + load_new_game_restore = -1; + + if (inside_script) { + curscript->queue_action(ePSARunAGSGame, mode | RAGMODE_LOADNOW, "RunAGSGame"); + ccInstance::GetCurrentInstance()->Abort(); + } + else + load_new_game = mode | RAGMODE_LOADNOW; + + return 0; + } + + int ee; + + unload_old_room(); + displayed_room = -10; + + save_config_file(); // save current user config in case engine fails to run new game + unload_game_file(); + + // Adjust config (NOTE: normally, RunAGSGame would need a redesign to allow separate config etc per each game) + usetup.translation = ""; // reset to default, prevent from trying translation file of game A in game B + + if (Common::AssetManager::SetDataFile(ResPaths.GamePak.Path) != Common::kAssetNoError) + quitprintf("!RunAGSGame: unable to load new game file '%s'", ResPaths.GamePak.Path.GetCStr()); + + show_preload(); + + HError err = load_game_file(); + if (!err) + quitprintf("!RunAGSGame: error loading new game file:\n%s", err->FullMessage().GetCStr()); + + spriteset.Reset(); + err = spriteset.InitFile(SpriteCache::DefaultSpriteFileName, SpriteCache::DefaultSpriteIndexName); + if (!err) + quitprintf("!RunAGSGame: error loading new sprites:\n%s", err->FullMessage().GetCStr()); + + if ((mode & RAGMODE_PRESERVEGLOBALINT) == 0) { + // reset GlobalInts + for (ee = 0; ee < MAXGSVALUES; ee++) + play.globalscriptvars[ee] = 0; + } + + engine_init_game_settings(); + play.screen_is_faded_out = 1; + + if (load_new_game_restore >= 0) { + try_restore_save(load_new_game_restore); + load_new_game_restore = -1; + } + else + start_game(); + + return 0; +} + +int GetGameParameter (int parm, int data1, int data2, int data3) { + switch (parm) { + case GP_SPRITEWIDTH: + return Game_GetSpriteWidth(data1); + case GP_SPRITEHEIGHT: + return Game_GetSpriteHeight(data1); + case GP_NUMLOOPS: + return Game_GetLoopCountForView(data1); + case GP_NUMFRAMES: + return Game_GetFrameCountForLoop(data1, data2); + case GP_FRAMESPEED: + case GP_FRAMEIMAGE: + case GP_FRAMESOUND: + case GP_ISFRAMEFLIPPED: + { + if ((data1 < 1) || (data1 > game.numviews)) { + quitprintf("!GetGameParameter: invalid view specified (v: %d, l: %d, f: %d)", data1, data2, data3); + } + if ((data2 < 0) || (data2 >= views[data1 - 1].numLoops)) { + quitprintf("!GetGameParameter: invalid loop specified (v: %d, l: %d, f: %d)", data1, data2, data3); + } + if ((data3 < 0) || (data3 >= views[data1 - 1].loops[data2].numFrames)) { + quitprintf("!GetGameParameter: invalid frame specified (v: %d, l: %d, f: %d)", data1, data2, data3); + } + + ViewFrame *pvf = &views[data1 - 1].loops[data2].frames[data3]; + + if (parm == GP_FRAMESPEED) + return pvf->speed; + else if (parm == GP_FRAMEIMAGE) + return pvf->pic; + else if (parm == GP_FRAMESOUND) + return get_old_style_number_for_sound(pvf->sound); + else if (parm == GP_ISFRAMEFLIPPED) + return (pvf->flags & VFLG_FLIPSPRITE) ? 1 : 0; + else + quit("GetGameParameter internal error"); + } + case GP_ISRUNNEXTLOOP: + return Game_GetRunNextSettingForLoop(data1, data2); + case GP_NUMGUIS: + return game.numgui; + case GP_NUMOBJECTS: + return croom->numobj; + case GP_NUMCHARACTERS: + return game.numcharacters; + case GP_NUMINVITEMS: + return game.numinvitems; + default: + quit("!GetGameParameter: unknown parameter specified"); + } + return 0; +} + +void QuitGame(int dialog) { + if (dialog) { + int rcode; + setup_for_dialog(); + rcode=quitdialog(); + restore_after_dialog(); + if (rcode==0) return; + } + quit("|You have exited."); +} + + + + +void SetRestartPoint() { + save_game(RESTART_POINT_SAVE_GAME_NUMBER, "Restart Game Auto-Save"); +} + + + +void SetGameSpeed(int newspd) { + newspd += play.game_speed_modifier; + if (newspd>1000) newspd=1000; + if (newspd<10) newspd=10; + set_game_speed(newspd); + debug_script_log("Game speed set to %d", newspd); +} + +int GetGameSpeed() { + return ::lround(get_current_fps()) - play.game_speed_modifier; +} + +int SetGameOption (int opt, int setting) { + if (((opt < 1) || (opt > OPT_HIGHESTOPTION)) && (opt != OPT_LIPSYNCTEXT)) + quit("!SetGameOption: invalid option specified"); + + if (opt == OPT_ANTIGLIDE) + { + for (int i = 0; i < game.numcharacters; i++) + { + if (setting) + game.chars[i].flags |= CHF_ANTIGLIDE; + else + game.chars[i].flags &= ~CHF_ANTIGLIDE; + } + } + + if ((opt == OPT_CROSSFADEMUSIC) && (game.audioClipTypes.size() > AUDIOTYPE_LEGACY_MUSIC)) + { + // legacy compatibility -- changing crossfade speed here also + // updates the new audio clip type style + game.audioClipTypes[AUDIOTYPE_LEGACY_MUSIC].crossfadeSpeed = setting; + } + + int oldval = game.options[opt]; + game.options[opt] = setting; + + if (opt == OPT_DUPLICATEINV) + update_invorder(); + else if (opt == OPT_DISABLEOFF) + gui_disabled_style = convert_gui_disabled_style(game.options[OPT_DISABLEOFF]); + else if (opt == OPT_PORTRAITSIDE) { + if (setting == 0) // set back to Left + play.swap_portrait_side = 0; + } + + return oldval; +} + +int GetGameOption (int opt) { + if (((opt < 1) || (opt > OPT_HIGHESTOPTION)) && (opt != OPT_LIPSYNCTEXT)) + quit("!GetGameOption: invalid option specified"); + + return game.options[opt]; +} + +void SkipUntilCharacterStops(int cc) { + if (!is_valid_character(cc)) + quit("!SkipUntilCharacterStops: invalid character specified"); + if (game.chars[cc].room!=displayed_room) + quit("!SkipUntilCharacterStops: specified character not in current room"); + + // if they are not currently moving, do nothing + if (!game.chars[cc].walking) + return; + + if (is_in_cutscene()) + quit("!SkipUntilCharacterStops: cannot be used within a cutscene"); + + initialize_skippable_cutscene(); + play.fast_forward = 2; + play.skip_until_char_stops = cc; +} + +void EndSkippingUntilCharStops() { + // not currently skipping, so ignore + if (play.skip_until_char_stops < 0) + return; + + stop_fast_forwarding(); + play.skip_until_char_stops = -1; +} + +void StartCutscene (int skipwith) { + static ScriptPosition last_cutscene_script_pos; + + if (is_in_cutscene()) { + quitprintf("!StartCutscene: already in a cutscene; previous started in \"%s\", line %d", + last_cutscene_script_pos.Section.GetCStr(), last_cutscene_script_pos.Line); + } + + if ((skipwith < 1) || (skipwith > 6)) + quit("!StartCutscene: invalid argument, must be 1 to 5."); + + get_script_position(last_cutscene_script_pos); + + // make sure they can't be skipping and cutsceneing at the same time + EndSkippingUntilCharStops(); + + play.in_cutscene = skipwith; + initialize_skippable_cutscene(); +} + +void SkipCutscene() +{ + if (is_in_cutscene()) + start_skipping_cutscene(); +} + +int EndCutscene () { + if (!is_in_cutscene()) + quit("!EndCutscene: not in a cutscene"); + + int retval = play.fast_forward; + play.in_cutscene = 0; + // Stop it fast-forwarding + stop_fast_forwarding(); + + // make sure that the screen redraws + invalidate_screen(); + + // Return whether the player skipped it + return retval; +} + +void sc_inputbox(const char*msg,char*bufr) { + VALIDATE_STRING(bufr); + setup_for_dialog(); + enterstringwindow(get_translation(msg),bufr); + restore_after_dialog(); +} + +// GetLocationType exported function - just call through +// to the main function with default 0 +int GetLocationType(int xxx,int yyy) { + return __GetLocationType(xxx, yyy, 0); +} + +void SaveCursorForLocationChange() { + // update the current location name + char tempo[100]; + GetLocationName(game_to_data_coord(mousex), game_to_data_coord(mousey), tempo); + + if (play.get_loc_name_save_cursor != play.get_loc_name_last_time) { + play.get_loc_name_save_cursor = play.get_loc_name_last_time; + play.restore_cursor_mode_to = GetCursorMode(); + play.restore_cursor_image_to = GetMouseCursor(); + debug_script_log("Saving mouse: mode %d cursor %d", play.restore_cursor_mode_to, play.restore_cursor_image_to); + } +} + +void GetLocationName(int xxx,int yyy,char*tempo) { + if (displayed_room < 0) + quit("!GetLocationName: no room has been loaded"); + + VALIDATE_STRING(tempo); + + tempo[0] = 0; + + if (GetGUIAt(xxx, yyy) >= 0) { + int mover = GetInvAt (xxx, yyy); + if (mover > 0) { + if (play.get_loc_name_last_time != 1000 + mover) + guis_need_update = 1; + play.get_loc_name_last_time = 1000 + mover; + strcpy(tempo,get_translation(game.invinfo[mover].name)); + } + else if ((play.get_loc_name_last_time > 1000) && (play.get_loc_name_last_time < 1000 + MAX_INV)) { + // no longer selecting an item + guis_need_update = 1; + play.get_loc_name_last_time = -1; + } + return; + } + + int loctype = GetLocationType(xxx, yyy); // GetLocationType takes screen coords + VpPoint vpt = play.ScreenToRoomDivDown(xxx, yyy); + if (vpt.second < 0) + return; + xxx = vpt.first.X; + yyy = vpt.first.Y; + if ((xxx>=thisroom.Width) | (xxx<0) | (yyy<0) | (yyy>=thisroom.Height)) + return; + + int onhs,aa; + if (loctype == 0) { + if (play.get_loc_name_last_time != 0) { + play.get_loc_name_last_time = 0; + guis_need_update = 1; + } + return; + } + + // on character + if (loctype == LOCTYPE_CHAR) { + onhs = getloctype_index; + strcpy(tempo,get_translation(game.chars[onhs].name)); + if (play.get_loc_name_last_time != 2000+onhs) + guis_need_update = 1; + play.get_loc_name_last_time = 2000+onhs; + return; + } + // on object + if (loctype == LOCTYPE_OBJ) { + aa = getloctype_index; + strcpy(tempo,get_translation(thisroom.Objects[aa].Name)); + // Compatibility: < 3.1.1 games returned space for nameless object + // (presumably was a bug, but fixing it affected certain games behavior) + if (loaded_game_file_version < kGameVersion_311 && tempo[0] == 0) { + tempo[0] = ' '; + tempo[1] = 0; + } + if (play.get_loc_name_last_time != 3000+aa) + guis_need_update = 1; + play.get_loc_name_last_time = 3000+aa; + return; + } + onhs = getloctype_index; + if (onhs>0) strcpy(tempo,get_translation(thisroom.Hotspots[onhs].Name)); + if (play.get_loc_name_last_time != onhs) + guis_need_update = 1; + play.get_loc_name_last_time = onhs; +} + +int IsKeyPressed (int keycode) { +#ifdef ALLEGRO_KEYBOARD_HANDLER + if (keyboard_needs_poll()) + poll_keyboard(); + + switch(keycode) { + case eAGSKeyCodeBackspace: return ags_iskeypressed(__allegro_KEY_BACKSPACE); break; + case eAGSKeyCodeTab: return ags_iskeypressed(__allegro_KEY_TAB); break; + case eAGSKeyCodeReturn: return ags_iskeypressed(__allegro_KEY_ENTER) || ags_iskeypressed(__allegro_KEY_ENTER_PAD); break; + case eAGSKeyCodeEscape: return ags_iskeypressed(__allegro_KEY_ESC); break; + case eAGSKeyCodeSpace: return ags_iskeypressed(__allegro_KEY_SPACE); break; + case eAGSKeyCodeSingleQuote: return ags_iskeypressed(__allegro_KEY_QUOTE); break; + case eAGSKeyCodeComma: return ags_iskeypressed(__allegro_KEY_COMMA); break; + case eAGSKeyCodePeriod: return ags_iskeypressed(__allegro_KEY_STOP); break; + case eAGSKeyCodeForwardSlash: return ags_iskeypressed(__allegro_KEY_SLASH) || ags_iskeypressed(__allegro_KEY_SLASH_PAD); break; + case eAGSKeyCodeBackSlash: return ags_iskeypressed(__allegro_KEY_BACKSLASH) || ags_iskeypressed(__allegro_KEY_BACKSLASH2); break; + case eAGSKeyCodeSemiColon: return ags_iskeypressed(__allegro_KEY_SEMICOLON); break; + case eAGSKeyCodeEquals: return ags_iskeypressed(__allegro_KEY_EQUALS) || ags_iskeypressed(__allegro_KEY_EQUALS_PAD); break; + case eAGSKeyCodeOpenBracket: return ags_iskeypressed(__allegro_KEY_OPENBRACE); break; + case eAGSKeyCodeCloseBracket: return ags_iskeypressed(__allegro_KEY_CLOSEBRACE); break; + // NOTE: we're treating EQUALS like PLUS, even though it is only available shifted. + case eAGSKeyCodePlus: return ags_iskeypressed(__allegro_KEY_EQUALS) || ags_iskeypressed(__allegro_KEY_PLUS_PAD); break; + case eAGSKeyCodeHyphen: return ags_iskeypressed(__allegro_KEY_MINUS) || ags_iskeypressed(__allegro_KEY_MINUS_PAD); break; + + // non-shifted versions of keys + case eAGSKeyCodeColon: return ags_iskeypressed(__allegro_KEY_COLON) || ags_iskeypressed(__allegro_KEY_COLON2); break; + case eAGSKeyCodeAsterisk: return ags_iskeypressed(__allegro_KEY_ASTERISK); break; + case eAGSKeyCodeAt: return ags_iskeypressed(__allegro_KEY_AT); break; + + case eAGSKeyCode0: return ags_iskeypressed(__allegro_KEY_0); break; + case eAGSKeyCode1: return ags_iskeypressed(__allegro_KEY_1); break; + case eAGSKeyCode2: return ags_iskeypressed(__allegro_KEY_2); break; + case eAGSKeyCode3: return ags_iskeypressed(__allegro_KEY_3); break; + case eAGSKeyCode4: return ags_iskeypressed(__allegro_KEY_4); break; + case eAGSKeyCode5: return ags_iskeypressed(__allegro_KEY_5); break; + case eAGSKeyCode6: return ags_iskeypressed(__allegro_KEY_6); break; + case eAGSKeyCode7: return ags_iskeypressed(__allegro_KEY_7); break; + case eAGSKeyCode8: return ags_iskeypressed(__allegro_KEY_8); break; + case eAGSKeyCode9: return ags_iskeypressed(__allegro_KEY_9); break; + + case eAGSKeyCodeA: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('A')); break; + case eAGSKeyCodeB: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('B')); break; + case eAGSKeyCodeC: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('C')); break; + case eAGSKeyCodeD: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('D')); break; + case eAGSKeyCodeE: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('E')); break; + case eAGSKeyCodeF: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('F')); break; + case eAGSKeyCodeG: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('G')); break; + case eAGSKeyCodeH: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('H')); break; + case eAGSKeyCodeI: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('I')); break; + case eAGSKeyCodeJ: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('J')); break; + case eAGSKeyCodeK: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('K')); break; + case eAGSKeyCodeL: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('L')); break; + case eAGSKeyCodeM: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('M')); break; + case eAGSKeyCodeN: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('N')); break; + case eAGSKeyCodeO: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('O')); break; + case eAGSKeyCodeP: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('P')); break; + case eAGSKeyCodeQ: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('Q')); break; + case eAGSKeyCodeR: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('R')); break; + case eAGSKeyCodeS: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('S')); break; + case eAGSKeyCodeT: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('T')); break; + case eAGSKeyCodeU: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('U')); break; + case eAGSKeyCodeV: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('V')); break; + case eAGSKeyCodeW: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('W')); break; + case eAGSKeyCodeX: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('X')); break; + case eAGSKeyCodeY: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('Y')); break; + case eAGSKeyCodeZ: return ags_iskeypressed(platform->ConvertKeycodeToScanCode('Z')); break; + + case eAGSKeyCodeF1: return ags_iskeypressed(__allegro_KEY_F1); break; + case eAGSKeyCodeF2: return ags_iskeypressed(__allegro_KEY_F2); break; + case eAGSKeyCodeF3: return ags_iskeypressed(__allegro_KEY_F3); break; + case eAGSKeyCodeF4: return ags_iskeypressed(__allegro_KEY_F4); break; + case eAGSKeyCodeF5: return ags_iskeypressed(__allegro_KEY_F5); break; + case eAGSKeyCodeF6: return ags_iskeypressed(__allegro_KEY_F6); break; + case eAGSKeyCodeF7: return ags_iskeypressed(__allegro_KEY_F7); break; + case eAGSKeyCodeF8: return ags_iskeypressed(__allegro_KEY_F8); break; + case eAGSKeyCodeF9: return ags_iskeypressed(__allegro_KEY_F9); break; + case eAGSKeyCodeF10: return ags_iskeypressed(__allegro_KEY_F10); break; + case eAGSKeyCodeF11: return ags_iskeypressed(__allegro_KEY_F11); break; + case eAGSKeyCodeF12: return ags_iskeypressed(__allegro_KEY_F12); break; + + case eAGSKeyCodeHome: return ags_iskeypressed(__allegro_KEY_HOME) || ags_iskeypressed(__allegro_KEY_7_PAD); break; + case eAGSKeyCodeUpArrow: return ags_iskeypressed(__allegro_KEY_UP) || ags_iskeypressed(__allegro_KEY_8_PAD); break; + case eAGSKeyCodePageUp: return ags_iskeypressed(__allegro_KEY_PGUP) || ags_iskeypressed(__allegro_KEY_9_PAD); break; + case eAGSKeyCodeLeftArrow: return ags_iskeypressed(__allegro_KEY_LEFT) || ags_iskeypressed(__allegro_KEY_4_PAD); break; + case eAGSKeyCodeNumPad5: return ags_iskeypressed(__allegro_KEY_5_PAD); break; + case eAGSKeyCodeRightArrow: return ags_iskeypressed(__allegro_KEY_RIGHT) || ags_iskeypressed(__allegro_KEY_6_PAD); break; + case eAGSKeyCodeEnd: return ags_iskeypressed(__allegro_KEY_END) || ags_iskeypressed(__allegro_KEY_1_PAD); break; + case eAGSKeyCodeDownArrow: return ags_iskeypressed(__allegro_KEY_DOWN) || ags_iskeypressed(__allegro_KEY_2_PAD); break; + case eAGSKeyCodePageDown: return ags_iskeypressed(__allegro_KEY_PGDN) || ags_iskeypressed(__allegro_KEY_3_PAD); break; + case eAGSKeyCodeInsert: return ags_iskeypressed(__allegro_KEY_INSERT) || ags_iskeypressed(__allegro_KEY_0_PAD); break; + case eAGSKeyCodeDelete: return ags_iskeypressed(__allegro_KEY_DEL) || ags_iskeypressed(__allegro_KEY_DEL_PAD); break; + + // These keys are not defined in the eAGSKey enum but are in the manual + // https://adventuregamestudio.github.io/ags-manual/ASCIIcodes.html + + case 403: return ags_iskeypressed(__allegro_KEY_LSHIFT); break; + case 404: return ags_iskeypressed(__allegro_KEY_RSHIFT); break; + case 405: return ags_iskeypressed(__allegro_KEY_LCONTROL); break; + case 406: return ags_iskeypressed(__allegro_KEY_RCONTROL); break; + case 407: return ags_iskeypressed(__allegro_KEY_ALT); break; + + // (noted here for interest) + // The following are the AGS_EXT_KEY_SHIFT, derived from applying arithmetic to the original keycodes. + // These do not have a corresponding ags key enum, do not appear in the manual and may not be accessible because of OS contraints. + + case 392: return ags_iskeypressed(__allegro_KEY_PRTSCR); break; + case 393: return ags_iskeypressed(__allegro_KEY_PAUSE); break; + case 394: return ags_iskeypressed(__allegro_KEY_ABNT_C1); break; // The ABNT_C1 (Brazilian) key + case 395: return ags_iskeypressed(__allegro_KEY_YEN); break; + case 396: return ags_iskeypressed(__allegro_KEY_KANA); break; + case 397: return ags_iskeypressed(__allegro_KEY_CONVERT); break; + case 398: return ags_iskeypressed(__allegro_KEY_NOCONVERT); break; + case 400: return ags_iskeypressed(__allegro_KEY_CIRCUMFLEX); break; + case 402: return ags_iskeypressed(__allegro_KEY_KANJI); break; + case 420: return ags_iskeypressed(__allegro_KEY_ALTGR); break; + case 421: return ags_iskeypressed(__allegro_KEY_LWIN); break; + case 422: return ags_iskeypressed(__allegro_KEY_RWIN); break; + case 423: return ags_iskeypressed(__allegro_KEY_MENU); break; + case 424: return ags_iskeypressed(__allegro_KEY_SCRLOCK); break; + case 425: return ags_iskeypressed(__allegro_KEY_NUMLOCK); break; + case 426: return ags_iskeypressed(__allegro_KEY_CAPSLOCK); break; + + // Allegro4 keys that were never supported: + // __allegro_KEY_COMMAND + // __allegro_KEY_TILDE + // __allegro_KEY_BACKQUOTE + + default: + // Remaining Allegro4 keycodes are offset by AGS_EXT_KEY_SHIFT + if (keycode >= AGS_EXT_KEY_SHIFT) { + if (ags_iskeypressed(keycode - AGS_EXT_KEY_SHIFT)) { return 1; } + } + debug_script_log("IsKeyPressed: unsupported keycode %d", keycode); + return 0; + } +#else + // old allegro version + quit("allegro keyboard handler not in use??"); +#endif +} + +int SaveScreenShot(const char*namm) { + String fileName; + String svg_dir = get_save_game_directory(); + + if (strchr(namm,'.') == nullptr) + fileName.Format("%s%s.bmp", svg_dir.GetCStr(), namm); + else + fileName.Format("%s%s", svg_dir.GetCStr(), namm); + + Bitmap *buffer = CopyScreenIntoBitmap(play.GetMainViewport().GetWidth(), play.GetMainViewport().GetHeight()); + if (!buffer->SaveToFile(fileName, palette) != 0) + { + delete buffer; + return 0; + } + delete buffer; + return 1; // successful +} + +void SetMultitasking (int mode) { + if ((mode < 0) | (mode > 1)) + quit("!SetMultitasking: invalid mode parameter"); + + if (usetup.override_multitasking >= 0) + { + mode = usetup.override_multitasking; + } + + // Don't allow background running if full screen + if ((mode == 1) && (!scsystem.windowed)) + mode = 0; + + if (mode == 0) { + if (set_display_switch_mode(SWITCH_PAUSE) == -1) + set_display_switch_mode(SWITCH_AMNESIA); + // install callbacks to stop the sound when switching away + set_display_switch_callback(SWITCH_IN, display_switch_in_resume); + set_display_switch_callback(SWITCH_OUT, display_switch_out_suspend); + } + else { + if (set_display_switch_mode (SWITCH_BACKGROUND) == -1) + set_display_switch_mode(SWITCH_BACKAMNESIA); + set_display_switch_callback(SWITCH_IN, display_switch_in); + set_display_switch_callback(SWITCH_OUT, display_switch_out); + } +} + +extern int getloctype_throughgui, getloctype_index; + +void RoomProcessClick(int xx,int yy,int mood) { + getloctype_throughgui = 1; + int loctype = GetLocationType (xx, yy); + VpPoint vpt = play.ScreenToRoomDivDown(xx, yy); + if (vpt.second < 0) + return; + xx = vpt.first.X; + yy = vpt.first.Y; + + if ((mood==MODE_WALK) && (game.options[OPT_NOWALKMODE]==0)) { + int hsnum=get_hotspot_at(xx,yy); + if (hsnum<1) ; + else if (thisroom.Hotspots[hsnum].WalkTo.X<1) ; + else if (play.auto_use_walkto_points == 0) ; + else { + xx=thisroom.Hotspots[hsnum].WalkTo.X; + yy=thisroom.Hotspots[hsnum].WalkTo.Y; + debug_script_log("Move to walk-to point hotspot %d", hsnum); + } + walk_character(game.playercharacter,xx,yy,0, true); + return; + } + play.usedmode=mood; + + if (loctype == 0) { + // click on nothing -> hotspot 0 + getloctype_index = 0; + loctype = LOCTYPE_HOTSPOT; + } + + if (loctype == LOCTYPE_CHAR) { + if (check_click_on_character(xx,yy,mood)) return; + } + else if (loctype == LOCTYPE_OBJ) { + if (check_click_on_object(xx,yy,mood)) return; + } + else if (loctype == LOCTYPE_HOTSPOT) + RunHotspotInteraction (getloctype_index, mood); +} + +int IsInteractionAvailable (int xx,int yy,int mood) { + getloctype_throughgui = 1; + int loctype = GetLocationType (xx, yy); + VpPoint vpt = play.ScreenToRoomDivDown(xx, yy); + if (vpt.second < 0) + return 0; + xx = vpt.first.X; + yy = vpt.first.Y; + + // You can always walk places + if ((mood==MODE_WALK) && (game.options[OPT_NOWALKMODE]==0)) + return 1; + + play.check_interaction_only = 1; + + if (loctype == 0) { + // click on nothing -> hotspot 0 + getloctype_index = 0; + loctype = LOCTYPE_HOTSPOT; + } + + if (loctype == LOCTYPE_CHAR) { + check_click_on_character(xx,yy,mood); + } + else if (loctype == LOCTYPE_OBJ) { + check_click_on_object(xx,yy,mood); + } + else if (loctype == LOCTYPE_HOTSPOT) + RunHotspotInteraction (getloctype_index, mood); + + int ciwas = play.check_interaction_only; + play.check_interaction_only = 0; + + if (ciwas == 2) + return 1; + + return 0; +} + +void GetMessageText (int msg, char *buffer) { + VALIDATE_STRING(buffer); + get_message_text (msg, buffer, 0); +} + +void SetSpeechFont (int fontnum) { + if ((fontnum < 0) || (fontnum >= game.numfonts)) + quit("!SetSpeechFont: invalid font number."); + play.speech_font = fontnum; +} + +void SetNormalFont (int fontnum) { + if ((fontnum < 0) || (fontnum >= game.numfonts)) + quit("!SetNormalFont: invalid font number."); + play.normal_font = fontnum; +} + +void _sc_AbortGame(const char* text) { + char displbuf[STD_BUFFER_SIZE] = "!?"; + snprintf(&displbuf[2], STD_BUFFER_SIZE - 3, "%s", text); + quit(displbuf); +} + +int GetGraphicalVariable (const char *varName) { + InteractionVariable *theVar = FindGraphicalVariable(varName); + if (theVar == nullptr) { + quitprintf("!GetGraphicalVariable: interaction variable '%s' not found", varName); + return 0; + } + return theVar->Value; +} + +void SetGraphicalVariable (const char *varName, int p_value) { + InteractionVariable *theVar = FindGraphicalVariable(varName); + if (theVar == nullptr) { + quitprintf("!SetGraphicalVariable: interaction variable '%s' not found", varName); + } + else + theVar->Value = p_value; +} + +int WaitImpl(int skip_type, int nloops) +{ + play.wait_counter = nloops; + play.wait_skipped_by = SKIP_AUTOTIMER; // we set timer flag by default to simplify that case + play.wait_skipped_by_data = 0; + play.key_skip_wait = skip_type; + + GameLoopUntilValueIsZero(&play.wait_counter); + + if (game.options[OPT_BASESCRIPTAPI] < kScriptAPI_v351) + { + // < 3.5.1 return 1 is skipped by user input, otherwise 0 + return (play.wait_skipped_by & (SKIP_KEYPRESS | SKIP_MOUSECLICK) != 0) ? 1 : 0; + } + // >= 3.5.1 return positive keycode, negative mouse button code, or 0 as time-out + switch (play.wait_skipped_by) + { + case SKIP_KEYPRESS: return play.wait_skipped_by_data; + case SKIP_MOUSECLICK: return -(play.wait_skipped_by_data + 1); // convert to 1-based code and negate + default: return 0; + } +} + +void scrWait(int nloops) { + WaitImpl(SKIP_AUTOTIMER, nloops); +} + +int WaitKey(int nloops) { + return WaitImpl(SKIP_KEYPRESS | SKIP_AUTOTIMER, nloops); +} + +int WaitMouse(int nloops) { + return WaitImpl(SKIP_MOUSECLICK | SKIP_AUTOTIMER, nloops); +} + +int WaitMouseKey(int nloops) { + return WaitImpl(SKIP_KEYPRESS | SKIP_MOUSECLICK | SKIP_AUTOTIMER, nloops); +} + +void SkipWait() { + play.wait_counter = 0; +} diff --git a/engines/ags/engine/ac/global_game.h b/engines/ags/engine/ac/global_game.h new file mode 100644 index 00000000000..25e471a7334 --- /dev/null +++ b/engines/ags/engine/ac/global_game.h @@ -0,0 +1,88 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALGAME_H +#define __AGS_EE_AC__GLOBALGAME_H + +#include "util/string.h" +using namespace AGS; // FIXME later + +void GiveScore(int amnt); +void restart_game(); +void RestoreGameSlot(int slnum); +void DeleteSaveSlot (int slnum); +int GetSaveSlotDescription(int slnum,char*desbuf); +int LoadSaveSlotScreenshot(int slnum, int width, int height); +void PauseGame(); +void UnPauseGame(); +int IsGamePaused(); +void SetGlobalInt(int index,int valu); +int GetGlobalInt(int index); +void SetGlobalString (int index, const char *newval); +void GetGlobalString (int index, char *strval); +int RunAGSGame (const char *newgame, unsigned int mode, int data); +int GetGameParameter (int parm, int data1, int data2, int data3); +void QuitGame(int dialog); +void SetRestartPoint(); +void SetGameSpeed(int newspd); +int GetGameSpeed(); +int SetGameOption (int opt, int setting); +int GetGameOption (int opt); + +void SkipUntilCharacterStops(int cc); +void EndSkippingUntilCharStops(); +// skipwith decides how it can be skipped: +// 1 = ESC only +// 2 = any key +// 3 = mouse button +// 4 = mouse button or any key +// 5 = right click or ESC only +void StartCutscene (int skipwith); +int EndCutscene (); +// Tell the game to skip current cutscene +void SkipCutscene(); + +void sc_inputbox(const char*msg,char*bufr); + +int GetLocationType(int xxx,int yyy); +void SaveCursorForLocationChange(); +void GetLocationName(int xxx,int yyy,char*tempo); + +int IsKeyPressed (int keycode); + +int SaveScreenShot(const char*namm); +void SetMultitasking (int mode); + +void RoomProcessClick(int xx,int yy,int mood); +int IsInteractionAvailable (int xx,int yy,int mood); + +void GetMessageText (int msg, char *buffer); + +void SetSpeechFont (int fontnum); +void SetNormalFont (int fontnum); + +void _sc_AbortGame(const char* text); + +int GetGraphicalVariable (const char *varName); +void SetGraphicalVariable (const char *varName, int p_value); +void scrWait(int nloops); +int WaitKey(int nloops); +int WaitMouse(int nloops); +int WaitMouseKey(int nloops); +void SkipWait(); + +#endif // __AGS_EE_AC__GLOBALGAME_H diff --git a/engines/ags/engine/ac/global_gui.cpp b/engines/ags/engine/ac/global_gui.cpp new file mode 100644 index 00000000000..3c8e6250ac3 --- /dev/null +++ b/engines/ags/engine/ac/global_gui.cpp @@ -0,0 +1,256 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_game.h" +#include "ac/global_gui.h" +#include "ac/gui.h" +#include "ac/guicontrol.h" +#include "ac/mouse.h" +#include "ac/string.h" +#include "debug/debug_log.h" +#include "font/fonts.h" +#include "gui/guimain.h" +#include "script/runtimescriptvalue.h" +#include "util/string_compat.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; +extern ScriptGUI *scrGui; + +int IsGUIOn (int guinum) { + if ((guinum < 0) || (guinum >= game.numgui)) + quit("!IsGUIOn: invalid GUI number specified"); + return (guis[guinum].IsDisplayed()) ? 1 : 0; +} + +// This is an internal script function, and is undocumented. +// It is used by the editor's automatic macro generation. +int FindGUIID (const char* GUIName) { + for (int ii = 0; ii < game.numgui; ii++) { + if (guis[ii].Name.IsEmpty()) + continue; + if (strcmp(guis[ii].Name, GUIName) == 0) + return ii; + if ((guis[ii].Name[0u] == 'g') && (ags_stricmp(guis[ii].Name.GetCStr() + 1, GUIName) == 0)) + return ii; + } + quit("FindGUIID: No matching GUI found: GUI may have been deleted"); + return -1; +} + +void InterfaceOn(int ifn) { + if ((ifn<0) | (ifn>=game.numgui)) + quit("!GUIOn: invalid GUI specified"); + + EndSkippingUntilCharStops(); + + if (guis[ifn].IsVisible()) { + debug_script_log("GUIOn(%d) ignored (already on)", ifn); + return; + } + guis_need_update = 1; + guis[ifn].SetVisible(true); + debug_script_log("GUI %d turned on", ifn); + // modal interface + if (guis[ifn].PopupStyle==kGUIPopupModal) PauseGame(); + // clear the cached mouse position + guis[ifn].OnControlPositionChanged(); + guis[ifn].Poll(); +} + +void InterfaceOff(int ifn) { + if ((ifn<0) | (ifn>=game.numgui)) quit("!GUIOff: invalid GUI specified"); + if (!guis[ifn].IsVisible()) { + debug_script_log("GUIOff(%d) ignored (already off)", ifn); + return; + } + debug_script_log("GUI %d turned off", ifn); + guis[ifn].SetVisible(false); + if (guis[ifn].MouseOverCtrl>=0) { + // Make sure that the overpic is turned off when the GUI goes off + guis[ifn].GetControl(guis[ifn].MouseOverCtrl)->OnMouseLeave(); + guis[ifn].MouseOverCtrl = -1; + } + guis[ifn].OnControlPositionChanged(); + guis_need_update = 1; + // modal interface + if (guis[ifn].PopupStyle==kGUIPopupModal) UnPauseGame(); +} + +void SetGUIObjectEnabled(int guin, int objn, int enabled) { + if ((guin<0) || (guin>=game.numgui)) + quit("!SetGUIObjectEnabled: invalid GUI number"); + if ((objn<0) || (objn>=guis[guin].GetControlCount())) + quit("!SetGUIObjectEnabled: invalid object number"); + + GUIControl_SetEnabled(guis[guin].GetControl(objn), enabled); +} + +void SetGUIObjectPosition(int guin, int objn, int xx, int yy) { + if ((guin<0) || (guin>=game.numgui)) + quit("!SetGUIObjectPosition: invalid GUI number"); + if ((objn<0) || (objn>=guis[guin].GetControlCount())) + quit("!SetGUIObjectPosition: invalid object number"); + + GUIControl_SetPosition(guis[guin].GetControl(objn), xx, yy); +} + +void SetGUIPosition(int ifn,int xx,int yy) { + if ((ifn<0) || (ifn>=game.numgui)) + quit("!SetGUIPosition: invalid GUI number"); + + GUI_SetPosition(&scrGui[ifn], xx, yy); +} + +void SetGUIObjectSize(int ifn, int objn, int newwid, int newhit) { + if ((ifn<0) || (ifn>=game.numgui)) + quit("!SetGUIObjectSize: invalid GUI number"); + + if ((objn<0) || (objn >= guis[ifn].GetControlCount())) + quit("!SetGUIObjectSize: invalid object number"); + + GUIControl_SetSize(guis[ifn].GetControl(objn), newwid, newhit); +} + +void SetGUISize (int ifn, int widd, int hitt) { + if ((ifn<0) || (ifn>=game.numgui)) + quit("!SetGUISize: invalid GUI number"); + + GUI_SetSize(&scrGui[ifn], widd, hitt); +} + +void SetGUIZOrder(int guin, int z) { + if ((guin<0) || (guin>=game.numgui)) + quit("!SetGUIZOrder: invalid GUI number"); + + GUI_SetZOrder(&scrGui[guin], z); +} + +void SetGUIClickable(int guin, int clickable) { + if ((guin<0) || (guin>=game.numgui)) + quit("!SetGUIClickable: invalid GUI number"); + + GUI_SetClickable(&scrGui[guin], clickable); +} + +// pass trans=0 for fully solid, trans=100 for fully transparent +void SetGUITransparency(int ifn, int trans) { + if ((ifn < 0) | (ifn >= game.numgui)) + quit("!SetGUITransparency: invalid GUI number"); + + GUI_SetTransparency(&scrGui[ifn], trans); +} + +void CentreGUI (int ifn) { + if ((ifn<0) | (ifn>=game.numgui)) + quit("!CentreGUI: invalid GUI number"); + + GUI_Centre(&scrGui[ifn]); +} + +int GetTextWidth(const char *text, int fontnum) { + VALIDATE_STRING(text); + if ((fontnum < 0) || (fontnum >= game.numfonts)) + quit("!GetTextWidth: invalid font number."); + + return game_to_data_coord(wgettextwidth_compensate(text, fontnum)); +} + +int GetTextHeight(const char *text, int fontnum, int width) { + VALIDATE_STRING(text); + if ((fontnum < 0) || (fontnum >= game.numfonts)) + quit("!GetTextHeight: invalid font number."); + + if (break_up_text_into_lines(text, Lines, data_to_game_coord(width), fontnum) == 0) + return 0; + return game_to_data_coord(getheightoflines(fontnum, Lines.Count())); +} + +int GetFontHeight(int fontnum) +{ + if ((fontnum < 0) || (fontnum >= game.numfonts)) + quit("!GetFontHeight: invalid font number."); + return game_to_data_coord(getfontheight_outlined(fontnum)); +} + +int GetFontLineSpacing(int fontnum) +{ + if ((fontnum < 0) || (fontnum >= game.numfonts)) + quit("!GetFontLineSpacing: invalid font number."); + return game_to_data_coord(getfontspacing_outlined(fontnum)); +} + +void SetGUIBackgroundPic (int guin, int slotn) { + if ((guin<0) | (guin>=game.numgui)) + quit("!SetGUIBackgroundPic: invalid GUI number"); + + GUI_SetBackgroundGraphic(&scrGui[guin], slotn); +} + +void DisableInterface() { + play.disabled_user_interface++; + guis_need_update = 1; + set_mouse_cursor(CURS_WAIT); + } + +void EnableInterface() { + guis_need_update = 1; + play.disabled_user_interface--; + if (play.disabled_user_interface<1) { + play.disabled_user_interface=0; + set_default_cursor(); + } + } +// Returns 1 if user interface is enabled, 0 if disabled +int IsInterfaceEnabled() { + return (play.disabled_user_interface > 0) ? 0 : 1; +} + +int GetGUIObjectAt (int xx, int yy) { + GUIObject *toret = GetGUIControlAtLocation(xx, yy); + if (toret == nullptr) + return -1; + + return toret->Id; +} + +int GetGUIAt (int xx,int yy) { + data_to_game_coords(&xx, &yy); + + int aa, ll; + for (ll = game.numgui - 1; ll >= 0; ll--) { + aa = play.gui_draw_order[ll]; + if (guis[aa].IsInteractableAt(xx, yy)) + return aa; + } + return -1; +} + +void SetTextWindowGUI (int guinum) { + if ((guinum < -1) | (guinum >= game.numgui)) + quit("!SetTextWindowGUI: invalid GUI number"); + + if (guinum < 0) ; // disable it + else if (!guis[guinum].IsTextWindow()) + quit("!SetTextWindowGUI: specified GUI is not a text window"); + + if (play.speech_textwindow_gui == game.options[OPT_TWCUSTOM]) + play.speech_textwindow_gui = guinum; + game.options[OPT_TWCUSTOM] = guinum; +} diff --git a/engines/ags/engine/ac/global_gui.h b/engines/ags/engine/ac/global_gui.h new file mode 100644 index 00000000000..2047fecc4b4 --- /dev/null +++ b/engines/ags/engine/ac/global_gui.h @@ -0,0 +1,54 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALGUI_H +#define __AGS_EE_AC__GLOBALGUI_H + +// IsGUIOn tells whether GUI is actually displayed on screen right now +int IsGUIOn (int guinum); +// This is an internal script function, and is undocumented. +// It is used by the editor's automatic macro generation. +// TODO: find out how relevant this comment is? +int FindGUIID (const char* GUIName); +// Sets GUI visible property on +void InterfaceOn(int ifn); +// Sets GUI visible property off +void InterfaceOff(int ifn); +void CentreGUI (int ifn); +int GetTextWidth(const char *text, int fontnum); +int GetTextHeight(const char *text, int fontnum, int width); +int GetFontHeight(int fontnum); +int GetFontLineSpacing(int fontnum); +void SetGUIBackgroundPic (int guin, int slotn); +void DisableInterface(); +void EnableInterface(); +// Returns 1 if user interface is enabled, 0 if disabled +int IsInterfaceEnabled(); +// pass trans=0 for fully solid, trans=100 for fully transparent +void SetGUITransparency(int ifn, int trans); +void SetGUIClickable(int guin, int clickable); +void SetGUIZOrder(int guin, int z); +void SetGUISize (int ifn, int widd, int hitt); +void SetGUIPosition(int ifn,int xx,int yy); +void SetGUIObjectSize(int ifn, int objn, int newwid, int newhit); +void SetGUIObjectEnabled(int guin, int objn, int enabled); +void SetGUIObjectPosition(int guin, int objn, int xx, int yy); +int GetGUIObjectAt (int xx, int yy); +int GetGUIAt (int xx,int yy); +void SetTextWindowGUI (int guinum); + +#endif // __AGS_EE_AC__GLOBALGUI_H diff --git a/engines/ags/engine/ac/global_hotspot.cpp b/engines/ags/engine/ac/global_hotspot.cpp new file mode 100644 index 00000000000..cab508f5628 --- /dev/null +++ b/engines/ags/engine/ac/global_hotspot.cpp @@ -0,0 +1,147 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_hotspot.h" +#include "ac/common.h" +#include "ac/common_defines.h" +#include "ac/characterinfo.h" +#include "ac/draw.h" +#include "ac/event.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_character.h" +#include "ac/global_translation.h" +#include "ac/hotspot.h" +#include "ac/properties.h" +#include "ac/roomstatus.h" +#include "ac/string.h" +#include "debug/debug_log.h" +#include "game/roomstruct.h" +#include "script/script.h" + +using namespace AGS::Common; + +extern RoomStruct thisroom; +extern RoomStatus*croom; +extern CharacterInfo*playerchar; +extern GameSetupStruct game; + + +void DisableHotspot(int hsnum) { + if ((hsnum<1) | (hsnum>=MAX_ROOM_HOTSPOTS)) + quit("!DisableHotspot: invalid hotspot specified"); + croom->hotspot_enabled[hsnum]=0; + debug_script_log("Hotspot %d disabled", hsnum); +} + +void EnableHotspot(int hsnum) { + if ((hsnum<1) | (hsnum>=MAX_ROOM_HOTSPOTS)) + quit("!EnableHotspot: invalid hotspot specified"); + croom->hotspot_enabled[hsnum]=1; + debug_script_log("Hotspot %d re-enabled", hsnum); +} + +int GetHotspotPointX (int hotspot) { + if ((hotspot < 0) || (hotspot >= MAX_ROOM_HOTSPOTS)) + quit("!GetHotspotPointX: invalid hotspot"); + + if (thisroom.Hotspots[hotspot].WalkTo.X < 1) + return -1; + + return thisroom.Hotspots[hotspot].WalkTo.X; +} + +int GetHotspotPointY (int hotspot) { + if ((hotspot < 0) || (hotspot >= MAX_ROOM_HOTSPOTS)) + quit("!GetHotspotPointY: invalid hotspot"); + + if (thisroom.Hotspots[hotspot].WalkTo.X < 1) // TODO: there was "x" here, why? + return -1; + + return thisroom.Hotspots[hotspot].WalkTo.Y; +} + +int GetHotspotIDAtScreen(int scrx, int scry) { + VpPoint vpt = play.ScreenToRoomDivDown(scrx, scry); + if (vpt.second < 0) return 0; + return get_hotspot_at(vpt.first.X, vpt.first.Y); +} + +void GetHotspotName(int hotspot, char *buffer) { + VALIDATE_STRING(buffer); + if ((hotspot < 0) || (hotspot >= MAX_ROOM_HOTSPOTS)) + quit("!GetHotspotName: invalid hotspot number"); + + strcpy(buffer, get_translation(thisroom.Hotspots[hotspot].Name)); +} + +void RunHotspotInteraction (int hotspothere, int mood) { + + int passon=-1,cdata=-1; + if (mood==MODE_TALK) passon=4; + else if (mood==MODE_WALK) passon=0; + else if (mood==MODE_LOOK) passon=1; + else if (mood==MODE_HAND) passon=2; + else if (mood==MODE_PICKUP) passon=7; + else if (mood==MODE_CUSTOM1) passon = 8; + else if (mood==MODE_CUSTOM2) passon = 9; + else if (mood==MODE_USE) { passon=3; + cdata=playerchar->activeinv; + play.usedinv=cdata; + } + + if ((game.options[OPT_WALKONLOOK]==0) & (mood==MODE_LOOK)) ; + else if (play.auto_use_walkto_points == 0) ; + else if ((mood!=MODE_WALK) && (play.check_interaction_only == 0)) + MoveCharacterToHotspot(game.playercharacter,hotspothere); + + // can't use the setevent functions because this ProcessClick is only + // executed once in a eventlist + const char *oldbasename = evblockbasename; + int oldblocknum = evblocknum; + + evblockbasename="hotspot%d"; + evblocknum=hotspothere; + + if (thisroom.Hotspots[hotspothere].EventHandlers != nullptr) + { + if (passon>=0) + run_interaction_script(thisroom.Hotspots[hotspothere].EventHandlers.get(), passon, 5, (passon == 3)); + run_interaction_script(thisroom.Hotspots[hotspothere].EventHandlers.get(), 5); // any click on hotspot + } + else + { + if (passon>=0) { + if (run_interaction_event(&croom->intrHotspot[hotspothere],passon, 5, (passon == 3))) { + evblockbasename = oldbasename; + evblocknum = oldblocknum; + return; + } + } + // run the 'any click on hs' event + run_interaction_event(&croom->intrHotspot[hotspothere],5); + } + + evblockbasename = oldbasename; + evblocknum = oldblocknum; +} + +int GetHotspotProperty (int hss, const char *property) +{ + return get_int_property(thisroom.Hotspots[hss].Properties, croom->hsProps[hss], property); +} + +void GetHotspotPropertyText (int item, const char *property, char *bufer) +{ + get_text_property(thisroom.Hotspots[item].Properties, croom->hsProps[item], property, bufer); +} diff --git a/engines/ags/engine/ac/global_hotspot.h b/engines/ags/engine/ac/global_hotspot.h new file mode 100644 index 00000000000..ec595042ead --- /dev/null +++ b/engines/ags/engine/ac/global_hotspot.h @@ -0,0 +1,35 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALHOTSPOT_H +#define __AGS_EE_AC__GLOBALHOTSPOT_H + +void DisableHotspot(int hsnum); +void EnableHotspot(int hsnum); +int GetHotspotPointX (int hotspot); +int GetHotspotPointY (int hotspot); +// Gets hotspot ID at the given screen coordinates; +// if hotspot is disabled or non-existing, returns 0 (no area) +int GetHotspotIDAtScreen(int xxx,int yyy); +void GetHotspotName(int hotspot, char *buffer); +void RunHotspotInteraction (int hotspothere, int mood); + +int GetHotspotProperty (int hss, const char *property); +void GetHotspotPropertyText (int item, const char *property, char *bufer); + + +#endif // __AGS_EE_AC__GLOBALHOTSPOT_H diff --git a/engines/ags/engine/ac/global_inventoryitem.cpp b/engines/ags/engine/ac/global_inventoryitem.cpp new file mode 100644 index 00000000000..4180cc283c4 --- /dev/null +++ b/engines/ags/engine/ac/global_inventoryitem.cpp @@ -0,0 +1,144 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_gui.h" +#include "ac/global_inventoryitem.h" +#include "ac/global_translation.h" +#include "ac/inventoryitem.h" +#include "ac/invwindow.h" +#include "ac/properties.h" +#include "ac/string.h" +#include "gui/guimain.h" +#include "gui/guiinv.h" +#include "ac/event.h" +#include "ac/gamestate.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; +extern GameState play; +extern int mousex, mousey; +extern int mouse_ifacebut_xoffs,mouse_ifacebut_yoffs; +extern const char*evblockbasename; +extern int evblocknum; +extern CharacterInfo*playerchar; + + +void set_inv_item_pic(int invi, int piccy) { + if ((invi < 1) || (invi > game.numinvitems)) + quit("!SetInvItemPic: invalid inventory item specified"); + + if (game.invinfo[invi].pic == piccy) + return; + + if (game.invinfo[invi].pic == game.invinfo[invi].cursorPic) + { + // Backwards compatibility -- there didn't used to be a cursorPic, + // so if they're the same update both. + set_inv_item_cursorpic(invi, piccy); + } + + game.invinfo[invi].pic = piccy; + guis_need_update = 1; +} + +void SetInvItemName(int invi, const char *newName) { + if ((invi < 1) || (invi > game.numinvitems)) + quit("!SetInvName: invalid inventory item specified"); + + // set the new name, making sure it doesn't overflow the buffer + strncpy(game.invinfo[invi].name, newName, 25); + game.invinfo[invi].name[24] = 0; + + // might need to redraw the GUI if it has the inv item name on it + guis_need_update = 1; +} + +int GetInvAt (int xxx, int yyy) { + int ongui = GetGUIAt (xxx, yyy); + if (ongui >= 0) { + int mxwas = mousex, mywas = mousey; + mousex = data_to_game_coord(xxx) - guis[ongui].X; + mousey = data_to_game_coord(yyy) - guis[ongui].Y; + int onobj = guis[ongui].FindControlUnderMouse(); + GUIObject *guio = guis[ongui].GetControl(onobj); + if (guio) { + mouse_ifacebut_xoffs = mousex-(guio->X); + mouse_ifacebut_yoffs = mousey-(guio->Y); + } + mousex = mxwas; + mousey = mywas; + if (guio && (guis[ongui].GetControlType(onobj) == kGUIInvWindow)) + return offset_over_inv((GUIInvWindow*)guio); + } + return -1; +} + +void GetInvName(int indx,char*buff) { + VALIDATE_STRING(buff); + if ((indx<0) | (indx>=game.numinvitems)) quit("!GetInvName: invalid inventory item specified"); + strcpy(buff,get_translation(game.invinfo[indx].name)); +} + +int GetInvGraphic(int indx) { + if ((indx<0) | (indx>=game.numinvitems)) quit("!GetInvGraphic: invalid inventory item specified"); + + return game.invinfo[indx].pic; +} + +void RunInventoryInteraction (int iit, int modd) { + if ((iit < 0) || (iit >= game.numinvitems)) + quit("!RunInventoryInteraction: invalid inventory number"); + + evblocknum = iit; + if (modd == MODE_LOOK) + run_event_block_inv(iit, 0); + else if (modd == MODE_HAND) + run_event_block_inv(iit, 1); + else if (modd == MODE_USE) { + play.usedinv = playerchar->activeinv; + run_event_block_inv(iit, 3); + } + else if (modd == MODE_TALK) + run_event_block_inv(iit, 2); + else // other click on invnetory + run_event_block_inv(iit, 4); +} + +int IsInventoryInteractionAvailable (int item, int mood) { + if ((item < 0) || (item >= MAX_INV)) + quit("!IsInventoryInteractionAvailable: invalid inventory number"); + + play.check_interaction_only = 1; + + RunInventoryInteraction(item, mood); + + int ciwas = play.check_interaction_only; + play.check_interaction_only = 0; + + if (ciwas == 2) + return 1; + + return 0; +} + +int GetInvProperty (int item, const char *property) { + return get_int_property (game.invProps[item], play.invProps[item], property); +} + +void GetInvPropertyText (int item, const char *property, char *bufer) { + get_text_property (game.invProps[item], play.invProps[item], property, bufer); +} diff --git a/engines/ags/engine/ac/global_inventoryitem.h b/engines/ags/engine/ac/global_inventoryitem.h new file mode 100644 index 00000000000..840bb73e0ea --- /dev/null +++ b/engines/ags/engine/ac/global_inventoryitem.h @@ -0,0 +1,31 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALINVENTORYITEM_H +#define __AGS_EE_AC__GLOBALINVENTORYITEM_H + +void set_inv_item_pic(int invi, int piccy); +void SetInvItemName(int invi, const char *newName); +int GetInvAt (int xxx, int yyy); +void GetInvName(int indx,char*buff); +int GetInvGraphic(int indx); +void RunInventoryInteraction (int iit, int modd); +int IsInventoryInteractionAvailable (int item, int mood); +int GetInvProperty (int item, const char *property); +void GetInvPropertyText (int item, const char *property, char *bufer); + +#endif // __AGS_EE_AC__GLOBALINVENTORYITEM_H diff --git a/engines/ags/engine/ac/global_invwindow.cpp b/engines/ags/engine/ac/global_invwindow.cpp new file mode 100644 index 00000000000..edd4ed926b4 --- /dev/null +++ b/engines/ags/engine/ac/global_invwindow.cpp @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/gamestate.h" +#include "ac/global_invwindow.h" +#include "ac/global_translation.h" +#include "ac/properties.h" +#include "gui/guiinv.h" +#include "script/executingscript.h" + +extern ExecutingScript*curscript; +extern GameState play; + +void sc_invscreen() { + curscript->queue_action(ePSAInvScreen, 0, "InventoryScreen"); +} + +void SetInvDimensions(int ww,int hh) { + play.inv_item_wid = ww; + play.inv_item_hit = hh; + play.inv_numdisp = 0; + // backwards compatibility + for (int i = 0; i < numguiinv; i++) { + guiinv[i].ItemWidth = ww; + guiinv[i].ItemHeight = hh; + guiinv[i].OnResized(); + } + guis_need_update = 1; +} diff --git a/engines/ags/engine/ac/global_invwindow.h b/engines/ags/engine/ac/global_invwindow.h new file mode 100644 index 00000000000..afa1d25c5cb --- /dev/null +++ b/engines/ags/engine/ac/global_invwindow.h @@ -0,0 +1,24 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALINVWINDOW_H +#define __AGS_EE_AC__GLOBALINVWINDOW_H + +void sc_invscreen(); +void SetInvDimensions(int ww,int hh); + +#endif // __AGS_EE_AC__GLOBALINVWINDOW_H diff --git a/engines/ags/engine/ac/global_label.cpp b/engines/ags/engine/ac/global_label.cpp new file mode 100644 index 00000000000..7a0335e9374 --- /dev/null +++ b/engines/ags/engine/ac/global_label.cpp @@ -0,0 +1,58 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_label.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/label.h" +#include "ac/string.h" +#include "gui/guimain.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; + +void SetLabelColor(int guin,int objn, int colr) { + if ((guin<0) | (guin>=game.numgui)) + quit("!SetLabelColor: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) + quit("!SetLabelColor: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUILabel) + quit("!SetLabelColor: specified control is not a label"); + + GUILabel*guil=(GUILabel*)guis[guin].GetControl(objn); + Label_SetColor(guil, colr); +} + +void SetLabelText(int guin,int objn, const char*newtx) { + VALIDATE_STRING(newtx); + if ((guin<0) | (guin>=game.numgui)) quit("!SetLabelText: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) quit("!SetLabelTexT: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUILabel) + quit("!SetLabelText: specified control is not a label"); + + GUILabel*guil=(GUILabel*)guis[guin].GetControl(objn); + Label_SetText(guil, newtx); +} + +void SetLabelFont(int guin,int objn, int fontnum) { + + if ((guin<0) | (guin>=game.numgui)) quit("!SetLabelFont: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) quit("!SetLabelFont: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUILabel) + quit("!SetLabelFont: specified control is not a label"); + + GUILabel*guil=(GUILabel*)guis[guin].GetControl(objn); + Label_SetFont(guil, fontnum); +} diff --git a/engines/ags/engine/ac/global_label.h b/engines/ags/engine/ac/global_label.h new file mode 100644 index 00000000000..fa96942f759 --- /dev/null +++ b/engines/ags/engine/ac/global_label.h @@ -0,0 +1,25 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALLABEL_H +#define __AGS_EE_AC__GLOBALLABEL_H + +void SetLabelColor(int guin,int objn, int colr); +void SetLabelText(int guin,int objn, const char*newtx); +void SetLabelFont(int guin,int objn, int fontnum); + +#endif // __AGS_EE_AC__GLOBALLABEL_H diff --git a/engines/ags/engine/ac/global_listbox.cpp b/engines/ags/engine/ac/global_listbox.cpp new file mode 100644 index 00000000000..546eac2e008 --- /dev/null +++ b/engines/ags/engine/ac/global_listbox.cpp @@ -0,0 +1,62 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_listbox.h" +#include "ac/common.h" +#include "ac/listbox.h" +#include "ac/string.h" + +void ListBoxClear(int guin, int objn) { + GUIListBox*guisl=is_valid_listbox(guin,objn); + ListBox_Clear(guisl); +} +void ListBoxAdd(int guin, int objn, const char*newitem) { + GUIListBox*guisl=is_valid_listbox(guin,objn); + ListBox_AddItem(guisl, newitem); +} +void ListBoxRemove(int guin, int objn, int itemIndex) { + GUIListBox*guisl = is_valid_listbox(guin,objn); + ListBox_RemoveItem(guisl, itemIndex); +} +int ListBoxGetSelected(int guin, int objn) { + GUIListBox*guisl=is_valid_listbox(guin,objn); + return ListBox_GetSelectedIndex(guisl); +} +int ListBoxGetNumItems(int guin, int objn) { + GUIListBox*guisl=is_valid_listbox(guin,objn); + return ListBox_GetItemCount(guisl); +} +char* ListBoxGetItemText(int guin, int objn, int item, char*buffer) { + VALIDATE_STRING(buffer); + GUIListBox*guisl=is_valid_listbox(guin,objn); + return ListBox_GetItemText(guisl, item, buffer); +} +void ListBoxSetSelected(int guin, int objn, int newsel) { + GUIListBox*guisl=is_valid_listbox(guin,objn); + ListBox_SetSelectedIndex(guisl, newsel); +} +void ListBoxSetTopItem (int guin, int objn, int item) { + GUIListBox*guisl = is_valid_listbox(guin,objn); + ListBox_SetTopItem(guisl, item); +} + +int ListBoxSaveGameList (int guin, int objn) { + GUIListBox*guisl=is_valid_listbox(guin,objn); + return ListBox_FillSaveGameList(guisl); +} + +void ListBoxDirList (int guin, int objn, const char*filemask) { + GUIListBox *guisl = is_valid_listbox(guin,objn); + ListBox_FillDirList(guisl, filemask); +} diff --git a/engines/ags/engine/ac/global_listbox.h b/engines/ags/engine/ac/global_listbox.h new file mode 100644 index 00000000000..d6be2f449fd --- /dev/null +++ b/engines/ags/engine/ac/global_listbox.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALLISTBOX_H +#define __AGS_EE_AC__GLOBALLISTBOX_H + +void ListBoxClear(int guin, int objn); +void ListBoxAdd(int guin, int objn, const char*newitem); +void ListBoxRemove(int guin, int objn, int itemIndex); +int ListBoxGetSelected(int guin, int objn); +int ListBoxGetNumItems(int guin, int objn); +char* ListBoxGetItemText(int guin, int objn, int item, char*buffer); +void ListBoxSetSelected(int guin, int objn, int newsel); +void ListBoxSetTopItem (int guin, int objn, int item); +int ListBoxSaveGameList (int guin, int objn); +void ListBoxDirList (int guin, int objn, const char*filemask); + +#endif // __AGS_EE_AC__GLOBALLISTBOX_H diff --git a/engines/ags/engine/ac/global_mouse.cpp b/engines/ags/engine/ac/global_mouse.cpp new file mode 100644 index 00000000000..206f5029773 --- /dev/null +++ b/engines/ags/engine/ac/global_mouse.cpp @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_mouse.h" +#include "ac/gamestate.h" + +extern GameState play; + +void HideMouseCursor () { + play.mouse_cursor_hidden = 1; +} + +void ShowMouseCursor () { + play.mouse_cursor_hidden = 0; +} diff --git a/engines/ags/engine/ac/global_mouse.h b/engines/ags/engine/ac/global_mouse.h new file mode 100644 index 00000000000..dbff7fa3863 --- /dev/null +++ b/engines/ags/engine/ac/global_mouse.h @@ -0,0 +1,24 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALMOUSE_H +#define __AGS_EE_AC__GLOBALMOUSE_H + +void HideMouseCursor (); +void ShowMouseCursor (); + +#endif // __AGS_EE_AC__GLOBALMOUSE_H diff --git a/engines/ags/engine/ac/global_object.cpp b/engines/ags/engine/ac/global_object.cpp new file mode 100644 index 00000000000..aa43d906b70 --- /dev/null +++ b/engines/ags/engine/ac/global_object.cpp @@ -0,0 +1,523 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_object.h" +#include "ac/common.h" +#include "ac/object.h" +#include "ac/view.h" +#include "ac/character.h" +#include "ac/draw.h" +#include "ac/event.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_character.h" +#include "ac/global_translation.h" +#include "ac/object.h" +#include "ac/objectcache.h" +#include "ac/properties.h" +#include "ac/roomobject.h" +#include "ac/roomstatus.h" +#include "ac/string.h" +#include "ac/viewframe.h" +#include "debug/debug_log.h" +#include "main/game_run.h" +#include "script/script.h" +#include "ac/spritecache.h" +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" +#include "gfx/gfx_def.h" + +using namespace AGS::Common; + +#define OVERLAPPING_OBJECT 1000 + +extern RoomStatus*croom; +extern RoomObject*objs; +extern ViewStruct*views; +extern GameSetupStruct game; +extern ObjectCache objcache[MAX_ROOM_OBJECTS]; +extern RoomStruct thisroom; +extern CharacterInfo*playerchar; +extern int displayed_room; +extern SpriteCache spriteset; +extern int actSpsCount; +extern Bitmap **actsps; +extern IDriverDependantBitmap* *actspsbmp; +extern IGraphicsDriver *gfxDriver; + +// Used for deciding whether a char or obj was closer +int obj_lowest_yp; + +int GetObjectIDAtScreen(int scrx, int scry) +{ + // translate screen co-ordinates to room co-ordinates + VpPoint vpt = play.ScreenToRoomDivDown(scrx, scry); + if (vpt.second < 0) + return -1; + return GetObjectIDAtRoom(vpt.first.X, vpt.first.Y); +} + +int GetObjectIDAtRoom(int roomx, int roomy) +{ + int aa,bestshotyp=-1,bestshotwas=-1; + // Iterate through all objects in the room + for (aa=0;aanumobj;aa++) { + if (objs[aa].on != 1) continue; + if (objs[aa].flags & OBJF_NOINTERACT) + continue; + int xxx=objs[aa].x,yyy=objs[aa].y; + int isflipped = 0; + int spWidth = game_to_data_coord(objs[aa].get_width()); + int spHeight = game_to_data_coord(objs[aa].get_height()); + if (objs[aa].view >= 0) + isflipped = views[objs[aa].view].loops[objs[aa].loop].frames[objs[aa].frame].flags & VFLG_FLIPSPRITE; + + Bitmap *theImage = GetObjectImage(aa, &isflipped); + + if (is_pos_in_sprite(roomx, roomy, xxx, yyy - spHeight, theImage, + spWidth, spHeight, isflipped) == FALSE) + continue; + + int usebasel = objs[aa].get_baseline(); + if (usebasel < bestshotyp) continue; + + bestshotwas = aa; + bestshotyp = usebasel; + } + obj_lowest_yp = bestshotyp; + return bestshotwas; +} + +void SetObjectTint(int obj, int red, int green, int blue, int opacity, int luminance) { + if ((red < 0) || (green < 0) || (blue < 0) || + (red > 255) || (green > 255) || (blue > 255) || + (opacity < 0) || (opacity > 100) || + (luminance < 0) || (luminance > 100)) + quit("!SetObjectTint: invalid parameter. R,G,B must be 0-255, opacity & luminance 0-100"); + + if (!is_valid_object(obj)) + quit("!SetObjectTint: invalid object number specified"); + + debug_script_log("Set object %d tint RGB(%d,%d,%d) %d%%", obj, red, green, blue, opacity); + + objs[obj].tint_r = red; + objs[obj].tint_g = green; + objs[obj].tint_b = blue; + objs[obj].tint_level = opacity; + objs[obj].tint_light = (luminance * 25) / 10; + objs[obj].flags &= ~OBJF_HASLIGHT; + objs[obj].flags |= OBJF_HASTINT; +} + +void RemoveObjectTint(int obj) { + if (!is_valid_object(obj)) + quit("!RemoveObjectTint: invalid object"); + + if (objs[obj].flags & (OBJF_HASTINT | OBJF_HASLIGHT)) { + debug_script_log("Un-tint object %d", obj); + objs[obj].flags &= ~(OBJF_HASTINT | OBJF_HASLIGHT); + } + else { + debug_script_warn("RemoveObjectTint called but object was not tinted"); + } +} + +void SetObjectView(int obn,int vii) { + if (!is_valid_object(obn)) quit("!SetObjectView: invalid object number specified"); + debug_script_log("Object %d set to view %d", obn, vii); + if ((vii < 1) || (vii > game.numviews)) { + quitprintf("!SetObjectView: invalid view number (You said %d, max is %d)", vii, game.numviews); + } + vii--; + + objs[obn].view=vii; + objs[obn].frame=0; + if (objs[obn].loop >= views[vii].numLoops) + objs[obn].loop=0; + objs[obn].cycling=0; + objs[obn].num = views[vii].loops[0].frames[0].pic; +} + +void SetObjectFrame(int obn,int viw,int lop,int fra) { + if (!is_valid_object(obn)) quit("!SetObjectFrame: invalid object number specified"); + viw--; + if (viw < 0 || viw >= game.numviews) quitprintf("!SetObjectFrame: invalid view number used (%d, range is 0 - %d)", viw, game.numviews - 1); + if (lop < 0 || lop >= views[viw].numLoops) quitprintf("!SetObjectFrame: invalid loop number used (%d, range is 0 - %d)", lop, views[viw].numLoops - 1); + // AGS < 3.5.1 let user to pass literally any positive invalid frame value by silently reassigning it to zero... + if (loaded_game_file_version < kGameVersion_351) + { + if (fra >= views[viw].loops[lop].numFrames) + { + debug_script_warn("SetObjectFrame: frame index out of range (%d, must be 0 - %d), set to 0", fra, views[viw].loops[lop].numFrames - 1); + fra = 0; + } + } + if (fra < 0 || fra >= views[viw].loops[lop].numFrames) quitprintf("!SetObjectFrame: invalid frame number used (%d, range is 0 - %d)", fra, views[viw].loops[lop].numFrames - 1); + // AGS >= 3.2.0 do not let assign an empty loop + // NOTE: pre-3.2.0 games are converting views from ViewStruct272 struct, always has at least 1 frame + if (loaded_game_file_version >= kGameVersion_320) + { + if (views[viw].loops[lop].numFrames == 0) + quit("!SetObjectFrame: specified loop has no frames"); + } + objs[obn].view = viw; + objs[obn].loop = lop; + objs[obn].frame = fra; + objs[obn].cycling=0; + objs[obn].num = views[viw].loops[lop].frames[fra].pic; + CheckViewFrame(viw, objs[obn].loop, objs[obn].frame); +} + +// pass trans=0 for fully solid, trans=100 for fully transparent +void SetObjectTransparency(int obn,int trans) { + if (!is_valid_object(obn)) quit("!SetObjectTransparent: invalid object number specified"); + if ((trans < 0) || (trans > 100)) quit("!SetObjectTransparent: transparency value must be between 0 and 100"); + + objs[obn].transparent = GfxDef::Trans100ToLegacyTrans255(trans); +} + + + +void SetObjectBaseline (int obn, int basel) { + if (!is_valid_object(obn)) quit("!SetObjectBaseline: invalid object number specified"); + // baseline has changed, invalidate the cache + if (objs[obn].baseline != basel) { + objcache[obn].ywas = -9999; + objs[obn].baseline = basel; + } +} + +int GetObjectBaseline(int obn) { + if (!is_valid_object(obn)) quit("!GetObjectBaseline: invalid object number specified"); + + if (objs[obn].baseline < 1) + return 0; + + return objs[obn].baseline; +} + +void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, int blocking, int sframe) { + if (obn>=MANOBJNUM) { + scAnimateCharacter(obn - 100,loopn,spdd,rept); + return; + } + if (!is_valid_object(obn)) + quit("!AnimateObject: invalid object number specified"); + if (objs[obn].view < 0) + quit("!AnimateObject: object has not been assigned a view"); + if (loopn < 0 || loopn >= views[objs[obn].view].numLoops) + quit("!AnimateObject: invalid loop number specified"); + if (sframe < 0 || sframe >= views[objs[obn].view].loops[loopn].numFrames) + quit("!AnimateObject: invalid starting frame number specified"); + if ((direction < 0) || (direction > 1)) + quit("!AnimateObjectEx: invalid direction"); + if ((rept < 0) || (rept > 2)) + quit("!AnimateObjectEx: invalid repeat value"); + if (views[objs[obn].view].loops[loopn].numFrames < 1) + quit("!AnimateObject: no frames in the specified view loop"); + + debug_script_log("Obj %d start anim view %d loop %d, speed %d, repeat %d, frame %d", obn, objs[obn].view+1, loopn, spdd, rept, sframe); + + objs[obn].cycling = rept+1 + (direction * 10); + objs[obn].loop=loopn; + // reverse animation starts at the *previous frame* + if (direction) { + sframe--; + if (sframe < 0) + sframe = views[objs[obn].view].loops[loopn].numFrames - (-sframe); + } + objs[obn].frame = sframe; + + objs[obn].overall_speed=spdd; + objs[obn].wait = spdd+views[objs[obn].view].loops[loopn].frames[objs[obn].frame].speed; + objs[obn].num = views[objs[obn].view].loops[loopn].frames[objs[obn].frame].pic; + CheckViewFrame (objs[obn].view, loopn, objs[obn].frame); + + if (blocking) + GameLoopUntilValueIsZero(&objs[obn].cycling); +} + +void AnimateObjectEx(int obn, int loopn, int spdd, int rept, int direction, int blocking) { + AnimateObjectImpl(obn, loopn, spdd, rept, direction, blocking, 0); +} + +void AnimateObject(int obn,int loopn,int spdd,int rept) { + AnimateObjectImpl(obn, loopn, spdd, rept, 0, 0, 0); +} + +void MergeObject(int obn) { + if (!is_valid_object(obn)) quit("!MergeObject: invalid object specified"); + int theHeight; + + construct_object_gfx(obn, nullptr, &theHeight, true); + + //Bitmap *oldabuf = graphics->bmp; + //abuf = thisroom.BgFrames.Graphic[play.bg_frame]; + PBitmap bg_frame = thisroom.BgFrames[play.bg_frame].Graphic; + if (bg_frame->GetColorDepth() != actsps[obn]->GetColorDepth()) + quit("!MergeObject: unable to merge object due to color depth differences"); + + int xpos = data_to_game_coord(objs[obn].x); + int ypos = (data_to_game_coord(objs[obn].y) - theHeight); + + draw_sprite_support_alpha(bg_frame.get(), false, xpos, ypos, actsps[obn], (game.SpriteInfos[objs[obn].num].Flags & SPF_ALPHACHANNEL) != 0); + invalidate_screen(); + mark_current_background_dirty(); + + //abuf = oldabuf; + // mark the sprite as merged + objs[obn].on = 2; + debug_script_log("Object %d merged into background", obn); +} + +void StopObjectMoving(int objj) { + if (!is_valid_object(objj)) + quit("!StopObjectMoving: invalid object number"); + objs[objj].moving = 0; + + debug_script_log("Object %d stop moving", objj); +} + +void ObjectOff(int obn) { + if (!is_valid_object(obn)) quit("!ObjectOff: invalid object specified"); + // don't change it if on == 2 (merged) + if (objs[obn].on == 1) { + objs[obn].on = 0; + debug_script_log("Object %d turned off", obn); + StopObjectMoving(obn); + } +} + +void ObjectOn(int obn) { + if (!is_valid_object(obn)) quit("!ObjectOn: invalid object specified"); + if (objs[obn].on == 0) { + objs[obn].on = 1; + debug_script_log("Object %d turned on", obn); + } +} + +int IsObjectOn (int objj) { + if (!is_valid_object(objj)) quit("!IsObjectOn: invalid object number"); + + // ==1 is on, ==2 is merged into background + if (objs[objj].on == 1) + return 1; + + return 0; +} + +void SetObjectGraphic(int obn,int slott) { + if (!is_valid_object(obn)) quit("!SetObjectGraphic: invalid object specified"); + + if (objs[obn].num != slott) { + objs[obn].num = slott; + debug_script_log("Object %d graphic changed to slot %d", obn, slott); + } + objs[obn].cycling=0; + objs[obn].frame = 0; + objs[obn].loop = 0; + objs[obn].view = -1; +} + +int GetObjectGraphic(int obn) { + if (!is_valid_object(obn)) quit("!GetObjectGraphic: invalid object specified"); + return objs[obn].num; +} + +int GetObjectY (int objj) { + if (!is_valid_object(objj)) quit("!GetObjectY: invalid object number"); + return objs[objj].y; +} + +int IsObjectAnimating(int objj) { + if (!is_valid_object(objj)) quit("!IsObjectAnimating: invalid object number"); + return (objs[objj].cycling != 0) ? 1 : 0; +} + +int IsObjectMoving(int objj) { + if (!is_valid_object(objj)) quit("!IsObjectMoving: invalid object number"); + return (objs[objj].moving > 0) ? 1 : 0; +} + +void SetObjectPosition(int objj, int tox, int toy) { + if (!is_valid_object(objj)) + quit("!SetObjectPosition: invalid object number"); + + if (objs[objj].moving > 0) + { + debug_script_warn("Object.SetPosition: cannot set position while object is moving"); + return; + } + + objs[objj].x = tox; + objs[objj].y = toy; +} + +void GetObjectName(int obj, char *buffer) { + VALIDATE_STRING(buffer); + if (!is_valid_object(obj)) + quit("!GetObjectName: invalid object number"); + + strcpy(buffer, get_translation(thisroom.Objects[obj].Name)); +} + +void MoveObject(int objj,int xx,int yy,int spp) { + move_object(objj,xx,yy,spp,0); +} +void MoveObjectDirect(int objj,int xx,int yy,int spp) { + move_object(objj,xx,yy,spp,1); +} + +void SetObjectClickable (int cha, int clik) { + if (!is_valid_object(cha)) + quit("!SetObjectClickable: Invalid object specified"); + objs[cha].flags&=~OBJF_NOINTERACT; + if (clik == 0) + objs[cha].flags|=OBJF_NOINTERACT; +} + +void SetObjectIgnoreWalkbehinds (int cha, int clik) { + if (!is_valid_object(cha)) + quit("!SetObjectIgnoreWalkbehinds: Invalid object specified"); + if (game.options[OPT_BASESCRIPTAPI] >= kScriptAPI_v350) + debug_script_warn("IgnoreWalkbehinds is not recommended for use, consider other solutions"); + objs[cha].flags&=~OBJF_NOWALKBEHINDS; + if (clik) + objs[cha].flags|=OBJF_NOWALKBEHINDS; + // clear the cache + objcache[cha].ywas = -9999; +} + +void RunObjectInteraction (int aa, int mood) { + if (!is_valid_object(aa)) + quit("!RunObjectInteraction: invalid object number for current room"); + int passon=-1,cdata=-1; + if (mood==MODE_LOOK) passon=0; + else if (mood==MODE_HAND) passon=1; + else if (mood==MODE_TALK) passon=2; + else if (mood==MODE_PICKUP) passon=5; + else if (mood==MODE_CUSTOM1) passon = 6; + else if (mood==MODE_CUSTOM2) passon = 7; + else if (mood==MODE_USE) { passon=3; + cdata=playerchar->activeinv; + play.usedinv=cdata; } + evblockbasename="object%d"; evblocknum=aa; + + if (thisroom.Objects[aa].EventHandlers != nullptr) + { + if (passon>=0) + { + if (run_interaction_script(thisroom.Objects[aa].EventHandlers.get(), passon, 4, (passon == 3))) + return; + } + run_interaction_script(thisroom.Objects[aa].EventHandlers.get(), 4); // any click on obj + } + else + { + if (passon>=0) { + if (run_interaction_event(&croom->intrObject[aa],passon, 4, (passon == 3))) + return; + } + run_interaction_event(&croom->intrObject[aa],4); // any click on obj + } +} + +int AreObjectsColliding(int obj1,int obj2) { + if ((!is_valid_object(obj1)) | (!is_valid_object(obj2))) + quit("!AreObjectsColliding: invalid object specified"); + + return (AreThingsOverlapping(obj1 + OVERLAPPING_OBJECT, obj2 + OVERLAPPING_OBJECT)) ? 1 : 0; +} + +int GetThingRect(int thing, _Rect *rect) { + if (is_valid_character(thing)) { + if (game.chars[thing].room != displayed_room) + return 0; + + int charwid = game_to_data_coord(GetCharacterWidth(thing)); + rect->x1 = game.chars[thing].x - (charwid / 2); + rect->x2 = rect->x1 + charwid; + rect->y1 = game.chars[thing].get_effective_y() - game_to_data_coord(GetCharacterHeight(thing)); + rect->y2 = game.chars[thing].get_effective_y(); + } + else if (is_valid_object(thing - OVERLAPPING_OBJECT)) { + int objid = thing - OVERLAPPING_OBJECT; + if (objs[objid].on != 1) + return 0; + rect->x1 = objs[objid].x; + rect->x2 = objs[objid].x + game_to_data_coord(objs[objid].get_width()); + rect->y1 = objs[objid].y - game_to_data_coord(objs[objid].get_height()); + rect->y2 = objs[objid].y; + } + else + quit("!AreThingsOverlapping: invalid parameter"); + + return 1; +} + +int AreThingsOverlapping(int thing1, int thing2) { + _Rect r1, r2; + // get the bounding rectangles, and return 0 if the object/char + // is currently turned off + if (GetThingRect(thing1, &r1) == 0) + return 0; + if (GetThingRect(thing2, &r2) == 0) + return 0; + + if ((r1.x2 > r2.x1) && (r1.x1 < r2.x2) && + (r1.y2 > r2.y1) && (r1.y1 < r2.y2)) { + // determine how far apart they are + // take the smaller of the X distances as the overlapping amount + int xdist = abs(r1.x2 - r2.x1); + if (abs(r1.x1 - r2.x2) < xdist) + xdist = abs(r1.x1 - r2.x2); + // take the smaller of the Y distances + int ydist = abs(r1.y2 - r2.y1); + if (abs(r1.y1 - r2.y2) < ydist) + ydist = abs(r1.y1 - r2.y2); + // the overlapping amount is the smaller of the X and Y ovrlap + if (xdist < ydist) + return xdist; + else + return ydist; + // return 1; + } + return 0; +} + +int GetObjectProperty (int hss, const char *property) +{ + if (!is_valid_object(hss)) + quit("!GetObjectProperty: invalid object"); + return get_int_property(thisroom.Objects[hss].Properties, croom->objProps[hss], property); +} + +void GetObjectPropertyText (int item, const char *property, char *bufer) +{ + get_text_property(thisroom.Objects[item].Properties, croom->objProps[item], property, bufer); +} + +Bitmap *GetObjectImage(int obj, int *isFlipped) +{ + if (!gfxDriver->HasAcceleratedTransform()) + { + if (actsps[obj] != nullptr) { + // the actsps image is pre-flipped, so no longer register the image as such + if (isFlipped) + *isFlipped = 0; + + return actsps[obj]; + } + } + return spriteset[objs[obj].num]; +} diff --git a/engines/ags/engine/ac/global_object.h b/engines/ags/engine/ac/global_object.h new file mode 100644 index 00000000000..ac4a8776572 --- /dev/null +++ b/engines/ags/engine/ac/global_object.h @@ -0,0 +1,71 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALOBJECT_H +#define __AGS_EE_AC__GLOBALOBJECT_H + +namespace AGS { namespace Common { class Bitmap; } } +using namespace AGS; // FIXME later + +// TODO: merge with other Rect declared in bitmap unit +struct _Rect { + int x1,y1,x2,y2; +}; + +// Get object at the given screen coordinates +int GetObjectIDAtScreen(int xx,int yy); +// Get object at the given room coordinates +int GetObjectIDAtRoom(int roomx, int roomy); +void SetObjectTint(int obj, int red, int green, int blue, int opacity, int luminance); +void RemoveObjectTint(int obj); +void SetObjectView(int obn,int vii); +void SetObjectFrame(int obn,int viw,int lop,int fra); +// pass trans=0 for fully solid, trans=100 for fully transparent +void SetObjectTransparency(int obn,int trans); +void SetObjectBaseline (int obn, int basel); +int GetObjectBaseline(int obn); +void AnimateObjectEx(int obn,int loopn,int spdd,int rept, int direction, int blocking); +void AnimateObject(int obn,int loopn,int spdd,int rept); +void AnimateObjectImpl(int obn, int loopn, int spdd, int rept, int direction, int blocking, int sframe); +void MergeObject(int obn); +void StopObjectMoving(int objj); +void ObjectOff(int obn); +void ObjectOn(int obn); +int IsObjectOn (int objj); +void SetObjectGraphic(int obn,int slott); +int GetObjectGraphic(int obn); +int GetObjectX (int objj); +int GetObjectY (int objj); +int IsObjectAnimating(int objj); +int IsObjectMoving(int objj); +void SetObjectPosition(int objj, int tox, int toy); +void GetObjectName(int obj, char *buffer); +void MoveObject(int objj,int xx,int yy,int spp); +void MoveObjectDirect(int objj,int xx,int yy,int spp); +void SetObjectClickable (int cha, int clik); +void SetObjectIgnoreWalkbehinds (int cha, int clik); +void RunObjectInteraction (int aa, int mood); +int AreObjectsColliding(int obj1,int obj2); +int GetThingRect(int thing, _Rect *rect); +int AreThingsOverlapping(int thing1, int thing2); + +int GetObjectProperty (int hss, const char *property); +void GetObjectPropertyText (int item, const char *property, char *bufer); + +Common::Bitmap *GetObjectImage(int obj, int *isFlipped); + +#endif // __AGS_EE_AC__GLOBALOBJECT_H diff --git a/engines/ags/engine/ac/global_overlay.cpp b/engines/ags/engine/ac/global_overlay.cpp new file mode 100644 index 00000000000..d9525810342 --- /dev/null +++ b/engines/ags/engine/ac/global_overlay.cpp @@ -0,0 +1,94 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_overlay.h" +#include "ac/common.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_translation.h" +#include "ac/overlay.h" +#include "ac/runtime_defines.h" +#include "ac/screenoverlay.h" +#include "ac/string.h" +#include "ac/spritecache.h" +#include "ac/system.h" +#include "gfx/bitmap.h" + +using namespace Common; +using namespace Engine; + +extern SpriteCache spriteset; +extern GameSetupStruct game; + + + +void RemoveOverlay(int ovrid) { + if (find_overlay_of_type(ovrid) < 0) quit("!RemoveOverlay: invalid overlay id passed"); + remove_screen_overlay(ovrid); +} + +int CreateGraphicOverlay(int xx,int yy,int slott,int trans) { + data_to_game_coords(&xx, &yy); + + Bitmap *screeno=BitmapHelper::CreateTransparentBitmap(game.SpriteInfos[slott].Width, game.SpriteInfos[slott].Height, game.GetColorDepth()); + wputblock(screeno, 0,0,spriteset[slott],trans); + bool hasAlpha = (game.SpriteInfos[slott].Flags & SPF_ALPHACHANNEL) != 0; + int nse = add_screen_overlay(xx, yy, OVER_CUSTOM, screeno, hasAlpha); + return screenover[nse].type; +} + +int CreateTextOverlayCore(int xx, int yy, int wii, int fontid, int text_color, const char *text, int disp_type, int allowShrink) { + if (wii<8) wii=play.GetUIViewport().GetWidth()/2; + if (xx<0) xx=play.GetUIViewport().GetWidth()/2-wii/2; + if (text_color ==0) text_color =16; + return _display_main(xx,yy,wii, text, disp_type, fontid, -text_color, 0, allowShrink, false); +} + +int CreateTextOverlay(int xx, int yy, int wii, int fontid, int text_color, const char* text, int disp_type) { + int allowShrink = 0; + + if (xx != OVR_AUTOPLACE) { + data_to_game_coords(&xx,&yy); + wii = data_to_game_coord(wii); + } + else // allow DisplaySpeechBackground to be shrunk + allowShrink = 1; + + return CreateTextOverlayCore(xx, yy, wii, fontid, text_color, text, disp_type, allowShrink); +} + +void SetTextOverlay(int ovrid, int xx, int yy, int wii, int fontid, int text_color, const char *text) { + RemoveOverlay(ovrid); + const int disp_type = ovrid; + if (CreateTextOverlay(xx, yy, wii, fontid, text_color, text, disp_type) !=ovrid) + quit("SetTextOverlay internal error: inconsistent type ids"); +} + +void MoveOverlay(int ovrid, int newx,int newy) { + data_to_game_coords(&newx, &newy); + + int ovri=find_overlay_of_type(ovrid); + if (ovri<0) quit("!MoveOverlay: invalid overlay ID specified"); + screenover[ovri].x=newx; + screenover[ovri].y=newy; +} + +int IsOverlayValid(int ovrid) { + if (find_overlay_of_type(ovrid) < 0) + return 0; + + return 1; +} diff --git a/engines/ags/engine/ac/global_overlay.h b/engines/ags/engine/ac/global_overlay.h new file mode 100644 index 00000000000..0282bdc8685 --- /dev/null +++ b/engines/ags/engine/ac/global_overlay.h @@ -0,0 +1,29 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALOVERLAY_H +#define __AGS_EE_AC__GLOBALOVERLAY_H + +void RemoveOverlay(int ovrid); +int CreateGraphicOverlay(int xx, int yy, int slott, int trans); +int CreateTextOverlayCore(int xx, int yy, int wii, int fontid, int text_color, const char *text, int disp_type, int allowShrink); +int CreateTextOverlay(int xx, int yy, int wii, int fontid, int clr, const char* text, int disp_type); +void SetTextOverlay(int ovrid, int xx, int yy, int wii, int fontid, int text_color, const char *text); +void MoveOverlay(int ovrid, int newx, int newy); +int IsOverlayValid(int ovrid); + +#endif // __AGS_EE_AC__GLOBALOVERLAY_H diff --git a/engines/ags/engine/ac/global_palette.cpp b/engines/ags/engine/ac/global_palette.cpp new file mode 100644 index 00000000000..89048897657 --- /dev/null +++ b/engines/ags/engine/ac/global_palette.cpp @@ -0,0 +1,67 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_palette.h" + +extern GameSetupStruct game; +extern GameState play; +extern color palette[256]; + + +void CyclePalette(int strt,int eend) { + // hi-color game must invalidate screen since the palette changes + // the effect of the drawing operations + if (game.color_depth > 1) + invalidate_screen(); + + if ((strt < 0) || (strt > 255) || (eend < 0) || (eend > 255)) + quit("!CyclePalette: start and end must be 0-255"); + + if (eend > strt) { + // forwards + wcolrotate(strt, eend, 0, palette); + set_palette_range(palette, strt, eend, 0); + } + else { + // backwards + wcolrotate(eend, strt, 1, palette); + set_palette_range(palette, eend, strt, 0); + } + +} +void SetPalRGB(int inndx,int rr,int gg,int bb) { + if (game.color_depth > 1) + invalidate_screen(); + + wsetrgb(inndx,rr,gg,bb,palette); + set_palette_range(palette, inndx, inndx, 0); +} +/*void scSetPal(color*pptr) { +wsetpalette(0,255,pptr); +} +void scGetPal(color*pptr) { +get_palette(pptr); +}*/ + +void UpdatePalette() { + if (game.color_depth > 1) + invalidate_screen(); + + if (!play.fast_forward) + setpal(); +} diff --git a/engines/ags/engine/ac/global_palette.h b/engines/ags/engine/ac/global_palette.h new file mode 100644 index 00000000000..95b03d449d9 --- /dev/null +++ b/engines/ags/engine/ac/global_palette.h @@ -0,0 +1,25 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALPALETTE_H +#define __AGS_EE_AC__GLOBALPALETTE_H + +void CyclePalette(int strt,int eend); +void SetPalRGB(int inndx,int rr,int gg,int bb); +void UpdatePalette(); + +#endif // __AGS_EE_AC__GLOBALPALETTE_H diff --git a/engines/ags/engine/ac/global_parser.cpp b/engines/ags/engine/ac/global_parser.cpp new file mode 100644 index 00000000000..eae49baf92f --- /dev/null +++ b/engines/ags/engine/ac/global_parser.cpp @@ -0,0 +1,29 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include //strcpy() +#include "ac/global_parser.h" +#include "ac/common.h" +#include "ac/gamestate.h" +#include "ac/string.h" + +extern GameState play; + +int SaidUnknownWord (char*buffer) { + VALIDATE_STRING(buffer); + strcpy (buffer, play.bad_parsed_word); + if (play.bad_parsed_word[0] == 0) + return 0; + return 1; +} diff --git a/engines/ags/engine/ac/global_parser.h b/engines/ags/engine/ac/global_parser.h new file mode 100644 index 00000000000..07ceebd3a6d --- /dev/null +++ b/engines/ags/engine/ac/global_parser.h @@ -0,0 +1,23 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALPARSER_H +#define __AGS_EE_AC__GLOBALPARSER_H + +int SaidUnknownWord (char*buffer); + +#endif // __AGS_EE_AC__GLOBALPARSER_H diff --git a/engines/ags/engine/ac/global_plugin.h b/engines/ags/engine/ac/global_plugin.h new file mode 100644 index 00000000000..e3e1a9c2818 --- /dev/null +++ b/engines/ags/engine/ac/global_plugin.h @@ -0,0 +1,24 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_PLUGIN__PLUGINFUNC_H +#define __AGS_EE_PLUGIN__PLUGINFUNC_H + +void PluginSimulateMouseClick(int pluginButtonID); +bool RegisterPluginStubs(const char* name); + +#endif // __AGS_EE_PLUGIN__PLUGINFUNC_H diff --git a/engines/ags/engine/ac/global_record.cpp b/engines/ags/engine/ac/global_record.cpp new file mode 100644 index 00000000000..afada9a47ec --- /dev/null +++ b/engines/ags/engine/ac/global_record.cpp @@ -0,0 +1,20 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_record.h" +#include "ac/common.h" + +void scStartRecording (int keyToStop) { + quit("!StartRecording: not supported"); +} diff --git a/engines/ags/engine/ac/global_record.h b/engines/ags/engine/ac/global_record.h new file mode 100644 index 00000000000..51c4972f56c --- /dev/null +++ b/engines/ags/engine/ac/global_record.h @@ -0,0 +1,23 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALRECORD_H +#define __AGS_EE_AC__GLOBALRECORD_H + +void scStartRecording (int keyToStop); + +#endif // __AGS_EE_AC__GLOBALRECORD_H diff --git a/engines/ags/engine/ac/global_region.cpp b/engines/ags/engine/ac/global_region.cpp new file mode 100644 index 00000000000..6c144213126 --- /dev/null +++ b/engines/ags/engine/ac/global_region.cpp @@ -0,0 +1,164 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_region.h" +#include "ac/common.h" +#include "ac/game_version.h" +#include "ac/region.h" +#include "ac/room.h" +#include "ac/roomstatus.h" +#include "debug/debug_log.h" +#include "game/roomstruct.h" +#include "gfx/bitmap.h" +#include "script/script.h" + + +using namespace AGS::Common; + +extern RoomStruct thisroom; +extern RoomStatus*croom; +extern const char*evblockbasename; +extern int evblocknum; + +int GetRegionIDAtRoom(int xxx, int yyy) { + // if the co-ordinates are off the edge of the screen, + // correct them to be just within + // this fixes walk-off-screen problems + xxx = room_to_mask_coord(xxx); + yyy = room_to_mask_coord(yyy); + + if (loaded_game_file_version >= kGameVersion_262) // Version 2.6.2+ + { + if (xxx >= thisroom.RegionMask->GetWidth()) + xxx = thisroom.RegionMask->GetWidth() - 1; + if (yyy >= thisroom.RegionMask->GetHeight()) + yyy = thisroom.RegionMask->GetHeight() - 1; + if (xxx < 0) + xxx = 0; + if (yyy < 0) + yyy = 0; + } + + int hsthere = thisroom.RegionMask->GetPixel (xxx, yyy); + if (hsthere <= 0 || hsthere >= MAX_ROOM_REGIONS) return 0; + if (croom->region_enabled[hsthere] == 0) return 0; + return hsthere; +} + +void SetAreaLightLevel(int area, int brightness) { + if ((area < 0) || (area > MAX_ROOM_REGIONS)) + quit("!SetAreaLightLevel: invalid region"); + if (brightness < -100) brightness = -100; + if (brightness > 100) brightness = 100; + thisroom.Regions[area].Light = brightness; + // disable RGB tint for this area + thisroom.Regions[area].Tint = 0; + debug_script_log("Region %d light level set to %d", area, brightness); +} + +void SetRegionTint (int area, int red, int green, int blue, int amount, int luminance) +{ + if ((area < 0) || (area > MAX_ROOM_REGIONS)) + quit("!SetRegionTint: invalid region"); + + if ((red < 0) || (red > 255) || (green < 0) || (green > 255) || + (blue < 0) || (blue > 255)) { + quit("!SetRegionTint: RGB values must be 0-255"); + } + + // originally the value was passed as 0 + // TODO: find out which versions had this; fixup only for past versions in the future! + if (amount == 0) + amount = 100; + + if ((amount < 1) || (amount > 100)) + quit("!SetRegionTint: amount must be 1-100"); + if ((luminance < 0) || (luminance > 100)) + quit("!SetRegionTint: luminance must be 0-100"); + + debug_script_log("Region %d tint set to %d,%d,%d", area, red, green, blue); + + /*red -= 100; + green -= 100; + blue -= 100;*/ + + thisroom.Regions[area].Tint = (red & 0xFF) | + ((green & 0xFF) << 8) | + ((blue & 0XFF) << 16) | + ((amount & 0xFF) << 24); + thisroom.Regions[area].Light = (luminance * 25) / 10; +} + +void DisableRegion(int hsnum) { + if ((hsnum < 0) || (hsnum >= MAX_ROOM_REGIONS)) + quit("!DisableRegion: invalid region specified"); + + croom->region_enabled[hsnum] = 0; + debug_script_log("Region %d disabled", hsnum); +} + +void EnableRegion(int hsnum) { + if ((hsnum < 0) || (hsnum >= MAX_ROOM_REGIONS)) + quit("!EnableRegion: invalid region specified"); + + croom->region_enabled[hsnum] = 1; + debug_script_log("Region %d enabled", hsnum); +} + +void DisableGroundLevelAreas(int alsoEffects) { + if ((alsoEffects < 0) || (alsoEffects > 1)) + quit("!DisableGroundLevelAreas: invalid parameter: must be 0 or 1"); + + play.ground_level_areas_disabled = GLED_INTERACTION; + + if (alsoEffects) + play.ground_level_areas_disabled |= GLED_EFFECTS; + + debug_script_log("Ground-level areas disabled"); +} + +void EnableGroundLevelAreas() { + play.ground_level_areas_disabled = 0; + + debug_script_log("Ground-level areas re-enabled"); +} + +void RunRegionInteraction (int regnum, int mood) { + if ((regnum < 0) || (regnum >= MAX_ROOM_REGIONS)) + quit("!RunRegionInteraction: invalid region speicfied"); + if ((mood < 0) || (mood > 2)) + quit("!RunRegionInteraction: invalid event specified"); + + // We need a backup, because region interactions can run + // while another interaction (eg. hotspot) is in a Wait + // command, and leaving our basename would call the wrong + // script later on + const char *oldbasename = evblockbasename; + int oldblocknum = evblocknum; + + evblockbasename = "region%d"; + evblocknum = regnum; + + if (thisroom.Regions[regnum].EventHandlers != nullptr) + { + run_interaction_script(thisroom.Regions[regnum].EventHandlers.get(), mood); + } + else + { + run_interaction_event(&croom->intrRegion[regnum], mood); + } + + evblockbasename = oldbasename; + evblocknum = oldblocknum; +} diff --git a/engines/ags/engine/ac/global_region.h b/engines/ags/engine/ac/global_region.h new file mode 100644 index 00000000000..40c65e0eeb7 --- /dev/null +++ b/engines/ags/engine/ac/global_region.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALREGION_H +#define __AGS_EE_AC__GLOBALREGION_H + +// Gets region ID at the given room coordinates; +// if region is disabled or non-existing, returns 0 (no area) +int GetRegionIDAtRoom(int xxx, int yyy); +void SetAreaLightLevel(int area, int brightness); +void SetRegionTint (int area, int red, int green, int blue, int amount, int luminance = 100); +void DisableRegion(int hsnum); +void EnableRegion(int hsnum); +void DisableGroundLevelAreas(int alsoEffects); +void EnableGroundLevelAreas(); +void RunRegionInteraction (int regnum, int mood); + +#endif // __AGS_EE_AC__GLOBALREGION_H diff --git a/engines/ags/engine/ac/global_room.cpp b/engines/ags/engine/ac/global_room.cpp new file mode 100644 index 00000000000..daccbd40bb1 --- /dev/null +++ b/engines/ags/engine/ac/global_room.cpp @@ -0,0 +1,223 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_room.h" +#include "ac/common.h" +#include "ac/character.h" +#include "ac/characterinfo.h" +#include "ac/draw.h" +#include "ac/event.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_character.h" +#include "ac/global_game.h" +#include "ac/movelist.h" +#include "ac/properties.h" +#include "ac/room.h" +#include "ac/roomstatus.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "script/script.h" +#include "util/math.h" + +using namespace Common; + +extern GameState play; +extern GameSetupStruct game; +extern RoomStatus *croom; +extern CharacterInfo*playerchar; +extern int displayed_room; +extern int in_enters_screen; +extern int in_leaves_screen; +extern int in_inv_screen, inv_screen_newroom; +extern MoveList *mls; +extern int gs_to_newroom; +extern RoomStruct thisroom; + +void SetAmbientTint (int red, int green, int blue, int opacity, int luminance) { + if ((red < 0) || (green < 0) || (blue < 0) || + (red > 255) || (green > 255) || (blue > 255) || + (opacity < 0) || (opacity > 100) || + (luminance < 0) || (luminance > 100)) + quit("!SetTint: invalid parameter. R,G,B must be 0-255, opacity & luminance 0-100"); + + debug_script_log("Set ambient tint RGB(%d,%d,%d) %d%%", red, green, blue, opacity); + + play.rtint_enabled = opacity > 0; + play.rtint_red = red; + play.rtint_green = green; + play.rtint_blue = blue; + play.rtint_level = opacity; + play.rtint_light = (luminance * 25) / 10; +} + +void SetAmbientLightLevel(int light_level) +{ + light_level = Math::Clamp(light_level, -100, 100); + + play.rtint_enabled = light_level != 0; + play.rtint_level = 0; + play.rtint_light = light_level; +} + +extern ScriptPosition last_in_dialog_request_script_pos; +void NewRoom(int nrnum) { + if (nrnum < 0) + quitprintf("!NewRoom: room change requested to invalid room number %d.", nrnum); + + if (displayed_room < 0) { + // called from game_start; change the room where the game will start + playerchar->room = nrnum; + return; + } + + + debug_script_log("Room change requested to room %d", nrnum); + EndSkippingUntilCharStops(); + + can_run_delayed_command(); + + if (play.stop_dialog_at_end != DIALOG_NONE) { + if (play.stop_dialog_at_end == DIALOG_RUNNING) + play.stop_dialog_at_end = DIALOG_NEWROOM + nrnum; + else { + quitprintf("!NewRoom: two NewRoom/RunDialog/StopDialog requests within dialog; last was called in \"%s\", line %d", + last_in_dialog_request_script_pos.Section.GetCStr(), last_in_dialog_request_script_pos.Line); + } + return; + } + + get_script_position(last_in_dialog_request_script_pos); + + if (in_leaves_screen >= 0) { + // NewRoom called from the Player Leaves Screen event -- just + // change which room it will go to + in_leaves_screen = nrnum; + } + else if (in_enters_screen) { + setevent(EV_NEWROOM,nrnum); + return; + } + else if (in_inv_screen) { + inv_screen_newroom = nrnum; + return; + } + else if ((inside_script==0) & (in_graph_script==0)) { + new_room(nrnum,playerchar); + return; + } + else if (inside_script) { + curscript->queue_action(ePSANewRoom, nrnum, "NewRoom"); + // we might be within a MoveCharacterBlocking -- the room + // change should abort it + if ((playerchar->walking > 0) && (playerchar->walking < TURNING_AROUND)) { + // nasty hack - make sure it doesn't move the character + // to a walkable area + mls[playerchar->walking].direct = 1; + StopMoving(game.playercharacter); + } + } + else if (in_graph_script) + gs_to_newroom = nrnum; +} + + +void NewRoomEx(int nrnum,int newx,int newy) { + Character_ChangeRoom(playerchar, nrnum, newx, newy); +} + +void NewRoomNPC(int charid, int nrnum, int newx, int newy) { + if (!is_valid_character(charid)) + quit("!NewRoomNPC: invalid character"); + if (charid == game.playercharacter) + quit("!NewRoomNPC: use NewRoomEx with the player character"); + + Character_ChangeRoom(&game.chars[charid], nrnum, newx, newy); +} + +void ResetRoom(int nrnum) { + if (nrnum == displayed_room) + quit("!ResetRoom: cannot reset current room"); + if ((nrnum<0) | (nrnum>=MAX_ROOMS)) + quit("!ResetRoom: invalid room number"); + + if (isRoomStatusValid(nrnum)) + { + RoomStatus* roomstat = getRoomStatus(nrnum); + roomstat->FreeScriptData(); + roomstat->FreeProperties(); + roomstat->beenhere = 0; + } + + debug_script_log("Room %d reset to original state", nrnum); +} + +int HasPlayerBeenInRoom(int roomnum) { + if ((roomnum < 0) || (roomnum >= MAX_ROOMS)) + return 0; + if (isRoomStatusValid(roomnum)) + return getRoomStatus(roomnum)->beenhere; + else + return 0; +} + +void CallRoomScript (int value) { + can_run_delayed_command(); + + if (!inside_script) + quit("!CallRoomScript: not inside a script???"); + + play.roomscript_finished = 0; + RuntimeScriptValue rval_null; + curscript->run_another("on_call", kScInstRoom, 1, RuntimeScriptValue().SetInt32(value), rval_null); +} + +int HasBeenToRoom (int roomnum) { + if ((roomnum < 0) || (roomnum >= MAX_ROOMS)) + quit("!HasBeenToRoom: invalid room number specified"); + + if (isRoomStatusValid(roomnum)) + return getRoomStatus(roomnum)->beenhere; + else + return 0; +} + +void GetRoomPropertyText (const char *property, char *bufer) +{ + get_text_property(thisroom.Properties, croom->roomProps, property, bufer); +} + +void SetBackgroundFrame(int frnum) { + if ((frnum < -1) || (frnum != -1 && (size_t)frnum >= thisroom.BgFrameCount)) + quit("!SetBackgrondFrame: invalid frame number specified"); + if (frnum<0) { + play.bg_frame_locked=0; + return; + } + + play.bg_frame_locked = 1; + + if (frnum == play.bg_frame) + { + // already on this frame, do nothing + return; + } + + play.bg_frame = frnum; + on_background_frame_change (); +} + +int GetBackgroundFrame() { + return play.bg_frame; +} diff --git a/engines/ags/engine/ac/global_room.h b/engines/ags/engine/ac/global_room.h new file mode 100644 index 00000000000..4028423a17b --- /dev/null +++ b/engines/ags/engine/ac/global_room.h @@ -0,0 +1,35 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALROOM_H +#define __AGS_EE_AC__GLOBALROOM_H + +void SetAmbientTint (int red, int green, int blue, int opacity, int luminance); +void SetAmbientLightLevel(int light_level); +void NewRoom(int nrnum); +void NewRoomEx(int nrnum,int newx,int newy); +void NewRoomNPC(int charid, int nrnum, int newx, int newy); +void ResetRoom(int nrnum); +int HasPlayerBeenInRoom(int roomnum); +void CallRoomScript (int value); +int HasBeenToRoom (int roomnum); +void GetRoomPropertyText (const char *property, char *bufer); + +void SetBackgroundFrame(int frnum); +int GetBackgroundFrame() ; + +#endif // __AGS_EE_AC__GLOBALROOM_H diff --git a/engines/ags/engine/ac/global_screen.cpp b/engines/ags/engine/ac/global_screen.cpp new file mode 100644 index 00000000000..2a77a129146 --- /dev/null +++ b/engines/ags/engine/ac/global_screen.cpp @@ -0,0 +1,180 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/gamesetup.h" +#include "ac/draw.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_game.h" +#include "ac/global_screen.h" +#include "ac/runtime_defines.h" +#include "ac/screen.h" +#include "debug/debug_log.h" +#include "platform/base/agsplatformdriver.h" +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetup usetup; +extern GameState play; +extern GameSetupStruct game; +extern RoomStruct thisroom; +extern IGraphicsDriver *gfxDriver; +extern AGSPlatformDriver *platform; +extern color palette[256]; +extern unsigned int loopcounter; + +void FlipScreen(int amount) { + if ((amount<0) | (amount>3)) quit("!FlipScreen: invalid argument (0-3)"); + play.screen_flipped=amount; +} + +void ShakeScreen(int severe) { + EndSkippingUntilCharStops(); + + if (play.fast_forward) + return; + + severe = data_to_game_coord(severe); + + // TODO: support shaking room viewport separately + // TODO: rely on game speed setting? and/or provide frequency and duration args + // TODO: unify blocking and non-blocking shake update + + play.shakesc_length = 10; + play.shakesc_delay = 2; + play.shakesc_amount = severe; + play.mouse_cursor_hidden++; + + if (gfxDriver->RequiresFullRedrawEachFrame()) + { + for (int hh = 0; hh < 40; hh++) + { + loopcounter++; + platform->Delay(50); + + render_graphics(); + + update_polled_stuff_if_runtime(); + } + } + else + { + // Optimized variant for software render: create game scene once and shake it + construct_game_scene(); + gfxDriver->RenderToBackBuffer(); + for (int hh = 0; hh < 40; hh++) + { + platform->Delay(50); + const int yoff = hh % 2 == 0 ? 0 : severe; + play.shake_screen_yoff = yoff; + render_to_screen(); + update_polled_stuff_if_runtime(); + } + clear_letterbox_borders(); + render_to_screen(); + } + + play.mouse_cursor_hidden--; + play.shakesc_length = 0; + play.shakesc_delay = 0; + play.shakesc_amount = 0; +} + +void ShakeScreenBackground (int delay, int amount, int length) { + if (delay < 2) + quit("!ShakeScreenBackground: invalid delay parameter"); + + amount = data_to_game_coord(amount); + + if (amount < play.shakesc_amount) + { + // from a bigger to smaller shake, clear up the borders + clear_letterbox_borders(); + } + + play.shakesc_amount = amount; + play.shakesc_delay = delay; + play.shakesc_length = length; +} + +void TintScreen(int red, int grn, int blu) { + if ((red < 0) || (grn < 0) || (blu < 0) || (red > 100) || (grn > 100) || (blu > 100)) + quit("!TintScreen: RGB values must be 0-100"); + + invalidate_screen(); + + if ((red == 0) && (grn == 0) && (blu == 0)) { + play.screen_tint = -1; + return; + } + red = (red * 25) / 10; + grn = (grn * 25) / 10; + blu = (blu * 25) / 10; + play.screen_tint = red + (grn << 8) + (blu << 16); +} + +void my_fade_out(int spdd) { + EndSkippingUntilCharStops(); + + if (play.fast_forward) + return; + + if (play.screen_is_faded_out == 0) + gfxDriver->FadeOut(spdd, play.fade_to_red, play.fade_to_green, play.fade_to_blue); + + if (game.color_depth > 1) + play.screen_is_faded_out = 1; +} + +void SetScreenTransition(int newtrans) { + if ((newtrans < 0) || (newtrans > FADE_LAST)) + quit("!SetScreenTransition: invalid transition type"); + + play.fade_effect = newtrans; + + debug_script_log("Screen transition changed"); +} + +void SetNextScreenTransition(int newtrans) { + if ((newtrans < 0) || (newtrans > FADE_LAST)) + quit("!SetNextScreenTransition: invalid transition type"); + + play.next_screen_transition = newtrans; + + debug_script_log("SetNextScreenTransition engaged"); +} + +void SetFadeColor(int red, int green, int blue) { + if ((red < 0) || (red > 255) || (green < 0) || (green > 255) || + (blue < 0) || (blue > 255)) + quit("!SetFadeColor: Red, Green and Blue must be 0-255"); + + play.fade_to_red = red; + play.fade_to_green = green; + play.fade_to_blue = blue; +} + +void FadeIn(int sppd) { + EndSkippingUntilCharStops(); + + if (play.fast_forward) + return; + + my_fade_in(palette,sppd); +} diff --git a/engines/ags/engine/ac/global_screen.h b/engines/ags/engine/ac/global_screen.h new file mode 100644 index 00000000000..7158285d8a3 --- /dev/null +++ b/engines/ags/engine/ac/global_screen.h @@ -0,0 +1,31 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALSCREEN_H +#define __AGS_EE_AC__GLOBALSCREEN_H + +void FlipScreen(int amount); +void ShakeScreen(int severe); +void ShakeScreenBackground (int delay, int amount, int length); +void TintScreen(int red, int grn, int blu); +void my_fade_out(int spdd); +void SetScreenTransition(int newtrans); +void SetNextScreenTransition(int newtrans); +void SetFadeColor(int red, int green, int blue); +void FadeIn(int sppd); + +#endif // __AGS_EE_AC__GLOBALSCREEN_H diff --git a/engines/ags/engine/ac/global_slider.cpp b/engines/ags/engine/ac/global_slider.cpp new file mode 100644 index 00000000000..613f2a1283a --- /dev/null +++ b/engines/ags/engine/ac/global_slider.cpp @@ -0,0 +1,42 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_slider.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/slider.h" +#include "gui/guimain.h" +#include "gui/guislider.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; + +void SetSliderValue(int guin,int objn, int valn) { + if ((guin<0) | (guin>=game.numgui)) quit("!SetSliderValue: invalid GUI number"); + if (guis[guin].GetControlType(objn)!=kGUISlider) + quit("!SetSliderValue: specified control is not a slider"); + + GUISlider*guisl=(GUISlider*)guis[guin].GetControl(objn); + Slider_SetValue(guisl, valn); +} + +int GetSliderValue(int guin,int objn) { + if ((guin<0) | (guin>=game.numgui)) quit("!GetSliderValue: invalid GUI number"); + if (guis[guin].GetControlType(objn)!=kGUISlider) + quit("!GetSliderValue: specified control is not a slider"); + + GUISlider*guisl=(GUISlider*)guis[guin].GetControl(objn); + return Slider_GetValue(guisl); +} diff --git a/engines/ags/engine/ac/global_slider.h b/engines/ags/engine/ac/global_slider.h new file mode 100644 index 00000000000..27e0edf9b34 --- /dev/null +++ b/engines/ags/engine/ac/global_slider.h @@ -0,0 +1,24 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALSLIDER_H +#define __AGS_EE_AC__GLOBALSLIDER_H + +void SetSliderValue(int guin,int objn, int valn); +int GetSliderValue(int guin,int objn); + +#endif // __AGS_EE_AC__GLOBALSLIDER_H diff --git a/engines/ags/engine/ac/global_string.cpp b/engines/ags/engine/ac/global_string.cpp new file mode 100644 index 00000000000..7851ae91d21 --- /dev/null +++ b/engines/ags/engine/ac/global_string.cpp @@ -0,0 +1,73 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/common.h" +#include "ac/global_string.h" +#include "ac/global_translation.h" +#include "ac/runtime_defines.h" +#include "ac/string.h" +#include "util/string_compat.h" + +extern int MAXSTRLEN; + +int StrGetCharAt (const char *strin, int posn) { + if ((posn < 0) || (posn >= (int)strlen(strin))) + return 0; + return strin[posn]; +} + +void StrSetCharAt (char *strin, int posn, int nchar) { + if ((posn < 0) || (posn > (int)strlen(strin)) || (posn >= MAX_MAXSTRLEN)) + quit("!StrSetCharAt: tried to write past end of string"); + + if (posn == (int)strlen(strin)) + strin[posn+1] = 0; + strin[posn] = nchar; +} + +void _sc_strcat(char*s1, const char*s2) { + // make sure they don't try to append a char to the string + VALIDATE_STRING (s2); + check_strlen(s1); + int mosttocopy=(MAXSTRLEN-strlen(s1))-1; + // int numbf=game.iface[4].numbuttons; + my_strncpy(&s1[strlen(s1)], s2, mosttocopy); +} + +void _sc_strlower (char *desbuf) { + VALIDATE_STRING(desbuf); + check_strlen (desbuf); + ags_strlwr (desbuf); +} + +void _sc_strupper (char *desbuf) { + VALIDATE_STRING(desbuf); + check_strlen (desbuf); + ags_strupr (desbuf); +} + +/*int _sc_strcmp (char *s1, char *s2) { +return strcmp (get_translation (s1), get_translation(s2)); +} + +int _sc_stricmp (char *s1, char *s2) { +return ags_stricmp (get_translation (s1), get_translation(s2)); +}*/ + +void _sc_strcpy(char*destt, const char *text) { + VALIDATE_STRING(destt); + check_strlen(destt); + my_strncpy(destt, text, MAXSTRLEN - 1); +} diff --git a/engines/ags/engine/ac/global_string.h b/engines/ags/engine/ac/global_string.h new file mode 100644 index 00000000000..91966661916 --- /dev/null +++ b/engines/ags/engine/ac/global_string.h @@ -0,0 +1,28 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALSTRING_H +#define __AGS_EE_AC__GLOBALSTRING_H + +int StrGetCharAt (const char *strin, int posn); +void StrSetCharAt (char *strin, int posn, int nchar); +void _sc_strcat(char*s1, const char*s2); +void _sc_strlower (char *desbuf); +void _sc_strupper (char *desbuf); +void _sc_strcpy(char*destt, const char*text); + +#endif // __AGS_EE_AC__GLOBALSTRING_H diff --git a/engines/ags/engine/ac/global_textbox.cpp b/engines/ags/engine/ac/global_textbox.cpp new file mode 100644 index 00000000000..672c0807e2a --- /dev/null +++ b/engines/ags/engine/ac/global_textbox.cpp @@ -0,0 +1,57 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_textbox.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/string.h" +#include "ac/textbox.h" +#include "gui/guimain.h" +#include "gui/guitextbox.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; + +void SetTextBoxFont(int guin,int objn, int fontnum) { + + if ((guin<0) | (guin>=game.numgui)) quit("!SetTextBoxFont: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) quit("!SetTextBoxFont: invalid object number"); + if (guis[guin].GetControlType(objn) != kGUITextBox) + quit("!SetTextBoxFont: specified control is not a text box"); + + GUITextBox *guit = (GUITextBox*)guis[guin].GetControl(objn); + TextBox_SetFont(guit, fontnum); +} + +void GetTextBoxText(int guin, int objn, char*txbuf) { + VALIDATE_STRING(txbuf); + if ((guin<0) | (guin>=game.numgui)) quit("!GetTextBoxText: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) quit("!GetTextBoxText: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUITextBox) + quit("!GetTextBoxText: specified control is not a text box"); + + GUITextBox*guisl=(GUITextBox*)guis[guin].GetControl(objn); + TextBox_GetText(guisl, txbuf); +} + +void SetTextBoxText(int guin, int objn, const char* txbuf) { + if ((guin<0) | (guin>=game.numgui)) quit("!SetTextBoxText: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) quit("!SetTextBoxText: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUITextBox) + quit("!SetTextBoxText: specified control is not a text box"); + + GUITextBox*guisl=(GUITextBox*)guis[guin].GetControl(objn); + TextBox_SetText(guisl, txbuf); +} diff --git a/engines/ags/engine/ac/global_textbox.h b/engines/ags/engine/ac/global_textbox.h new file mode 100644 index 00000000000..1962f4f30c2 --- /dev/null +++ b/engines/ags/engine/ac/global_textbox.h @@ -0,0 +1,25 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALTEXTBOX_H +#define __AGS_EE_AC__GLOBALTEXTBOX_H + +void SetTextBoxFont(int guin,int objn, int fontnum); +void GetTextBoxText(int guin, int objn, char*txbuf); +void SetTextBoxText(int guin, int objn, const char*txbuf); + +#endif // __AGS_EE_AC__GLOBALTEXTBOX_H diff --git a/engines/ags/engine/ac/global_timer.cpp b/engines/ags/engine/ac/global_timer.cpp new file mode 100644 index 00000000000..4f197d889a7 --- /dev/null +++ b/engines/ags/engine/ac/global_timer.cpp @@ -0,0 +1,37 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_timer.h" +#include "ac/runtime_defines.h" +#include "ac/common.h" +#include "ac/gamestate.h" + +extern GameState play; + + +void script_SetTimer(int tnum,int timeout) { + if ((tnum < 1) || (tnum >= MAX_TIMERS)) + quit("!StartTimer: invalid timer number"); + play.script_timers[tnum] = timeout; +} + +int IsTimerExpired(int tnum) { + if ((tnum < 1) || (tnum >= MAX_TIMERS)) + quit("!IsTimerExpired: invalid timer number"); + if (play.script_timers[tnum] == 1) { + play.script_timers[tnum] = 0; + return 1; + } + return 0; +} diff --git a/engines/ags/engine/ac/global_timer.h b/engines/ags/engine/ac/global_timer.h new file mode 100644 index 00000000000..589a97ee8d5 --- /dev/null +++ b/engines/ags/engine/ac/global_timer.h @@ -0,0 +1,24 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALTIMER_H +#define __AGS_EE_AC__GLOBALTIMER_H + +void script_SetTimer(int tnum,int timeout); +int IsTimerExpired(int tnum); + +#endif // __AGS_EE_AC__GLOBALTIMER_H diff --git a/engines/ags/engine/ac/global_translation.cpp b/engines/ags/engine/ac/global_translation.cpp new file mode 100644 index 00000000000..f793e111943 --- /dev/null +++ b/engines/ags/engine/ac/global_translation.cpp @@ -0,0 +1,85 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/common.h" +#include "ac/display.h" +#include "ac/gamestate.h" +#include "ac/global_translation.h" +#include "ac/string.h" +#include "ac/tree_map.h" +#include "platform/base/agsplatformdriver.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "util/memory.h" +#include "core/types.h" + +using namespace AGS::Common::Memory; + +extern GameState play; +extern AGSPlatformDriver *platform; +extern TreeMap *transtree; +extern char transFileName[MAX_PATH]; + +const char *get_translation (const char *text) { + if (text == nullptr) + quit("!Null string supplied to CheckForTranslations"); + + source_text_length = GetTextDisplayLength(text); + +#if AGS_PLATFORM_64BIT + // check if a plugin wants to translate it - if so, return that + // TODO: plugin API is currently strictly 32-bit, so this may break on 64-bit systems + char *plResult = Int32ToPtr(pl_run_plugin_hooks(AGSE_TRANSLATETEXT, PtrToInt32(text))); + if (plResult) { + return plResult; + } +#endif + + if (transtree != nullptr) { + // translate the text using the translation file + char * transl = transtree->findValue (text); + if (transl != nullptr) + return transl; + } + // return the original text + return text; +} + +int IsTranslationAvailable () { + if (transtree != nullptr) + return 1; + return 0; +} + +int GetTranslationName (char* buffer) { + VALIDATE_STRING (buffer); + const char *copyFrom = transFileName; + + while (strchr(copyFrom, '\\') != nullptr) + { + copyFrom = strchr(copyFrom, '\\') + 1; + } + while (strchr(copyFrom, '/') != nullptr) + { + copyFrom = strchr(copyFrom, '/') + 1; + } + + strcpy (buffer, copyFrom); + // remove the ".tra" from the end of the filename + if (strstr (buffer, ".tra") != nullptr) + strstr (buffer, ".tra")[0] = 0; + + return IsTranslationAvailable(); +} diff --git a/engines/ags/engine/ac/global_translation.h b/engines/ags/engine/ac/global_translation.h new file mode 100644 index 00000000000..b506fd9498a --- /dev/null +++ b/engines/ags/engine/ac/global_translation.h @@ -0,0 +1,25 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALTRANSLATION_H +#define __AGS_EE_AC__GLOBALTRANSLATION_H + +const char *get_translation (const char *text); +int IsTranslationAvailable (); +int GetTranslationName (char* buffer); + +#endif // __AGS_EE_AC__GLOBALTRANSLATION_H diff --git a/engines/ags/engine/ac/global_video.cpp b/engines/ags/engine/ac/global_video.cpp new file mode 100644 index 00000000000..438fcc83371 --- /dev/null +++ b/engines/ags/engine/ac/global_video.cpp @@ -0,0 +1,85 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/gamesetup.h" +#include "ac/gamestate.h" +#include "ac/global_audio.h" +#include "ac/global_game.h" +#include "ac/global_video.h" +#include "ac/path_helper.h" +#include "debug/debugger.h" +#include "media/video/video.h" +#include "media/audio/audio_system.h" +#include "platform/base/agsplatformdriver.h" +#include "util/string_compat.h" + + +void scrPlayVideo(const char* name, int skip, int flags) { + EndSkippingUntilCharStops(); + + if (play.fast_forward) + return; + if (debug_flags & DBG_NOVIDEO) + return; + + if ((flags < 10) && (usetup.digicard == DIGI_NONE)) { + // if game audio is disabled in Setup, then don't + // play any sound on the video either + flags += 10; + } + + pause_sound_if_necessary_and_play_video(name, skip, flags); +} + + +#ifndef AGS_NO_VIDEO_PLAYER + +void pause_sound_if_necessary_and_play_video(const char *name, int skip, int flags) +{ + int musplaying = play.cur_music_number, i; + int ambientWas[MAX_SOUND_CHANNELS]; + for (i = 1; i < MAX_SOUND_CHANNELS; i++) + ambientWas[i] = ambient[i].channel; + + if ((strlen(name) > 3) && (ags_stricmp(&name[strlen(name) - 3], "ogv") == 0)) + { + play_theora_video(name, skip, flags); + } + else + { + char videoFilePath[MAX_PATH]; + get_install_dir_path(videoFilePath, name); + + platform->PlayVideo(videoFilePath, skip, flags); + } + + if (flags < 10) + { + update_music_volume(); + // restart the music + if (musplaying >= 0) + newmusic (musplaying); + for (i = 1; i < MAX_SOUND_CHANNELS; i++) { + if (ambientWas[i] > 0) + PlayAmbientSound(ambientWas[i], ambient[i].num, ambient[i].vol, ambient[i].x, ambient[i].y); + } + } +} + +#else + +void pause_sound_if_necessary_and_play_video(const char *name, int skip, int flags) {} + +#endif \ No newline at end of file diff --git a/engines/ags/engine/ac/global_video.h b/engines/ags/engine/ac/global_video.h new file mode 100644 index 00000000000..c3fa993847b --- /dev/null +++ b/engines/ags/engine/ac/global_video.h @@ -0,0 +1,24 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALVIDEO_H +#define __AGS_EE_AC__GLOBALVIDEO_H + +void scrPlayVideo(const char* name, int skip, int flags); +void pause_sound_if_necessary_and_play_video(const char *name, int skip, int flags); + +#endif // __AGS_EE_AC__GLOBALVIDEO_H diff --git a/engines/ags/engine/ac/global_viewframe.cpp b/engines/ags/engine/ac/global_viewframe.cpp new file mode 100644 index 00000000000..81d55ca1b01 --- /dev/null +++ b/engines/ags/engine/ac/global_viewframe.cpp @@ -0,0 +1,49 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_viewframe.h" +#include "ac/common.h" +#include "ac/view.h" +#include "ac/gamesetupstruct.h" +#include "debug/debug_log.h" +#include "media/audio/audio_system.h" + +extern GameSetupStruct game; +extern ViewStruct*views; + + +void SetFrameSound (int vii, int loop, int frame, int sound) { + if ((vii < 1) || (vii > game.numviews)) + quit("!SetFrameSound: invalid view number"); + vii--; + + if (loop >= views[vii].numLoops) + quit("!SetFrameSound: invalid loop number"); + + if (frame >= views[vii].loops[loop].numFrames) + quit("!SetFrameSound: invalid frame number"); + + if (sound < 1) + { + views[vii].loops[loop].frames[frame].sound = -1; + } + else + { + ScriptAudioClip* clip = GetAudioClipForOldStyleNumber(game, false, sound); + if (clip == nullptr) + quitprintf("!SetFrameSound: audio clip aSound%d not found", sound); + + views[vii].loops[loop].frames[frame].sound = clip->id + (game.IsLegacyAudioSystem() ? 0x10000000 : 0); + } +} diff --git a/engines/ags/engine/ac/global_viewframe.h b/engines/ags/engine/ac/global_viewframe.h new file mode 100644 index 00000000000..b92c3400b70 --- /dev/null +++ b/engines/ags/engine/ac/global_viewframe.h @@ -0,0 +1,23 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALVIEWFRAME_H +#define __AGS_EE_AC__GLOBALVIEWFRAME_H + +void SetFrameSound (int vii, int loop, int frame, int sound); + +#endif // __AGS_EE_AC__GLOBALVIEWFRAME_H diff --git a/engines/ags/engine/ac/global_viewport.cpp b/engines/ags/engine/ac/global_viewport.cpp new file mode 100644 index 00000000000..ea2b1dd8a2e --- /dev/null +++ b/engines/ags/engine/ac/global_viewport.cpp @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_viewport.h" +#include "ac/draw.h" +#include "debug/debug_log.h" + +void SetViewport(int offsx, int offsy) { + offsx = data_to_game_coord(offsx); + offsy = data_to_game_coord(offsy); + play.GetRoomCamera(0)->LockAt(offsx, offsy); +} +void ReleaseViewport() { + play.GetRoomCamera(0)->Release(); +} +int GetViewportX () { + return game_to_data_coord(play.GetRoomCamera(0)->GetRect().Left); +} +int GetViewportY () { + return game_to_data_coord(play.GetRoomCamera(0)->GetRect().Top); +} diff --git a/engines/ags/engine/ac/global_viewport.h b/engines/ags/engine/ac/global_viewport.h new file mode 100644 index 00000000000..5c3903cb5e0 --- /dev/null +++ b/engines/ags/engine/ac/global_viewport.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALVIEWPORT_H +#define __AGS_EE_AC__GLOBALVIEWPORT_H + +void SetViewport(int offsx,int offsy); +void ReleaseViewport(); +int GetViewportX (); +int GetViewportY (); + +#endif // __AGS_EE_AC__GLOBAL_VIEWPORT_H diff --git a/engines/ags/engine/ac/global_walkablearea.cpp b/engines/ags/engine/ac/global_walkablearea.cpp new file mode 100644 index 00000000000..219333f3ddd --- /dev/null +++ b/engines/ags/engine/ac/global_walkablearea.cpp @@ -0,0 +1,91 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_walkablearea.h" +#include "ac/common.h" +#include "ac/common_defines.h" +#include "ac/draw.h" +#include "ac/walkablearea.h" +#include "debug/debug_log.h" +#include "game/roomstruct.h" + +using namespace AGS::Common; + +extern RoomStruct thisroom; + + +int GetScalingAt (int x, int y) { + int onarea = get_walkable_area_pixel(x, y); + if (onarea < 0) + return 100; + + return get_area_scaling (onarea, x, y); +} + +void SetAreaScaling(int area, int min, int max) { + if ((area < 0) || (area > MAX_WALK_AREAS)) + quit("!SetAreaScaling: invalid walkalbe area"); + + if (min > max) + quit("!SetAreaScaling: min > max"); + + if ((min < 5) || (max < 5) || (min > 200) || (max > 200)) + quit("!SetAreaScaling: min and max must be in range 5-200"); + + // the values are stored differently + min -= 100; + max -= 100; + + if (min == max) { + thisroom.WalkAreas[area].ScalingFar = min; + thisroom.WalkAreas[area].ScalingNear = NOT_VECTOR_SCALED; + } + else { + thisroom.WalkAreas[area].ScalingFar = min; + thisroom.WalkAreas[area].ScalingNear = max; + } +} + +void RemoveWalkableArea(int areanum) { + if ((areanum<1) | (areanum>15)) + quit("!RemoveWalkableArea: invalid area number specified (1-15)."); + play.walkable_areas_on[areanum]=0; + redo_walkable_areas(); + debug_script_log("Walkable area %d removed", areanum); +} + +void RestoreWalkableArea(int areanum) { + if ((areanum<1) | (areanum>15)) + quit("!RestoreWalkableArea: invalid area number specified (1-15)."); + play.walkable_areas_on[areanum]=1; + redo_walkable_areas(); + debug_script_log("Walkable area %d restored", areanum); +} + +int GetWalkableAreaAtScreen(int x, int y) { + VpPoint vpt = play.ScreenToRoomDivDown(x, y); + if (vpt.second < 0) + return 0; + return GetWalkableAreaAtRoom(vpt.first.X, vpt.first.Y); +} + +int GetWalkableAreaAtRoom(int x, int y) { + int area = get_walkable_area_pixel(x, y); + // IMPORTANT: disabled walkable areas are actually erased completely from the mask; + // see: RemoveWalkableArea() and RestoreWalkableArea(). + return area >= 0 && area < (MAX_WALK_AREAS + 1) ? area : 0; +} + +//============================================================================= + diff --git a/engines/ags/engine/ac/global_walkablearea.h b/engines/ags/engine/ac/global_walkablearea.h new file mode 100644 index 00000000000..87f6ab811d8 --- /dev/null +++ b/engines/ags/engine/ac/global_walkablearea.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALWALKABLEAREA_H +#define __AGS_EE_AC__GLOBALWALKABLEAREA_H + +int GetScalingAt (int x, int y); +void SetAreaScaling(int area, int min, int max); +void RemoveWalkableArea(int areanum); +void RestoreWalkableArea(int areanum); +// Gets walkable area at the given room coordinates; +// if area is disabled or non-existing, returns 0 (no area) +int GetWalkableAreaAtRoom(int x, int y); +// Gets walkable area at the given screen coordinates +// if area is disabled or non-existing, returns 0 (no area) +int GetWalkableAreaAtScreen(int x, int y); + +#endif // __AGS_EE_AC__GLOBALWALKABLEAREA_H diff --git a/engines/ags/engine/ac/global_walkbehind.cpp b/engines/ags/engine/ac/global_walkbehind.cpp new file mode 100644 index 00000000000..4e0181a475a --- /dev/null +++ b/engines/ags/engine/ac/global_walkbehind.cpp @@ -0,0 +1,36 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/global_walkbehind.h" +#include "ac/common.h" +#include "ac/common_defines.h" +#include "ac/draw.h" +#include "ac/roomstatus.h" +#include "ac/walkbehind.h" +#include "debug/debug_log.h" + +extern RoomStatus*croom; +extern int walk_behind_baselines_changed; + +void SetWalkBehindBase(int wa,int bl) { + if ((wa < 1) || (wa >= MAX_WALK_BEHINDS)) + quit("!SetWalkBehindBase: invalid walk-behind area specified"); + + if (bl != croom->walkbehind_base[wa]) { + walk_behind_baselines_changed = 1; + invalidate_cached_walkbehinds(); + croom->walkbehind_base[wa] = bl; + debug_script_log("Walk-behind %d baseline changed to %d", wa, bl); + } +} diff --git a/engines/ags/engine/ac/global_walkbehind.h b/engines/ags/engine/ac/global_walkbehind.h new file mode 100644 index 00000000000..acf56b9472f --- /dev/null +++ b/engines/ags/engine/ac/global_walkbehind.h @@ -0,0 +1,23 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GLOBALWALKBEHIND_H +#define __AGS_EE_AC__GLOBALWALKBEHIND_H + +void SetWalkBehindBase(int wa,int bl); + +#endif // __AGS_EE_AC__GLOBALWALKBEHIND_H diff --git a/engines/ags/engine/ac/gui.cpp b/engines/ags/engine/ac/gui.cpp new file mode 100644 index 00000000000..330674102d1 --- /dev/null +++ b/engines/ags/engine/ac/gui.cpp @@ -0,0 +1,1001 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/gui.h" +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/event.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_character.h" +#include "ac/global_game.h" +#include "ac/global_gui.h" +#include "ac/global_inventoryitem.h" +#include "ac/global_screen.h" +#include "ac/guicontrol.h" +#include "ac/interfacebutton.h" +#include "ac/invwindow.h" +#include "ac/mouse.h" +#include "ac/runtime_defines.h" +#include "ac/system.h" +#include "ac/dynobj/cc_guiobject.h" +#include "ac/dynobj/scriptgui.h" +#include "script/cc_instance.h" +#include "debug/debug_log.h" +#include "device/mousew32.h" +#include "gfx/gfxfilter.h" +#include "gui/guibutton.h" +#include "gui/guimain.h" +#include "script/script.h" +#include "script/script_runtime.h" +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" +#include "ac/dynobj/cc_gui.h" +#include "ac/dynobj/cc_guiobject.h" +#include "script/runtimescriptvalue.h" +#include "util/string_compat.h" + + +using namespace AGS::Common; +using namespace AGS::Engine; + + +extern GameSetup usetup; +extern RoomStruct thisroom; +extern int cur_mode,cur_cursor; +extern ccInstance *gameinst; +extern ScriptGUI *scrGui; +extern GameSetupStruct game; +extern CCGUIObject ccDynamicGUIObject; +extern Bitmap **guibg; +extern IDriverDependantBitmap **guibgbmp; +extern IGraphicsDriver *gfxDriver; + +extern CCGUI ccDynamicGUI; +extern CCGUIObject ccDynamicGUIObject; + + +int ifacepopped=-1; // currently displayed pop-up GUI (-1 if none) +int mouse_on_iface=-1; // mouse cursor is over this interface +int mouse_ifacebut_xoffs=-1,mouse_ifacebut_yoffs=-1; + +int eip_guinum, eip_guiobj; + + +ScriptGUI* GUI_AsTextWindow(ScriptGUI *tehgui) +{ // Internally both GUI and TextWindow are implemented by same class + return guis[tehgui->id].IsTextWindow() ? &scrGui[tehgui->id] : nullptr; +} + +int GUI_GetPopupStyle(ScriptGUI *tehgui) +{ + return guis[tehgui->id].PopupStyle; +} + +void GUI_SetVisible(ScriptGUI *tehgui, int isvisible) { + if (isvisible) + InterfaceOn(tehgui->id); + else + InterfaceOff(tehgui->id); +} + +int GUI_GetVisible(ScriptGUI *tehgui) { + // GUI_GetVisible is slightly different from IsGUIOn, because + // with a mouse ypos gui it returns 1 if the GUI is enabled, + // whereas IsGUIOn actually checks if it is displayed + return guis[tehgui->id].IsVisible() ? 1 : 0; +} + +int GUI_GetX(ScriptGUI *tehgui) { + return game_to_data_coord(guis[tehgui->id].X); +} + +void GUI_SetX(ScriptGUI *tehgui, int xx) { + guis[tehgui->id].X = data_to_game_coord(xx); +} + +int GUI_GetY(ScriptGUI *tehgui) { + return game_to_data_coord(guis[tehgui->id].Y); +} + +void GUI_SetY(ScriptGUI *tehgui, int yy) { + guis[tehgui->id].Y = data_to_game_coord(yy); +} + +void GUI_SetPosition(ScriptGUI *tehgui, int xx, int yy) { + GUI_SetX(tehgui, xx); + GUI_SetY(tehgui, yy); +} + +void GUI_SetSize(ScriptGUI *sgui, int widd, int hitt) { + if ((widd < 1) || (hitt < 1)) + quitprintf("!SetGUISize: invalid dimensions (tried to set to %d x %d)", widd, hitt); + + GUIMain *tehgui = &guis[sgui->id]; + data_to_game_coords(&widd, &hitt); + + if ((tehgui->Width == widd) && (tehgui->Height == hitt)) + return; + + tehgui->Width = widd; + tehgui->Height = hitt; + + recreate_guibg_image(tehgui); + + guis_need_update = 1; +} + +int GUI_GetWidth(ScriptGUI *sgui) { + return game_to_data_coord(guis[sgui->id].Width); +} + +int GUI_GetHeight(ScriptGUI *sgui) { + return game_to_data_coord(guis[sgui->id].Height); +} + +void GUI_SetWidth(ScriptGUI *sgui, int newwid) { + GUI_SetSize(sgui, newwid, GUI_GetHeight(sgui)); +} + +void GUI_SetHeight(ScriptGUI *sgui, int newhit) { + GUI_SetSize(sgui, GUI_GetWidth(sgui), newhit); +} + +void GUI_SetZOrder(ScriptGUI *tehgui, int z) { + guis[tehgui->id].ZOrder = z; + update_gui_zorder(); +} + +int GUI_GetZOrder(ScriptGUI *tehgui) { + return guis[tehgui->id].ZOrder; +} + +void GUI_SetClickable(ScriptGUI *tehgui, int clickable) { + guis[tehgui->id].SetClickable(clickable != 0); +} + +int GUI_GetClickable(ScriptGUI *tehgui) { + return guis[tehgui->id].IsClickable() ? 1 : 0; +} + +int GUI_GetID(ScriptGUI *tehgui) { + return tehgui->id; +} + +GUIObject* GUI_GetiControls(ScriptGUI *tehgui, int idx) { + if ((idx < 0) || (idx >= guis[tehgui->id].GetControlCount())) + return nullptr; + return guis[tehgui->id].GetControl(idx); +} + +int GUI_GetControlCount(ScriptGUI *tehgui) { + return guis[tehgui->id].GetControlCount(); +} + +int GUI_GetPopupYPos(ScriptGUI *tehgui) +{ + return guis[tehgui->id].PopupAtMouseY; +} + +void GUI_SetPopupYPos(ScriptGUI *tehgui, int newpos) +{ + if (!guis[tehgui->id].IsTextWindow()) + guis[tehgui->id].PopupAtMouseY = newpos; +} + +void GUI_SetTransparency(ScriptGUI *tehgui, int trans) { + if ((trans < 0) | (trans > 100)) + quit("!SetGUITransparency: transparency value must be between 0 and 100"); + + guis[tehgui->id].SetTransparencyAsPercentage(trans); +} + +int GUI_GetTransparency(ScriptGUI *tehgui) { + if (guis[tehgui->id].Transparency == 0) + return 0; + if (guis[tehgui->id].Transparency == 255) + return 100; + + return 100 - ((guis[tehgui->id].Transparency * 10) / 25); +} + +void GUI_Centre(ScriptGUI *sgui) { + GUIMain *tehgui = &guis[sgui->id]; + tehgui->X = play.GetUIViewport().GetWidth() / 2 - tehgui->Width / 2; + tehgui->Y = play.GetUIViewport().GetHeight() / 2 - tehgui->Height / 2; +} + +void GUI_SetBackgroundGraphic(ScriptGUI *tehgui, int slotn) { + if (guis[tehgui->id].BgImage != slotn) { + guis[tehgui->id].BgImage = slotn; + guis_need_update = 1; + } +} + +int GUI_GetBackgroundGraphic(ScriptGUI *tehgui) { + if (guis[tehgui->id].BgImage < 1) + return 0; + return guis[tehgui->id].BgImage; +} + +void GUI_SetBackgroundColor(ScriptGUI *tehgui, int newcol) +{ + if (guis[tehgui->id].BgColor != newcol) + { + guis[tehgui->id].BgColor = newcol; + guis_need_update = 1; + } +} + +int GUI_GetBackgroundColor(ScriptGUI *tehgui) +{ + return guis[tehgui->id].BgColor; +} + +void GUI_SetBorderColor(ScriptGUI *tehgui, int newcol) +{ + if (guis[tehgui->id].IsTextWindow()) + return; + if (guis[tehgui->id].FgColor != newcol) + { + guis[tehgui->id].FgColor = newcol; + guis_need_update = 1; + } +} + +int GUI_GetBorderColor(ScriptGUI *tehgui) +{ + if (guis[tehgui->id].IsTextWindow()) + return 0; + return guis[tehgui->id].FgColor; +} + +void GUI_SetTextColor(ScriptGUI *tehgui, int newcol) +{ + if (!guis[tehgui->id].IsTextWindow()) + return; + if (guis[tehgui->id].FgColor != newcol) + { + guis[tehgui->id].FgColor = newcol; + guis_need_update = 1; + } +} + +int GUI_GetTextColor(ScriptGUI *tehgui) +{ + if (!guis[tehgui->id].IsTextWindow()) + return 0; + return guis[tehgui->id].FgColor; +} + +int GUI_GetTextPadding(ScriptGUI *tehgui) +{ + return guis[tehgui->id].Padding; +} + +void GUI_SetTextPadding(ScriptGUI *tehgui, int newpos) +{ + if (guis[tehgui->id].IsTextWindow()) + guis[tehgui->id].Padding = newpos; +} + +ScriptGUI *GetGUIAtLocation(int xx, int yy) { + int guiid = GetGUIAt(xx, yy); + if (guiid < 0) + return nullptr; + return &scrGui[guiid]; +} + +void GUI_Click(ScriptGUI *scgui, int mbut) +{ + process_interface_click(scgui->id, -1, mbut); +} + +void GUI_ProcessClick(int x, int y, int mbut) +{ + int guiid = gui_get_interactable(x, y); + if (guiid >= 0) + { + const int real_mousex = mousex; + const int real_mousey = mousey; + mousex = x; + mousey = y; + guis[guiid].Poll(); + gui_on_mouse_down(guiid, mbut); + gui_on_mouse_up(guiid, mbut); + mousex = real_mousex; + mousey = real_mousey; + } +} + +//============================================================================= + +void remove_popup_interface(int ifacenum) { + if (ifacepopped != ifacenum) return; + ifacepopped=-1; UnPauseGame(); + guis[ifacenum].SetConceal(true); + if (mousey<=guis[ifacenum].PopupAtMouseY) + Mouse::SetPosition(Point(mousex, guis[ifacenum].PopupAtMouseY+2)); + if ((!IsInterfaceEnabled()) && (cur_cursor == cur_mode)) + // Only change the mouse cursor if it hasn't been specifically changed first + set_mouse_cursor(CURS_WAIT); + else if (IsInterfaceEnabled()) + set_default_cursor(); + + if (ifacenum==mouse_on_iface) mouse_on_iface=-1; + guis_need_update = 1; +} + +void process_interface_click(int ifce, int btn, int mbut) { + if (btn < 0) { + // click on GUI background + QueueScriptFunction(kScInstGame, guis[ifce].OnClickHandler, 2, + RuntimeScriptValue().SetDynamicObject(&scrGui[ifce], &ccDynamicGUI), + RuntimeScriptValue().SetInt32(mbut)); + return; + } + + int btype = guis[ifce].GetControlType(btn); + int rtype=kGUIAction_None,rdata; + if (btype==kGUIButton) { + GUIButton*gbuto=(GUIButton*)guis[ifce].GetControl(btn); + rtype=gbuto->ClickAction[kMouseLeft]; + rdata=gbuto->ClickData[kMouseLeft]; + } + else if ((btype==kGUISlider) || (btype == kGUITextBox) || (btype == kGUIListBox)) + rtype = kGUIAction_RunScript; + else quit("unknown GUI object triggered process_interface"); + + if (rtype==kGUIAction_None) ; + else if (rtype==kGUIAction_SetMode) + set_cursor_mode(rdata); + else if (rtype==kGUIAction_RunScript) { + GUIObject *theObj = guis[ifce].GetControl(btn); + // if the object has a special handler script then run it; + // otherwise, run interface_click + if ((theObj->GetEventCount() > 0) && + (!theObj->EventHandlers[0].IsEmpty()) && + (!gameinst->GetSymbolAddress(theObj->EventHandlers[0]).IsNull())) { + // control-specific event handler + if (strchr(theObj->GetEventArgs(0), ',') != nullptr) + QueueScriptFunction(kScInstGame, theObj->EventHandlers[0], 2, + RuntimeScriptValue().SetDynamicObject(theObj, &ccDynamicGUIObject), + RuntimeScriptValue().SetInt32(mbut)); + else + QueueScriptFunction(kScInstGame, theObj->EventHandlers[0], 1, + RuntimeScriptValue().SetDynamicObject(theObj, &ccDynamicGUIObject)); + } + else + QueueScriptFunction(kScInstGame, "interface_click", 2, + RuntimeScriptValue().SetInt32(ifce), + RuntimeScriptValue().SetInt32(btn)); + } +} + + +void replace_macro_tokens(const char *text, String &fixed_text) { + const char*curptr=&text[0]; + char tmpm[3]; + const char*endat = curptr + strlen(text); + fixed_text.Empty(); + char tempo[STD_BUFFER_SIZE]; + + while (1) { + if (curptr[0]==0) break; + if (curptr>=endat) break; + if (curptr[0]=='@') { + const char *curptrWasAt = curptr; + char macroname[21]; int idd=0; curptr++; + for (idd=0;idd<20;idd++) { + if (curptr[0]=='@') { + macroname[idd]=0; + curptr++; + break; + } + // unterminated macro (eg. "@SCORETEXT"), so abort + if (curptr[0] == 0) + break; + macroname[idd]=curptr[0]; + curptr++; + } + macroname[idd]=0; + tempo[0]=0; + if (ags_stricmp(macroname,"score")==0) + sprintf(tempo,"%d",play.score); + else if (ags_stricmp(macroname,"totalscore")==0) + sprintf(tempo,"%d",MAXSCORE); + else if (ags_stricmp(macroname,"scoretext")==0) + sprintf(tempo,"%d of %d",play.score,MAXSCORE); + else if (ags_stricmp(macroname,"gamename")==0) + strcpy(tempo, play.game_name); + else if (ags_stricmp(macroname,"overhotspot")==0) { + // While game is in Wait mode, no overhotspot text + if (!IsInterfaceEnabled()) + tempo[0] = 0; + else + GetLocationName(game_to_data_coord(mousex), game_to_data_coord(mousey), tempo); + } + else { // not a macro, there's just a @ in the message + curptr = curptrWasAt + 1; + strcpy(tempo, "@"); + } + + fixed_text.Append(tempo); + } + else { + tmpm[0]=curptr[0]; tmpm[1]=0; + fixed_text.Append(tmpm); + curptr++; + } + } +} + + +void update_gui_zorder() { + int numdone = 0, b; + + // for each GUI + for (int a = 0; a < game.numgui; a++) { + // find the right place in the draw order array + int insertAt = numdone; + for (b = 0; b < numdone; b++) { + if (guis[a].ZOrder < guis[play.gui_draw_order[b]].ZOrder) { + insertAt = b; + break; + } + } + // insert the new item + for (b = numdone - 1; b >= insertAt; b--) + play.gui_draw_order[b + 1] = play.gui_draw_order[b]; + play.gui_draw_order[insertAt] = a; + numdone++; + } + +} + + +void export_gui_controls(int ee) +{ + for (int ff = 0; ff < guis[ee].GetControlCount(); ff++) + { + GUIObject *guio = guis[ee].GetControl(ff); + if (!guio->Name.IsEmpty()) + ccAddExternalDynamicObject(guio->Name, guio, &ccDynamicGUIObject); + ccRegisterManagedObject(guio, &ccDynamicGUIObject); + } +} + +void unexport_gui_controls(int ee) +{ + for (int ff = 0; ff < guis[ee].GetControlCount(); ff++) + { + GUIObject *guio = guis[ee].GetControl(ff); + if (!guio->Name.IsEmpty()) + ccRemoveExternalSymbol(guio->Name); + if (!ccUnRegisterManagedObject(guio)) + quit("unable to unregister guicontrol object"); + } +} + +int convert_gui_disabled_style(int oldStyle) { + int toret = GUIDIS_GREYOUT; + + // if GUIs Turn Off is selected, don't grey out buttons for + // any Persistent GUIs which remain + // set to 0x80 so that it is still non-zero, but has no effect + if (oldStyle == 3) + toret = GUIDIS_GUIOFF; + // GUIs Go Black + else if (oldStyle == 1) + toret = GUIDIS_BLACKOUT; + // GUIs unchanged + else if (oldStyle == 2) + toret = GUIDIS_UNCHANGED; + + return toret; +} + +void update_gui_disabled_status() { + // update GUI display status (perhaps we've gone into + // an interface disabled state) + int all_buttons_was = all_buttons_disabled; + all_buttons_disabled = 0; + + if (!IsInterfaceEnabled()) { + all_buttons_disabled = gui_disabled_style; + } + + if (all_buttons_was != all_buttons_disabled) { + // GUIs might have been removed/added + for (int aa = 0; aa < game.numgui; aa++) { + guis[aa].OnControlPositionChanged(); + } + guis_need_update = 1; + invalidate_screen(); + } +} + + +int adjust_x_for_guis (int xx, int yy) { + if ((game.options[OPT_DISABLEOFF]==3) && (all_buttons_disabled > 0)) + return xx; + // If it's covered by a GUI, move it right a bit + for (int aa=0;aa < game.numgui; aa++) { + if (!guis[aa].IsDisplayed()) + continue; + if ((guis[aa].X > xx) || (guis[aa].Y > yy) || (guis[aa].Y + guis[aa].Height < yy)) + continue; + // totally transparent GUI, ignore + if ((guis[aa].BgColor == 0) && (guis[aa].BgImage < 1)) + continue; + + // try to deal with full-width GUIs across the top + if (guis[aa].X + guis[aa].Width >= get_fixed_pixel_size(280)) + continue; + + if (xx < guis[aa].X + guis[aa].Width) + xx = guis[aa].X + guis[aa].Width + 2; + } + return xx; +} + +int adjust_y_for_guis ( int yy) { + if ((game.options[OPT_DISABLEOFF]==3) && (all_buttons_disabled > 0)) + return yy; + // If it's covered by a GUI, move it down a bit + for (int aa=0;aa < game.numgui; aa++) { + if (!guis[aa].IsDisplayed()) + continue; + if (guis[aa].Y > yy) + continue; + // totally transparent GUI, ignore + if ((guis[aa].BgColor == 0) && (guis[aa].BgImage < 1)) + continue; + + // try to deal with full-height GUIs down the left or right + if (guis[aa].Height > get_fixed_pixel_size(50)) + continue; + + if (yy < guis[aa].Y + guis[aa].Height) + yy = guis[aa].Y + guis[aa].Height + 2; + } + return yy; +} + +void recreate_guibg_image(GUIMain *tehgui) +{ + int ifn = tehgui->ID; + delete guibg[ifn]; + guibg[ifn] = BitmapHelper::CreateBitmap(tehgui->Width, tehgui->Height, game.GetColorDepth()); + if (guibg[ifn] == nullptr) + quit("SetGUISize: internal error: unable to reallocate gui cache"); + guibg[ifn] = ReplaceBitmapWithSupportedFormat(guibg[ifn]); + + if (guibgbmp[ifn] != nullptr) + { + gfxDriver->DestroyDDB(guibgbmp[ifn]); + guibgbmp[ifn] = nullptr; + } +} + +extern int is_complete_overlay; + +int gui_get_interactable(int x,int y) +{ + if ((game.options[OPT_DISABLEOFF]==3) && (all_buttons_disabled > 0)) + return -1; + return GetGUIAt(x, y); +} + +int gui_on_mouse_move() +{ + int mouse_over_gui = -1; + // If all GUIs are off, skip the loop + if ((game.options[OPT_DISABLEOFF]==3) && (all_buttons_disabled > 0)) ; + else { + // Scan for mouse-y-pos GUIs, and pop one up if appropriate + // Also work out the mouse-over GUI while we're at it + int ll; + for (ll = 0; ll < game.numgui;ll++) { + const int guin = play.gui_draw_order[ll]; + if (guis[guin].IsInteractableAt(mousex, mousey)) mouse_over_gui=guin; + + if (guis[guin].PopupStyle!=kGUIPopupMouseY) continue; + if (is_complete_overlay>0) break; // interfaces disabled + // if (play.disabled_user_interface>0) break; + if (ifacepopped==guin) continue; + if (!guis[guin].IsVisible()) continue; + // Don't allow it to be popped up while skipping cutscene + if (play.fast_forward) continue; + + if (mousey < guis[guin].PopupAtMouseY) { + set_mouse_cursor(CURS_ARROW); + guis[guin].SetConceal(false); guis_need_update = 1; + ifacepopped=guin; PauseGame(); + break; + } + } + } + return mouse_over_gui; +} + +void gui_on_mouse_hold(const int wasongui, const int wasbutdown) +{ + for (int i=0;iIsActivated) continue; + if (guis[wasongui].GetControlType(i)!=kGUISlider) continue; + // GUI Slider repeatedly activates while being dragged + guio->IsActivated = false; + force_event(EV_IFACECLICK, wasongui, i, wasbutdown); + break; + } +} + +void gui_on_mouse_up(const int wasongui, const int wasbutdown) +{ + guis[wasongui].OnMouseButtonUp(); + + for (int i=0;iIsActivated) continue; + guio->IsActivated = false; + if (!IsInterfaceEnabled()) break; + + int cttype=guis[wasongui].GetControlType(i); + if ((cttype == kGUIButton) || (cttype == kGUISlider) || (cttype == kGUIListBox)) { + force_event(EV_IFACECLICK, wasongui, i, wasbutdown); + } + else if (cttype == kGUIInvWindow) { + mouse_ifacebut_xoffs=mousex-(guio->X)-guis[wasongui].X; + mouse_ifacebut_yoffs=mousey-(guio->Y)-guis[wasongui].Y; + int iit=offset_over_inv((GUIInvWindow*)guio); + if (iit>=0) { + evblocknum=iit; + play.used_inv_on = iit; + if (game.options[OPT_HANDLEINVCLICKS]) { + // Let the script handle the click + // LEFTINV is 5, RIGHTINV is 6 + force_event(EV_TEXTSCRIPT,TS_MCLICK, wasbutdown + 4); + } + else if (wasbutdown==2) // right-click is always Look + run_event_block_inv(iit, 0); + else if (cur_mode == MODE_HAND) + SetActiveInventory(iit); + else + RunInventoryInteraction (iit, cur_mode); + evblocknum=-1; + } + } + else quit("clicked on unknown control type"); + if (guis[wasongui].PopupStyle==kGUIPopupMouseY) + remove_popup_interface(wasongui); + break; + } + + run_on_event(GE_GUI_MOUSEUP, RuntimeScriptValue().SetInt32(wasongui)); +} + +void gui_on_mouse_down(const int guin, const int mbut) +{ + debug_script_log("Mouse click over GUI %d", guin); + guis[guin].OnMouseButtonDown(); + // run GUI click handler if not on any control + if ((guis[guin].MouseDownCtrl < 0) && (!guis[guin].OnClickHandler.IsEmpty())) + force_event(EV_IFACECLICK, guin, -1, mbut); + + run_on_event(GE_GUI_MOUSEDOWN, RuntimeScriptValue().SetInt32(guin)); +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// void GUI_Centre(ScriptGUI *sgui) +RuntimeScriptValue Sc_GUI_Centre(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptGUI, GUI_Centre); +} + +// ScriptGUI *(int xx, int yy) +RuntimeScriptValue Sc_GetGUIAtLocation(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(ScriptGUI, ccDynamicGUI, GetGUIAtLocation); +} + +// void (ScriptGUI *tehgui, int xx, int yy) +RuntimeScriptValue Sc_GUI_SetPosition(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(ScriptGUI, GUI_SetPosition); +} + +// void (ScriptGUI *sgui, int widd, int hitt) +RuntimeScriptValue Sc_GUI_SetSize(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(ScriptGUI, GUI_SetSize); +} + +// int (ScriptGUI *tehgui) +RuntimeScriptValue Sc_GUI_GetBackgroundGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetBackgroundGraphic); +} + +// void (ScriptGUI *tehgui, int slotn) +RuntimeScriptValue Sc_GUI_SetBackgroundGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetBackgroundGraphic); +} + +RuntimeScriptValue Sc_GUI_GetBackgroundColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetBackgroundColor); +} + +RuntimeScriptValue Sc_GUI_SetBackgroundColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetBackgroundColor); +} + +RuntimeScriptValue Sc_GUI_GetBorderColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetBorderColor); +} + +RuntimeScriptValue Sc_GUI_SetBorderColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetBorderColor); +} + +RuntimeScriptValue Sc_GUI_GetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetTextColor); +} + +RuntimeScriptValue Sc_GUI_SetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetTextColor); +} + +// int (ScriptGUI *tehgui) +RuntimeScriptValue Sc_GUI_GetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetClickable); +} + +// void (ScriptGUI *tehgui, int clickable) +RuntimeScriptValue Sc_GUI_SetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetClickable); +} + +// int (ScriptGUI *tehgui) +RuntimeScriptValue Sc_GUI_GetControlCount(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetControlCount); +} + +// GUIObject* (ScriptGUI *tehgui, int idx) +RuntimeScriptValue Sc_GUI_GetiControls(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT(ScriptGUI, GUIObject, ccDynamicGUIObject, GUI_GetiControls); +} + +// int (ScriptGUI *sgui) +RuntimeScriptValue Sc_GUI_GetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetHeight); +} + +// void (ScriptGUI *sgui, int newhit) +RuntimeScriptValue Sc_GUI_SetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetHeight); +} + +// int (ScriptGUI *tehgui) +RuntimeScriptValue Sc_GUI_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetID); +} + +RuntimeScriptValue Sc_GUI_GetPopupYPos(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetPopupYPos); +} + +RuntimeScriptValue Sc_GUI_SetPopupYPos(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetPopupYPos); +} + +RuntimeScriptValue Sc_GUI_GetTextPadding(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetTextPadding); +} + +RuntimeScriptValue Sc_GUI_SetTextPadding(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetTextPadding); +} + +// int (ScriptGUI *tehgui) +RuntimeScriptValue Sc_GUI_GetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetTransparency); +} + +// void (ScriptGUI *tehgui, int trans) +RuntimeScriptValue Sc_GUI_SetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetTransparency); +} + +// int (ScriptGUI *tehgui) +RuntimeScriptValue Sc_GUI_GetVisible(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetVisible); +} + +// void (ScriptGUI *tehgui, int isvisible) +RuntimeScriptValue Sc_GUI_SetVisible(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetVisible); +} + +// int (ScriptGUI *sgui) +RuntimeScriptValue Sc_GUI_GetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetWidth); +} + +// void (ScriptGUI *sgui, int newwid) +RuntimeScriptValue Sc_GUI_SetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetWidth); +} + +// int (ScriptGUI *tehgui) +RuntimeScriptValue Sc_GUI_GetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetX); +} + +// void (ScriptGUI *tehgui, int xx) +RuntimeScriptValue Sc_GUI_SetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetX); +} + +// int (ScriptGUI *tehgui) +RuntimeScriptValue Sc_GUI_GetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetY); +} + +// void (ScriptGUI *tehgui, int yy) +RuntimeScriptValue Sc_GUI_SetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetY); +} + +// int (ScriptGUI *tehgui) +RuntimeScriptValue Sc_GUI_GetZOrder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetZOrder); +} + +// void (ScriptGUI *tehgui, int z) +RuntimeScriptValue Sc_GUI_SetZOrder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_SetZOrder); +} + +RuntimeScriptValue Sc_GUI_AsTextWindow(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptGUI, ScriptGUI, ccDynamicGUI, GUI_AsTextWindow); +} + +RuntimeScriptValue Sc_GUI_GetPopupStyle(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptGUI, GUI_GetPopupStyle); +} + +RuntimeScriptValue Sc_GUI_Click(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptGUI, GUI_Click); +} + +RuntimeScriptValue Sc_GUI_ProcessClick(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(GUI_ProcessClick); +} + +void RegisterGUIAPI() +{ + ccAddExternalObjectFunction("GUI::Centre^0", Sc_GUI_Centre); + ccAddExternalObjectFunction("GUI::Click^1", Sc_GUI_Click); + ccAddExternalStaticFunction("GUI::GetAtScreenXY^2", Sc_GetGUIAtLocation); + ccAddExternalStaticFunction("GUI::ProcessClick^3", Sc_GUI_ProcessClick); + ccAddExternalObjectFunction("GUI::SetPosition^2", Sc_GUI_SetPosition); + ccAddExternalObjectFunction("GUI::SetSize^2", Sc_GUI_SetSize); + ccAddExternalObjectFunction("GUI::get_BackgroundGraphic", Sc_GUI_GetBackgroundGraphic); + ccAddExternalObjectFunction("GUI::set_BackgroundGraphic", Sc_GUI_SetBackgroundGraphic); + ccAddExternalObjectFunction("GUI::get_BackgroundColor", Sc_GUI_GetBackgroundColor); + ccAddExternalObjectFunction("GUI::set_BackgroundColor", Sc_GUI_SetBackgroundColor); + ccAddExternalObjectFunction("GUI::get_BorderColor", Sc_GUI_GetBorderColor); + ccAddExternalObjectFunction("GUI::set_BorderColor", Sc_GUI_SetBorderColor); + ccAddExternalObjectFunction("GUI::get_Clickable", Sc_GUI_GetClickable); + ccAddExternalObjectFunction("GUI::set_Clickable", Sc_GUI_SetClickable); + ccAddExternalObjectFunction("GUI::get_ControlCount", Sc_GUI_GetControlCount); + ccAddExternalObjectFunction("GUI::geti_Controls", Sc_GUI_GetiControls); + ccAddExternalObjectFunction("GUI::get_Height", Sc_GUI_GetHeight); + ccAddExternalObjectFunction("GUI::set_Height", Sc_GUI_SetHeight); + ccAddExternalObjectFunction("GUI::get_ID", Sc_GUI_GetID); + ccAddExternalObjectFunction("GUI::get_AsTextWindow", Sc_GUI_AsTextWindow); + ccAddExternalObjectFunction("GUI::get_PopupStyle", Sc_GUI_GetPopupStyle); + ccAddExternalObjectFunction("GUI::get_PopupYPos", Sc_GUI_GetPopupYPos); + ccAddExternalObjectFunction("GUI::set_PopupYPos", Sc_GUI_SetPopupYPos); + ccAddExternalObjectFunction("TextWindowGUI::get_TextColor", Sc_GUI_GetTextColor); + ccAddExternalObjectFunction("TextWindowGUI::set_TextColor", Sc_GUI_SetTextColor); + ccAddExternalObjectFunction("TextWindowGUI::get_TextPadding", Sc_GUI_GetTextPadding); + ccAddExternalObjectFunction("TextWindowGUI::set_TextPadding", Sc_GUI_SetTextPadding); + ccAddExternalObjectFunction("GUI::get_Transparency", Sc_GUI_GetTransparency); + ccAddExternalObjectFunction("GUI::set_Transparency", Sc_GUI_SetTransparency); + ccAddExternalObjectFunction("GUI::get_Visible", Sc_GUI_GetVisible); + ccAddExternalObjectFunction("GUI::set_Visible", Sc_GUI_SetVisible); + ccAddExternalObjectFunction("GUI::get_Width", Sc_GUI_GetWidth); + ccAddExternalObjectFunction("GUI::set_Width", Sc_GUI_SetWidth); + ccAddExternalObjectFunction("GUI::get_X", Sc_GUI_GetX); + ccAddExternalObjectFunction("GUI::set_X", Sc_GUI_SetX); + ccAddExternalObjectFunction("GUI::get_Y", Sc_GUI_GetY); + ccAddExternalObjectFunction("GUI::set_Y", Sc_GUI_SetY); + ccAddExternalObjectFunction("GUI::get_ZOrder", Sc_GUI_GetZOrder); + ccAddExternalObjectFunction("GUI::set_ZOrder", Sc_GUI_SetZOrder); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("GUI::Centre^0", (void*)GUI_Centre); + ccAddExternalFunctionForPlugin("GUI::GetAtScreenXY^2", (void*)GetGUIAtLocation); + ccAddExternalFunctionForPlugin("GUI::SetPosition^2", (void*)GUI_SetPosition); + ccAddExternalFunctionForPlugin("GUI::SetSize^2", (void*)GUI_SetSize); + ccAddExternalFunctionForPlugin("GUI::get_BackgroundGraphic", (void*)GUI_GetBackgroundGraphic); + ccAddExternalFunctionForPlugin("GUI::set_BackgroundGraphic", (void*)GUI_SetBackgroundGraphic); + ccAddExternalFunctionForPlugin("GUI::get_Clickable", (void*)GUI_GetClickable); + ccAddExternalFunctionForPlugin("GUI::set_Clickable", (void*)GUI_SetClickable); + ccAddExternalFunctionForPlugin("GUI::get_ControlCount", (void*)GUI_GetControlCount); + ccAddExternalFunctionForPlugin("GUI::geti_Controls", (void*)GUI_GetiControls); + ccAddExternalFunctionForPlugin("GUI::get_Height", (void*)GUI_GetHeight); + ccAddExternalFunctionForPlugin("GUI::set_Height", (void*)GUI_SetHeight); + ccAddExternalFunctionForPlugin("GUI::get_ID", (void*)GUI_GetID); + ccAddExternalFunctionForPlugin("GUI::get_Transparency", (void*)GUI_GetTransparency); + ccAddExternalFunctionForPlugin("GUI::set_Transparency", (void*)GUI_SetTransparency); + ccAddExternalFunctionForPlugin("GUI::get_Visible", (void*)GUI_GetVisible); + ccAddExternalFunctionForPlugin("GUI::set_Visible", (void*)GUI_SetVisible); + ccAddExternalFunctionForPlugin("GUI::get_Width", (void*)GUI_GetWidth); + ccAddExternalFunctionForPlugin("GUI::set_Width", (void*)GUI_SetWidth); + ccAddExternalFunctionForPlugin("GUI::get_X", (void*)GUI_GetX); + ccAddExternalFunctionForPlugin("GUI::set_X", (void*)GUI_SetX); + ccAddExternalFunctionForPlugin("GUI::get_Y", (void*)GUI_GetY); + ccAddExternalFunctionForPlugin("GUI::set_Y", (void*)GUI_SetY); + ccAddExternalFunctionForPlugin("GUI::get_ZOrder", (void*)GUI_GetZOrder); + ccAddExternalFunctionForPlugin("GUI::set_ZOrder", (void*)GUI_SetZOrder); +} diff --git a/engines/ags/engine/ac/gui.h b/engines/ags/engine/ac/gui.h new file mode 100644 index 00000000000..6bdcbdc0490 --- /dev/null +++ b/engines/ags/engine/ac/gui.h @@ -0,0 +1,88 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GUI_H +#define __AGS_EE_AC__GUI_H + +#include "ac/dynobj/scriptgui.h" +#include "gui/guimain.h" + +using AGS::Common::GUIMain; +using AGS::Common::GUIObject; + +ScriptGUI *GUI_AsTextWindow(ScriptGUI *tehgui); +int GUI_GetPopupStyle(ScriptGUI *tehgui); +void GUI_SetVisible(ScriptGUI *tehgui, int isvisible); +int GUI_GetVisible(ScriptGUI *tehgui); +int GUI_GetX(ScriptGUI *tehgui); +void GUI_SetX(ScriptGUI *tehgui, int xx); +int GUI_GetY(ScriptGUI *tehgui); +void GUI_SetY(ScriptGUI *tehgui, int yy); +void GUI_SetPosition(ScriptGUI *tehgui, int xx, int yy); +void GUI_SetSize(ScriptGUI *sgui, int widd, int hitt); +int GUI_GetWidth(ScriptGUI *sgui); +int GUI_GetHeight(ScriptGUI *sgui); +void GUI_SetWidth(ScriptGUI *sgui, int newwid); +void GUI_SetHeight(ScriptGUI *sgui, int newhit); +void GUI_SetZOrder(ScriptGUI *tehgui, int z); +int GUI_GetZOrder(ScriptGUI *tehgui); +void GUI_SetClickable(ScriptGUI *tehgui, int clickable); +int GUI_GetClickable(ScriptGUI *tehgui); +int GUI_GetID(ScriptGUI *tehgui); +GUIObject* GUI_GetiControls(ScriptGUI *tehgui, int idx); +int GUI_GetControlCount(ScriptGUI *tehgui); +void GUI_SetPopupYPos(ScriptGUI *tehgui, int newpos); +int GUI_GetPopupYPos(ScriptGUI *tehgui); +void GUI_SetTransparency(ScriptGUI *tehgui, int trans); +int GUI_GetTransparency(ScriptGUI *tehgui); +void GUI_Centre(ScriptGUI *sgui); +void GUI_SetBackgroundGraphic(ScriptGUI *tehgui, int slotn); +int GUI_GetBackgroundGraphic(ScriptGUI *tehgui); +void GUI_SetBackgroundColor(ScriptGUI *tehgui, int newcol); +int GUI_GetBackgroundColor(ScriptGUI *tehgui); +void GUI_SetBorderColor(ScriptGUI *tehgui, int newcol); +int GUI_GetBorderColor(ScriptGUI *tehgui); +void GUI_SetTextColor(ScriptGUI *tehgui, int newcol); +int GUI_GetTextColor(ScriptGUI *tehgui); +void GUI_SetTextPadding(ScriptGUI *tehgui, int newpos); +int GUI_GetTextPadding(ScriptGUI *tehgui); +ScriptGUI *GetGUIAtLocation(int xx, int yy); + +void remove_popup_interface(int ifacenum); +void process_interface_click(int ifce, int btn, int mbut); +void replace_macro_tokens(const char *text, AGS::Common::String &fixed_text); +void update_gui_zorder(); +void export_gui_controls(int ee); +void unexport_gui_controls(int ee); +int convert_gui_disabled_style(int oldStyle); +void update_gui_disabled_status(); +int adjust_x_for_guis (int xx, int yy); +int adjust_y_for_guis ( int yy); +void recreate_guibg_image(GUIMain *tehgui); + +int gui_get_interactable(int x,int y); +int gui_on_mouse_move(); +void gui_on_mouse_hold(const int wasongui, const int wasbutdown); +void gui_on_mouse_up(const int wasongui, const int wasbutdown); +void gui_on_mouse_down(const int guin, const int mbut); + +extern int ifacepopped; + +extern int ifacepopped; +extern int mouse_on_iface; + +#endif // __AGS_EE_AC__GUI_H diff --git a/engines/ags/engine/ac/guicontrol.cpp b/engines/ags/engine/ac/guicontrol.cpp new file mode 100644 index 00000000000..b007330da02 --- /dev/null +++ b/engines/ags/engine/ac/guicontrol.cpp @@ -0,0 +1,474 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/guicontrol.h" +#include "ac/global_gui.h" +#include "debug/debug_log.h" +#include "gui/guibutton.h" +#include "gui/guiinv.h" +#include "gui/guilabel.h" +#include "gui/guilistbox.h" +#include "gui/guimain.h" +#include "gui/guislider.h" +#include "gui/guitextbox.h" +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_gui.h" +#include "ac/dynobj/cc_guiobject.h" + +using namespace AGS::Common; + +extern ScriptGUI*scrGui; +extern CCGUI ccDynamicGUI; +extern CCGUIObject ccDynamicGUIObject; + +GUIObject *GetGUIControlAtLocation(int xx, int yy) { + int guinum = GetGUIAt(xx, yy); + if (guinum == -1) + return nullptr; + + data_to_game_coords(&xx, &yy); + + int oldmousex = mousex, oldmousey = mousey; + mousex = xx - guis[guinum].X; + mousey = yy - guis[guinum].Y; + int toret = guis[guinum].FindControlUnderMouse(0, false); + mousex = oldmousex; + mousey = oldmousey; + if (toret < 0) + return nullptr; + + return guis[guinum].GetControl(toret); +} + +int GUIControl_GetVisible(GUIObject *guio) { + return guio->IsVisible(); +} + +void GUIControl_SetVisible(GUIObject *guio, int visible) +{ + const bool on = visible != 0; + if (on != guio->IsVisible()) + { + guio->SetVisible(on); + guis[guio->ParentId].OnControlPositionChanged(); + guis_need_update = 1; + } +} + +int GUIControl_GetClickable(GUIObject *guio) { + if (guio->IsClickable()) + return 1; + return 0; +} + +void GUIControl_SetClickable(GUIObject *guio, int enabled) { + if (enabled) + guio->SetClickable(true); + else + guio->SetClickable(false); + + guis[guio->ParentId].OnControlPositionChanged(); + guis_need_update = 1; +} + +int GUIControl_GetEnabled(GUIObject *guio) { + return guio->IsEnabled() ? 1 : 0; +} + +void GUIControl_SetEnabled(GUIObject *guio, int enabled) { + const bool on = enabled != 0; + if (on != guio->IsEnabled()) + { + guio->SetEnabled(on); + guis[guio->ParentId].OnControlPositionChanged(); + guis_need_update = 1; + } +} + + +int GUIControl_GetID(GUIObject *guio) { + return guio->Id; +} + +ScriptGUI* GUIControl_GetOwningGUI(GUIObject *guio) { + return &scrGui[guio->ParentId]; +} + +GUIButton* GUIControl_GetAsButton(GUIObject *guio) { + if (guis[guio->ParentId].GetControlType(guio->Id) != kGUIButton) + return nullptr; + + return (GUIButton*)guio; +} + +GUIInvWindow* GUIControl_GetAsInvWindow(GUIObject *guio) { + if (guis[guio->ParentId].GetControlType(guio->Id) != kGUIInvWindow) + return nullptr; + + return (GUIInvWindow*)guio; +} + +GUILabel* GUIControl_GetAsLabel(GUIObject *guio) { + if (guis[guio->ParentId].GetControlType(guio->Id) != kGUILabel) + return nullptr; + + return (GUILabel*)guio; +} + +GUIListBox* GUIControl_GetAsListBox(GUIObject *guio) { + if (guis[guio->ParentId].GetControlType(guio->Id) != kGUIListBox) + return nullptr; + + return (GUIListBox*)guio; +} + +GUISlider* GUIControl_GetAsSlider(GUIObject *guio) { + if (guis[guio->ParentId].GetControlType(guio->Id) != kGUISlider) + return nullptr; + + return (GUISlider*)guio; +} + +GUITextBox* GUIControl_GetAsTextBox(GUIObject *guio) { + if (guis[guio->ParentId].GetControlType(guio->Id) != kGUITextBox) + return nullptr; + + return (GUITextBox*)guio; +} + +int GUIControl_GetX(GUIObject *guio) { + return game_to_data_coord(guio->X); +} + +void GUIControl_SetX(GUIObject *guio, int xx) { + guio->X = data_to_game_coord(xx); + guis[guio->ParentId].OnControlPositionChanged(); + guis_need_update = 1; +} + +int GUIControl_GetY(GUIObject *guio) { + return game_to_data_coord(guio->Y); +} + +void GUIControl_SetY(GUIObject *guio, int yy) { + guio->Y = data_to_game_coord(yy); + guis[guio->ParentId].OnControlPositionChanged(); + guis_need_update = 1; +} + +int GUIControl_GetZOrder(GUIObject *guio) +{ + return guio->ZOrder; +} + +void GUIControl_SetZOrder(GUIObject *guio, int zorder) +{ + if (guis[guio->ParentId].SetControlZOrder(guio->Id, zorder)) + guis_need_update = 1; +} + +void GUIControl_SetPosition(GUIObject *guio, int xx, int yy) { + GUIControl_SetX(guio, xx); + GUIControl_SetY(guio, yy); +} + + +int GUIControl_GetWidth(GUIObject *guio) { + return game_to_data_coord(guio->Width); +} + +void GUIControl_SetWidth(GUIObject *guio, int newwid) { + guio->Width = data_to_game_coord(newwid); + guio->OnResized(); + guis[guio->ParentId].OnControlPositionChanged(); + guis_need_update = 1; +} + +int GUIControl_GetHeight(GUIObject *guio) { + return game_to_data_coord(guio->Height); +} + +void GUIControl_SetHeight(GUIObject *guio, int newhit) { + guio->Height = data_to_game_coord(newhit); + guio->OnResized(); + guis[guio->ParentId].OnControlPositionChanged(); + guis_need_update = 1; +} + +void GUIControl_SetSize(GUIObject *guio, int newwid, int newhit) { + if ((newwid < 2) || (newhit < 2)) + quit("!SetGUIObjectSize: new size is too small (must be at least 2x2)"); + + debug_script_log("SetGUIObject %d,%d size %d,%d", guio->ParentId, guio->Id, newwid, newhit); + GUIControl_SetWidth(guio, newwid); + GUIControl_SetHeight(guio, newhit); +} + +void GUIControl_SendToBack(GUIObject *guio) { + if (guis[guio->ParentId].SendControlToBack(guio->Id)) + guis_need_update = 1; +} + +void GUIControl_BringToFront(GUIObject *guio) { + if (guis[guio->ParentId].BringControlToFront(guio->Id)) + guis_need_update = 1; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// void (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_BringToFront(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(GUIObject, GUIControl_BringToFront); +} + +// GUIObject *(int xx, int yy) +RuntimeScriptValue Sc_GetGUIControlAtLocation(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(GUIObject, ccDynamicGUIObject, GetGUIControlAtLocation); +} + +// void (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_SendToBack(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(GUIObject, GUIControl_SendToBack); +} + +// void (GUIObject *guio, int xx, int yy) +RuntimeScriptValue Sc_GUIControl_SetPosition(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(GUIObject, GUIControl_SetPosition); +} + +// void (GUIObject *guio, int newwid, int newhit) +RuntimeScriptValue Sc_GUIControl_SetSize(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(GUIObject, GUIControl_SetSize); +} + +// GUIButton* (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetAsButton(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUIObject, GUIButton, ccDynamicGUI, GUIControl_GetAsButton); +} + +// GUIInvWindow* (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetAsInvWindow(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUIObject, GUIInvWindow, ccDynamicGUI, GUIControl_GetAsInvWindow); +} + +// GUILabel* (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetAsLabel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUIObject, GUILabel, ccDynamicGUI, GUIControl_GetAsLabel); +} + +// GUIListBox* (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetAsListBox(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUIObject, GUIListBox, ccDynamicGUI, GUIControl_GetAsListBox); +} + +// GUISlider* (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetAsSlider(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUIObject, GUISlider, ccDynamicGUI, GUIControl_GetAsSlider); +} + +// GUITextBox* (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetAsTextBox(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUIObject, GUITextBox, ccDynamicGUI, GUIControl_GetAsTextBox); +} + +// int (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIObject, GUIControl_GetClickable); +} + +// void (GUIObject *guio, int enabled) +RuntimeScriptValue Sc_GUIControl_SetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetClickable); +} + +// int (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetEnabled(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIObject, GUIControl_GetEnabled); +} + +// void (GUIObject *guio, int enabled) +RuntimeScriptValue Sc_GUIControl_SetEnabled(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetEnabled); +} + +// int (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIObject, GUIControl_GetHeight); +} + +// void (GUIObject *guio, int newhit) +RuntimeScriptValue Sc_GUIControl_SetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetHeight); +} + +// int (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIObject, GUIControl_GetID); +} + +// ScriptGUI* (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetOwningGUI(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUIObject, ScriptGUI, ccDynamicGUI, GUIControl_GetOwningGUI); +} + +// int (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetVisible(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIObject, GUIControl_GetVisible); +} + +// void (GUIObject *guio, int visible) +RuntimeScriptValue Sc_GUIControl_SetVisible(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetVisible); +} + +// int (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIObject, GUIControl_GetWidth); +} + +// void (GUIObject *guio, int newwid) +RuntimeScriptValue Sc_GUIControl_SetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetWidth); +} + +// int (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIObject, GUIControl_GetX); +} + +// void (GUIObject *guio, int xx) +RuntimeScriptValue Sc_GUIControl_SetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetX); +} + +// int (GUIObject *guio) +RuntimeScriptValue Sc_GUIControl_GetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIObject, GUIControl_GetY); +} + +// void (GUIObject *guio, int yy) +RuntimeScriptValue Sc_GUIControl_SetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetY); +} + +RuntimeScriptValue Sc_GUIControl_GetZOrder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIObject, GUIControl_GetZOrder); +} + +RuntimeScriptValue Sc_GUIControl_SetZOrder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIObject, GUIControl_SetZOrder); +} + + + +void RegisterGUIControlAPI() +{ + ccAddExternalObjectFunction("GUIControl::BringToFront^0", Sc_GUIControl_BringToFront); + ccAddExternalStaticFunction("GUIControl::GetAtScreenXY^2", Sc_GetGUIControlAtLocation); + ccAddExternalObjectFunction("GUIControl::SendToBack^0", Sc_GUIControl_SendToBack); + ccAddExternalObjectFunction("GUIControl::SetPosition^2", Sc_GUIControl_SetPosition); + ccAddExternalObjectFunction("GUIControl::SetSize^2", Sc_GUIControl_SetSize); + ccAddExternalObjectFunction("GUIControl::get_AsButton", Sc_GUIControl_GetAsButton); + ccAddExternalObjectFunction("GUIControl::get_AsInvWindow", Sc_GUIControl_GetAsInvWindow); + ccAddExternalObjectFunction("GUIControl::get_AsLabel", Sc_GUIControl_GetAsLabel); + ccAddExternalObjectFunction("GUIControl::get_AsListBox", Sc_GUIControl_GetAsListBox); + ccAddExternalObjectFunction("GUIControl::get_AsSlider", Sc_GUIControl_GetAsSlider); + ccAddExternalObjectFunction("GUIControl::get_AsTextBox", Sc_GUIControl_GetAsTextBox); + ccAddExternalObjectFunction("GUIControl::get_Clickable", Sc_GUIControl_GetClickable); + ccAddExternalObjectFunction("GUIControl::set_Clickable", Sc_GUIControl_SetClickable); + ccAddExternalObjectFunction("GUIControl::get_Enabled", Sc_GUIControl_GetEnabled); + ccAddExternalObjectFunction("GUIControl::set_Enabled", Sc_GUIControl_SetEnabled); + ccAddExternalObjectFunction("GUIControl::get_Height", Sc_GUIControl_GetHeight); + ccAddExternalObjectFunction("GUIControl::set_Height", Sc_GUIControl_SetHeight); + ccAddExternalObjectFunction("GUIControl::get_ID", Sc_GUIControl_GetID); + ccAddExternalObjectFunction("GUIControl::get_OwningGUI", Sc_GUIControl_GetOwningGUI); + ccAddExternalObjectFunction("GUIControl::get_Visible", Sc_GUIControl_GetVisible); + ccAddExternalObjectFunction("GUIControl::set_Visible", Sc_GUIControl_SetVisible); + ccAddExternalObjectFunction("GUIControl::get_Width", Sc_GUIControl_GetWidth); + ccAddExternalObjectFunction("GUIControl::set_Width", Sc_GUIControl_SetWidth); + ccAddExternalObjectFunction("GUIControl::get_X", Sc_GUIControl_GetX); + ccAddExternalObjectFunction("GUIControl::set_X", Sc_GUIControl_SetX); + ccAddExternalObjectFunction("GUIControl::get_Y", Sc_GUIControl_GetY); + ccAddExternalObjectFunction("GUIControl::set_Y", Sc_GUIControl_SetY); + ccAddExternalObjectFunction("GUIControl::get_ZOrder", Sc_GUIControl_GetZOrder); + ccAddExternalObjectFunction("GUIControl::set_ZOrder", Sc_GUIControl_SetZOrder); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("GUIControl::BringToFront^0", (void*)GUIControl_BringToFront); + ccAddExternalFunctionForPlugin("GUIControl::GetAtScreenXY^2", (void*)GetGUIControlAtLocation); + ccAddExternalFunctionForPlugin("GUIControl::SendToBack^0", (void*)GUIControl_SendToBack); + ccAddExternalFunctionForPlugin("GUIControl::SetPosition^2", (void*)GUIControl_SetPosition); + ccAddExternalFunctionForPlugin("GUIControl::SetSize^2", (void*)GUIControl_SetSize); + ccAddExternalFunctionForPlugin("GUIControl::get_AsButton", (void*)GUIControl_GetAsButton); + ccAddExternalFunctionForPlugin("GUIControl::get_AsInvWindow", (void*)GUIControl_GetAsInvWindow); + ccAddExternalFunctionForPlugin("GUIControl::get_AsLabel", (void*)GUIControl_GetAsLabel); + ccAddExternalFunctionForPlugin("GUIControl::get_AsListBox", (void*)GUIControl_GetAsListBox); + ccAddExternalFunctionForPlugin("GUIControl::get_AsSlider", (void*)GUIControl_GetAsSlider); + ccAddExternalFunctionForPlugin("GUIControl::get_AsTextBox", (void*)GUIControl_GetAsTextBox); + ccAddExternalFunctionForPlugin("GUIControl::get_Clickable", (void*)GUIControl_GetClickable); + ccAddExternalFunctionForPlugin("GUIControl::set_Clickable", (void*)GUIControl_SetClickable); + ccAddExternalFunctionForPlugin("GUIControl::get_Enabled", (void*)GUIControl_GetEnabled); + ccAddExternalFunctionForPlugin("GUIControl::set_Enabled", (void*)GUIControl_SetEnabled); + ccAddExternalFunctionForPlugin("GUIControl::get_Height", (void*)GUIControl_GetHeight); + ccAddExternalFunctionForPlugin("GUIControl::set_Height", (void*)GUIControl_SetHeight); + ccAddExternalFunctionForPlugin("GUIControl::get_ID", (void*)GUIControl_GetID); + ccAddExternalFunctionForPlugin("GUIControl::get_OwningGUI", (void*)GUIControl_GetOwningGUI); + ccAddExternalFunctionForPlugin("GUIControl::get_Visible", (void*)GUIControl_GetVisible); + ccAddExternalFunctionForPlugin("GUIControl::set_Visible", (void*)GUIControl_SetVisible); + ccAddExternalFunctionForPlugin("GUIControl::get_Width", (void*)GUIControl_GetWidth); + ccAddExternalFunctionForPlugin("GUIControl::set_Width", (void*)GUIControl_SetWidth); + ccAddExternalFunctionForPlugin("GUIControl::get_X", (void*)GUIControl_GetX); + ccAddExternalFunctionForPlugin("GUIControl::set_X", (void*)GUIControl_SetX); + ccAddExternalFunctionForPlugin("GUIControl::get_Y", (void*)GUIControl_GetY); + ccAddExternalFunctionForPlugin("GUIControl::set_Y", (void*)GUIControl_SetY); +} diff --git a/engines/ags/engine/ac/guicontrol.h b/engines/ags/engine/ac/guicontrol.h new file mode 100644 index 00000000000..aa9ec3ad4c6 --- /dev/null +++ b/engines/ags/engine/ac/guicontrol.h @@ -0,0 +1,68 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__GUICONTROL_H +#define __AGS_EE_AC__GUICONTROL_H + +#include "gui/guiobject.h" +#include "gui/guibutton.h" +#include "gui/guiinv.h" +#include "gui/guilabel.h" +#include "gui/guilistbox.h" +#include "gui/guislider.h" +#include "gui/guitextbox.h" +#include "ac/dynobj/scriptgui.h" + +using AGS::Common::GUIObject; +using AGS::Common::GUIButton; +using AGS::Common::GUIInvWindow; +using AGS::Common::GUILabel; +using AGS::Common::GUIListBox; +using AGS::Common::GUISlider; +using AGS::Common::GUITextBox; + +GUIObject *GetGUIControlAtLocation(int xx, int yy); +int GUIControl_GetVisible(GUIObject *guio); +void GUIControl_SetVisible(GUIObject *guio, int visible); +int GUIControl_GetClickable(GUIObject *guio); +void GUIControl_SetClickable(GUIObject *guio, int enabled); +int GUIControl_GetEnabled(GUIObject *guio); +void GUIControl_SetEnabled(GUIObject *guio, int enabled); +int GUIControl_GetID(GUIObject *guio); +ScriptGUI* GUIControl_GetOwningGUI(GUIObject *guio); +GUIButton* GUIControl_GetAsButton(GUIObject *guio); +GUIInvWindow* GUIControl_GetAsInvWindow(GUIObject *guio); +GUILabel* GUIControl_GetAsLabel(GUIObject *guio); +GUIListBox* GUIControl_GetAsListBox(GUIObject *guio); +GUISlider* GUIControl_GetAsSlider(GUIObject *guio); +GUITextBox* GUIControl_GetAsTextBox(GUIObject *guio); +int GUIControl_GetX(GUIObject *guio); +void GUIControl_SetX(GUIObject *guio, int xx); +int GUIControl_GetY(GUIObject *guio); +void GUIControl_SetY(GUIObject *guio, int yy); +int GUIControl_GetZOrder(GUIObject *guio); +void GUIControl_SetZOrder(GUIObject *guio, int zorder); +void GUIControl_SetPosition(GUIObject *guio, int xx, int yy); +int GUIControl_GetWidth(GUIObject *guio); +void GUIControl_SetWidth(GUIObject *guio, int newwid); +int GUIControl_GetHeight(GUIObject *guio); +void GUIControl_SetHeight(GUIObject *guio, int newhit); +void GUIControl_SetSize(GUIObject *guio, int newwid, int newhit); +void GUIControl_SendToBack(GUIObject *guio); +void GUIControl_BringToFront(GUIObject *guio); + +#endif // __AGS_EE_AC__GUICONTROL_H diff --git a/engines/ags/engine/ac/guiinv.cpp b/engines/ags/engine/ac/guiinv.cpp new file mode 100644 index 00000000000..411bca2ec05 --- /dev/null +++ b/engines/ags/engine/ac/guiinv.cpp @@ -0,0 +1,94 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "gui/guiinv.h" +#include "gui/guimain.h" +#include "ac/draw.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/characterextras.h" +#include "ac/spritecache.h" +#include "gfx/bitmap.h" + + +extern GameSetupStruct game; +extern int gui_disabled_style; +extern GameState play; +extern CharacterExtras *charextra; +extern SpriteCache spriteset; + + +namespace AGS +{ +namespace Common +{ + +int GUIInvWindow::GetCharacterId() const +{ + if (CharId < 0) + return game.playercharacter; + + return CharId; +} + +void GUIInvWindow::Draw(Bitmap *ds) +{ + const bool enabled = IsGUIEnabled(this); + if (!enabled && (gui_disabled_style == GUIDIS_BLACKOUT)) + return; + + // backwards compatibility + play.inv_numinline = ColCount; + play.inv_numdisp = RowCount * ColCount; + play.obsolete_inv_numorder = charextra[game.playercharacter].invorder_count; + // if the user changes top_inv_item, switch into backwards + // compatibiltiy mode + if (play.inv_top) + play.inv_backwards_compatibility = 1; + if (play.inv_backwards_compatibility) + TopItem = play.inv_top; + + // draw the items + const int leftmost_x = X; + int at_x = X; + int at_y = Y; + int lastItem = TopItem + (ColCount * RowCount); + if (lastItem > charextra[GetCharacterId()].invorder_count) + lastItem = charextra[GetCharacterId()].invorder_count; + + for (int item = TopItem; item < lastItem; ++item) + { + // draw inv graphic + draw_gui_sprite(ds, game.invinfo[charextra[GetCharacterId()].invorder[item]].pic, at_x, at_y, true); + at_x += data_to_game_coord(ItemWidth); + + // go to next row when appropriate + if ((item - TopItem) % ColCount == (ColCount - 1)) + { + at_x = leftmost_x; + at_y += data_to_game_coord(ItemHeight); + } + } + + if (!enabled && + gui_disabled_style == GUIDIS_GREYOUT && + play.inventory_greys_out == 1) + { + // darken the inventory when disabled + GUI::DrawDisabledEffect(ds, RectWH(X, Y, Width, Height)); + } +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/engine/ac/hotspot.cpp b/engines/ags/engine/ac/hotspot.cpp new file mode 100644 index 00000000000..bca406d7726 --- /dev/null +++ b/engines/ags/engine/ac/hotspot.cpp @@ -0,0 +1,268 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/cc_hotspot.h" +#include "ac/hotspot.h" +#include "ac/gamestate.h" +#include "ac/global_hotspot.h" +#include "ac/global_translation.h" +#include "ac/properties.h" +#include "ac/room.h" +#include "ac/roomstatus.h" +#include "ac/string.h" +#include "game/roomstruct.h" +#include "gfx/bitmap.h" +#include "script/runtimescriptvalue.h" + +using namespace AGS::Common; + +extern RoomStruct thisroom; +extern RoomStatus*croom; +extern ScriptHotspot scrHotspot[MAX_ROOM_HOTSPOTS]; +extern CCHotspot ccDynamicHotspot; + +void Hotspot_SetEnabled(ScriptHotspot *hss, int newval) { + if (newval) + EnableHotspot(hss->id); + else + DisableHotspot(hss->id); +} + +int Hotspot_GetEnabled(ScriptHotspot *hss) { + return croom->hotspot_enabled[hss->id]; +} + +int Hotspot_GetID(ScriptHotspot *hss) { + return hss->id; +} + +int Hotspot_GetWalkToX(ScriptHotspot *hss) { + return GetHotspotPointX(hss->id); +} + +int Hotspot_GetWalkToY(ScriptHotspot *hss) { + return GetHotspotPointY(hss->id); +} + +ScriptHotspot *GetHotspotAtScreen(int xx, int yy) { + return &scrHotspot[GetHotspotIDAtScreen(xx, yy)]; +} + +ScriptHotspot *GetHotspotAtRoom(int x, int y) { + return &scrHotspot[get_hotspot_at(x, y)]; +} + +void Hotspot_GetName(ScriptHotspot *hss, char *buffer) { + GetHotspotName(hss->id, buffer); +} + +const char* Hotspot_GetName_New(ScriptHotspot *hss) { + return CreateNewScriptString(get_translation(thisroom.Hotspots[hss->id].Name)); +} + +bool Hotspot_IsInteractionAvailable(ScriptHotspot *hhot, int mood) { + + play.check_interaction_only = 1; + RunHotspotInteraction(hhot->id, mood); + int ciwas = play.check_interaction_only; + play.check_interaction_only = 0; + return (ciwas == 2); +} + +void Hotspot_RunInteraction (ScriptHotspot *hss, int mood) { + RunHotspotInteraction(hss->id, mood); +} + +int Hotspot_GetProperty (ScriptHotspot *hss, const char *property) +{ + return get_int_property(thisroom.Hotspots[hss->id].Properties, croom->hsProps[hss->id], property); +} + +void Hotspot_GetPropertyText (ScriptHotspot *hss, const char *property, char *bufer) +{ + get_text_property(thisroom.Hotspots[hss->id].Properties, croom->hsProps[hss->id], property, bufer); + +} + +const char* Hotspot_GetTextProperty(ScriptHotspot *hss, const char *property) +{ + return get_text_property_dynamic_string(thisroom.Hotspots[hss->id].Properties, croom->hsProps[hss->id], property); +} + +bool Hotspot_SetProperty(ScriptHotspot *hss, const char *property, int value) +{ + return set_int_property(croom->hsProps[hss->id], property, value); +} + +bool Hotspot_SetTextProperty(ScriptHotspot *hss, const char *property, const char *value) +{ + return set_text_property(croom->hsProps[hss->id], property, value); +} + +int get_hotspot_at(int xpp,int ypp) { + int onhs=thisroom.HotspotMask->GetPixel(room_to_mask_coord(xpp), room_to_mask_coord(ypp)); + if (onhs <= 0 || onhs >= MAX_ROOM_HOTSPOTS) return 0; + if (croom->hotspot_enabled[onhs]==0) return 0; + return onhs; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +RuntimeScriptValue Sc_GetHotspotAtRoom(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(ScriptHotspot, ccDynamicHotspot, GetHotspotAtRoom); +} + +// ScriptHotspot *(int xx, int yy) +RuntimeScriptValue Sc_GetHotspotAtScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(ScriptHotspot, ccDynamicHotspot, GetHotspotAtScreen); +} + +RuntimeScriptValue Sc_Hotspot_GetDrawingSurface(const RuntimeScriptValue *params, int32_t param_count) +{ + ScriptDrawingSurface* ret_obj = Room_GetDrawingSurfaceForMask(kRoomAreaHotspot); + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj); +} + +// void (ScriptHotspot *hss, char *buffer) +RuntimeScriptValue Sc_Hotspot_GetName(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(ScriptHotspot, Hotspot_GetName, char); +} + +// int (ScriptHotspot *hss, const char *property) +RuntimeScriptValue Sc_Hotspot_GetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(ScriptHotspot, Hotspot_GetProperty, const char); +} + +// void (ScriptHotspot *hss, const char *property, char *bufer) +RuntimeScriptValue Sc_Hotspot_GetPropertyText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ2(ScriptHotspot, Hotspot_GetPropertyText, const char, char); +} + +// const char* (ScriptHotspot *hss, const char *property) +RuntimeScriptValue Sc_Hotspot_GetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_POBJ(ScriptHotspot, const char, myScriptStringImpl, Hotspot_GetTextProperty, const char); +} + +RuntimeScriptValue Sc_Hotspot_SetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ_PINT(ScriptHotspot, Hotspot_SetProperty, const char); +} + +RuntimeScriptValue Sc_Hotspot_SetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ2(ScriptHotspot, Hotspot_SetTextProperty, const char, const char); +} + +RuntimeScriptValue Sc_Hotspot_IsInteractionAvailable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_PINT(ScriptHotspot, Hotspot_IsInteractionAvailable); +} + +// void (ScriptHotspot *hss, int mood) +RuntimeScriptValue Sc_Hotspot_RunInteraction(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptHotspot, Hotspot_RunInteraction); +} + +// int (ScriptHotspot *hss) +RuntimeScriptValue Sc_Hotspot_GetEnabled(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptHotspot, Hotspot_GetEnabled); +} + +// void (ScriptHotspot *hss, int newval) +RuntimeScriptValue Sc_Hotspot_SetEnabled(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptHotspot, Hotspot_SetEnabled); +} + +// int (ScriptHotspot *hss) +RuntimeScriptValue Sc_Hotspot_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptHotspot, Hotspot_GetID); +} + +// const char* (ScriptHotspot *hss) +RuntimeScriptValue Sc_Hotspot_GetName_New(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptHotspot, const char, myScriptStringImpl, Hotspot_GetName_New); +} + +// int (ScriptHotspot *hss) +RuntimeScriptValue Sc_Hotspot_GetWalkToX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptHotspot, Hotspot_GetWalkToX); +} + +// int (ScriptHotspot *hss) +RuntimeScriptValue Sc_Hotspot_GetWalkToY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptHotspot, Hotspot_GetWalkToY); +} + + + +void RegisterHotspotAPI() +{ + ccAddExternalStaticFunction("Hotspot::GetAtRoomXY^2", Sc_GetHotspotAtRoom); + ccAddExternalStaticFunction("Hotspot::GetAtScreenXY^2", Sc_GetHotspotAtScreen); + ccAddExternalStaticFunction("Hotspot::GetDrawingSurface", Sc_Hotspot_GetDrawingSurface); + ccAddExternalObjectFunction("Hotspot::GetName^1", Sc_Hotspot_GetName); + ccAddExternalObjectFunction("Hotspot::GetProperty^1", Sc_Hotspot_GetProperty); + ccAddExternalObjectFunction("Hotspot::GetPropertyText^2", Sc_Hotspot_GetPropertyText); + ccAddExternalObjectFunction("Hotspot::GetTextProperty^1", Sc_Hotspot_GetTextProperty); + ccAddExternalObjectFunction("Hotspot::SetProperty^2", Sc_Hotspot_SetProperty); + ccAddExternalObjectFunction("Hotspot::SetTextProperty^2", Sc_Hotspot_SetTextProperty); + ccAddExternalObjectFunction("Hotspot::IsInteractionAvailable^1", Sc_Hotspot_IsInteractionAvailable); + ccAddExternalObjectFunction("Hotspot::RunInteraction^1", Sc_Hotspot_RunInteraction); + ccAddExternalObjectFunction("Hotspot::get_Enabled", Sc_Hotspot_GetEnabled); + ccAddExternalObjectFunction("Hotspot::set_Enabled", Sc_Hotspot_SetEnabled); + ccAddExternalObjectFunction("Hotspot::get_ID", Sc_Hotspot_GetID); + ccAddExternalObjectFunction("Hotspot::get_Name", Sc_Hotspot_GetName_New); + ccAddExternalObjectFunction("Hotspot::get_WalkToX", Sc_Hotspot_GetWalkToX); + ccAddExternalObjectFunction("Hotspot::get_WalkToY", Sc_Hotspot_GetWalkToY); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Hotspot::GetAtRoomXY^2", (void*)GetHotspotAtRoom); + ccAddExternalFunctionForPlugin("Hotspot::GetAtScreenXY^2", (void*)GetHotspotAtScreen); + ccAddExternalFunctionForPlugin("Hotspot::GetName^1", (void*)Hotspot_GetName); + ccAddExternalFunctionForPlugin("Hotspot::GetProperty^1", (void*)Hotspot_GetProperty); + ccAddExternalFunctionForPlugin("Hotspot::GetPropertyText^2", (void*)Hotspot_GetPropertyText); + ccAddExternalFunctionForPlugin("Hotspot::GetTextProperty^1", (void*)Hotspot_GetTextProperty); + ccAddExternalFunctionForPlugin("Hotspot::RunInteraction^1", (void*)Hotspot_RunInteraction); + ccAddExternalFunctionForPlugin("Hotspot::get_Enabled", (void*)Hotspot_GetEnabled); + ccAddExternalFunctionForPlugin("Hotspot::set_Enabled", (void*)Hotspot_SetEnabled); + ccAddExternalFunctionForPlugin("Hotspot::get_ID", (void*)Hotspot_GetID); + ccAddExternalFunctionForPlugin("Hotspot::get_Name", (void*)Hotspot_GetName_New); + ccAddExternalFunctionForPlugin("Hotspot::get_WalkToX", (void*)Hotspot_GetWalkToX); + ccAddExternalFunctionForPlugin("Hotspot::get_WalkToY", (void*)Hotspot_GetWalkToY); +} diff --git a/engines/ags/engine/ac/hotspot.h b/engines/ags/engine/ac/hotspot.h new file mode 100644 index 00000000000..d1bb46f8b23 --- /dev/null +++ b/engines/ags/engine/ac/hotspot.h @@ -0,0 +1,42 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__HOTSPOT_H +#define __AGS_EE_AC__HOTSPOT_H + +#include "ac/dynobj/scripthotspot.h" + +void Hotspot_SetEnabled(ScriptHotspot *hss, int newval); +int Hotspot_GetEnabled(ScriptHotspot *hss); +int Hotspot_GetID(ScriptHotspot *hss); +ScriptHotspot *GetHotspotAtScreen(int xx, int yy); +int Hotspot_GetWalkToX(ScriptHotspot *hss);; +int Hotspot_GetWalkToY(ScriptHotspot *hss); +void Hotspot_GetName(ScriptHotspot *hss, char *buffer); +const char* Hotspot_GetName_New(ScriptHotspot *hss); +bool Hotspot_IsInteractionAvailable(ScriptHotspot *hhot, int mood); +void Hotspot_RunInteraction (ScriptHotspot *hss, int mood); + +int Hotspot_GetProperty (ScriptHotspot *hss, const char *property); +void Hotspot_GetPropertyText (ScriptHotspot *hss, const char *property, char *bufer); +const char* Hotspot_GetTextProperty(ScriptHotspot *hss, const char *property); + +// Gets hotspot ID at the given room coordinates; +// if hotspot is disabled or non-existing, returns 0 (no area) +int get_hotspot_at(int xpp,int ypp); + +#endif // __AGS_EE_AC__HOTSPOT_H diff --git a/engines/ags/engine/ac/interfacebutton.cpp b/engines/ags/engine/ac/interfacebutton.cpp new file mode 100644 index 00000000000..c474bb7ecb7 --- /dev/null +++ b/engines/ags/engine/ac/interfacebutton.cpp @@ -0,0 +1,21 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/interfacebutton.h" + +void InterfaceButton::set(int xx, int yy, int picc, int overpicc, int actionn) { + x = xx; y = yy; pic = picc; overpic = overpicc; leftclick = actionn; pushpic = 0; + rightclick = 0; flags = IBFLG_ENABLED; + reserved_for_future = 0; +} \ No newline at end of file diff --git a/engines/ags/engine/ac/interfaceelement.cpp b/engines/ags/engine/ac/interfaceelement.cpp new file mode 100644 index 00000000000..13abc93fdc2 --- /dev/null +++ b/engines/ags/engine/ac/interfaceelement.cpp @@ -0,0 +1,21 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/interfaceelement.h" + +InterfaceElement::InterfaceElement() { + vtextxp = 0; vtextyp = 1; strcpy(vtext,"@SCORETEXT@$r@GAMENAME@"); + numbuttons = 0; bgcol = 8; fgcol = 15; bordercol = 0; on = 1; flags = 0; +} diff --git a/engines/ags/engine/ac/inventoryitem.cpp b/engines/ags/engine/ac/inventoryitem.cpp new file mode 100644 index 00000000000..8f28fd6136b --- /dev/null +++ b/engines/ags/engine/ac/inventoryitem.cpp @@ -0,0 +1,266 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/inventoryitem.h" +#include "ac/characterinfo.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_inventoryitem.h" +#include "ac/global_translation.h" +#include "ac/mouse.h" +#include "ac/properties.h" +#include "ac/runtime_defines.h" +#include "ac/string.h" +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_inventory.h" + + +extern GameSetupStruct game; +extern ScriptInvItem scrInv[MAX_INV]; +extern int cur_cursor; +extern CharacterInfo*playerchar; +extern CCInventory ccDynamicInv; + + +void InventoryItem_SetCursorGraphic(ScriptInvItem *iitem, int newSprite) +{ + set_inv_item_cursorpic(iitem->id, newSprite); +} + +int InventoryItem_GetCursorGraphic(ScriptInvItem *iitem) +{ + return game.invinfo[iitem->id].cursorPic; +} + +void InventoryItem_SetGraphic(ScriptInvItem *iitem, int piccy) { + set_inv_item_pic(iitem->id, piccy); +} + +void InventoryItem_SetName(ScriptInvItem *scii, const char *newname) { + SetInvItemName(scii->id, newname); +} + +int InventoryItem_GetID(ScriptInvItem *scii) { + return scii->id; +} + +ScriptInvItem *GetInvAtLocation(int xx, int yy) { + int hsnum = GetInvAt(xx, yy); + if (hsnum <= 0) + return nullptr; + return &scrInv[hsnum]; +} + +void InventoryItem_GetName(ScriptInvItem *iitem, char *buff) { + GetInvName(iitem->id, buff); +} + +const char* InventoryItem_GetName_New(ScriptInvItem *invitem) { + return CreateNewScriptString(get_translation(game.invinfo[invitem->id].name)); +} + +int InventoryItem_GetGraphic(ScriptInvItem *iitem) { + return game.invinfo[iitem->id].pic; +} + +void InventoryItem_RunInteraction(ScriptInvItem *iitem, int mood) { + RunInventoryInteraction(iitem->id, mood); +} + +int InventoryItem_CheckInteractionAvailable(ScriptInvItem *iitem, int mood) { + return IsInventoryInteractionAvailable(iitem->id, mood); +} + +int InventoryItem_GetProperty(ScriptInvItem *scii, const char *property) { + return get_int_property (game.invProps[scii->id], play.invProps[scii->id], property); +} + +void InventoryItem_GetPropertyText(ScriptInvItem *scii, const char *property, char *bufer) { + get_text_property(game.invProps[scii->id], play.invProps[scii->id], property, bufer); +} + +const char* InventoryItem_GetTextProperty(ScriptInvItem *scii, const char *property) { + return get_text_property_dynamic_string(game.invProps[scii->id], play.invProps[scii->id], property); +} + +bool InventoryItem_SetProperty(ScriptInvItem *scii, const char *property, int value) +{ + return set_int_property(play.invProps[scii->id], property, value); +} + +bool InventoryItem_SetTextProperty(ScriptInvItem *scii, const char *property, const char *value) +{ + return set_text_property(play.invProps[scii->id], property, value); +} + +//============================================================================= + +void set_inv_item_cursorpic(int invItemId, int piccy) +{ + game.invinfo[invItemId].cursorPic = piccy; + + if ((cur_cursor == MODE_USE) && (playerchar->activeinv == invItemId)) + { + update_inv_cursor(invItemId); + set_mouse_cursor(cur_cursor); + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// ScriptInvItem *(int xx, int yy) +RuntimeScriptValue Sc_GetInvAtLocation(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(ScriptInvItem, ccDynamicInv, GetInvAtLocation); +} + +// int (ScriptInvItem *iitem, int mood) +RuntimeScriptValue Sc_InventoryItem_CheckInteractionAvailable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT(ScriptInvItem, InventoryItem_CheckInteractionAvailable); +} + +// void (ScriptInvItem *iitem, char *buff) +RuntimeScriptValue Sc_InventoryItem_GetName(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(ScriptInvItem, InventoryItem_GetName, char); +} + +// int (ScriptInvItem *scii, const char *property) +RuntimeScriptValue Sc_InventoryItem_GetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(ScriptInvItem, InventoryItem_GetProperty, const char); +} + +// void (ScriptInvItem *scii, const char *property, char *bufer) +RuntimeScriptValue Sc_InventoryItem_GetPropertyText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ2(ScriptInvItem, InventoryItem_GetPropertyText, const char, char); +} + +// const char* (ScriptInvItem *scii, const char *property) +RuntimeScriptValue Sc_InventoryItem_GetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_POBJ(ScriptInvItem, const char, myScriptStringImpl, InventoryItem_GetTextProperty, const char); +} + +RuntimeScriptValue Sc_InventoryItem_SetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ_PINT(ScriptInvItem, InventoryItem_SetProperty, const char); +} + +RuntimeScriptValue Sc_InventoryItem_SetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ2(ScriptInvItem, InventoryItem_SetTextProperty, const char, const char); +} + +// void (ScriptInvItem *iitem, int mood) +RuntimeScriptValue Sc_InventoryItem_RunInteraction(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptInvItem, InventoryItem_RunInteraction); +} + +// void (ScriptInvItem *scii, const char *newname) +RuntimeScriptValue Sc_InventoryItem_SetName(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(ScriptInvItem, InventoryItem_SetName, const char); +} + +// int (ScriptInvItem *iitem) +RuntimeScriptValue Sc_InventoryItem_GetCursorGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptInvItem, InventoryItem_GetCursorGraphic); +} + +// void (ScriptInvItem *iitem, int newSprite) +RuntimeScriptValue Sc_InventoryItem_SetCursorGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptInvItem, InventoryItem_SetCursorGraphic); +} + +// int (ScriptInvItem *iitem) +RuntimeScriptValue Sc_InventoryItem_GetGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptInvItem, InventoryItem_GetGraphic); +} + +// void (ScriptInvItem *iitem, int piccy) +RuntimeScriptValue Sc_InventoryItem_SetGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptInvItem, InventoryItem_SetGraphic); +} + +// int (ScriptInvItem *scii) +RuntimeScriptValue Sc_InventoryItem_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptInvItem, InventoryItem_GetID); +} + +// const char* (ScriptInvItem *invitem) +RuntimeScriptValue Sc_InventoryItem_GetName_New(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptInvItem, const char, myScriptStringImpl, InventoryItem_GetName_New); +} + + + +void RegisterInventoryItemAPI() +{ + ccAddExternalStaticFunction("InventoryItem::GetAtScreenXY^2", Sc_GetInvAtLocation); + ccAddExternalObjectFunction("InventoryItem::IsInteractionAvailable^1", Sc_InventoryItem_CheckInteractionAvailable); + ccAddExternalObjectFunction("InventoryItem::GetName^1", Sc_InventoryItem_GetName); + ccAddExternalObjectFunction("InventoryItem::GetProperty^1", Sc_InventoryItem_GetProperty); + ccAddExternalObjectFunction("InventoryItem::GetPropertyText^2", Sc_InventoryItem_GetPropertyText); + ccAddExternalObjectFunction("InventoryItem::GetTextProperty^1", Sc_InventoryItem_GetTextProperty); + ccAddExternalObjectFunction("InventoryItem::SetProperty^2", Sc_InventoryItem_SetProperty); + ccAddExternalObjectFunction("InventoryItem::SetTextProperty^2", Sc_InventoryItem_SetTextProperty); + ccAddExternalObjectFunction("InventoryItem::RunInteraction^1", Sc_InventoryItem_RunInteraction); + ccAddExternalObjectFunction("InventoryItem::SetName^1", Sc_InventoryItem_SetName); + ccAddExternalObjectFunction("InventoryItem::get_CursorGraphic", Sc_InventoryItem_GetCursorGraphic); + ccAddExternalObjectFunction("InventoryItem::set_CursorGraphic", Sc_InventoryItem_SetCursorGraphic); + ccAddExternalObjectFunction("InventoryItem::get_Graphic", Sc_InventoryItem_GetGraphic); + ccAddExternalObjectFunction("InventoryItem::set_Graphic", Sc_InventoryItem_SetGraphic); + ccAddExternalObjectFunction("InventoryItem::get_ID", Sc_InventoryItem_GetID); + ccAddExternalObjectFunction("InventoryItem::get_Name", Sc_InventoryItem_GetName_New); + ccAddExternalObjectFunction("InventoryItem::set_Name", Sc_InventoryItem_SetName); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("InventoryItem::GetAtScreenXY^2", (void*)GetInvAtLocation); + ccAddExternalFunctionForPlugin("InventoryItem::IsInteractionAvailable^1", (void*)InventoryItem_CheckInteractionAvailable); + ccAddExternalFunctionForPlugin("InventoryItem::GetName^1", (void*)InventoryItem_GetName); + ccAddExternalFunctionForPlugin("InventoryItem::GetProperty^1", (void*)InventoryItem_GetProperty); + ccAddExternalFunctionForPlugin("InventoryItem::GetPropertyText^2", (void*)InventoryItem_GetPropertyText); + ccAddExternalFunctionForPlugin("InventoryItem::GetTextProperty^1", (void*)InventoryItem_GetTextProperty); + ccAddExternalFunctionForPlugin("InventoryItem::RunInteraction^1", (void*)InventoryItem_RunInteraction); + ccAddExternalFunctionForPlugin("InventoryItem::SetName^1", (void*)InventoryItem_SetName); + ccAddExternalFunctionForPlugin("InventoryItem::get_CursorGraphic", (void*)InventoryItem_GetCursorGraphic); + ccAddExternalFunctionForPlugin("InventoryItem::set_CursorGraphic", (void*)InventoryItem_SetCursorGraphic); + ccAddExternalFunctionForPlugin("InventoryItem::get_Graphic", (void*)InventoryItem_GetGraphic); + ccAddExternalFunctionForPlugin("InventoryItem::set_Graphic", (void*)InventoryItem_SetGraphic); + ccAddExternalFunctionForPlugin("InventoryItem::get_ID", (void*)InventoryItem_GetID); + ccAddExternalFunctionForPlugin("InventoryItem::get_Name", (void*)InventoryItem_GetName_New); + ccAddExternalFunctionForPlugin("InventoryItem::set_Name", (void*)InventoryItem_SetName); +} diff --git a/engines/ags/engine/ac/inventoryitem.h b/engines/ags/engine/ac/inventoryitem.h new file mode 100644 index 00000000000..833209a6597 --- /dev/null +++ b/engines/ags/engine/ac/inventoryitem.h @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__INVENTORYITEM_H +#define __AGS_EE_AC__INVENTORYITEM_H + +#include "ac/dynobj/scriptinvitem.h" + +void InventoryItem_SetCursorGraphic(ScriptInvItem *iitem, int newSprite); +int InventoryItem_GetCursorGraphic(ScriptInvItem *iitem); +void InventoryItem_SetGraphic(ScriptInvItem *iitem, int piccy); +void InventoryItem_SetName(ScriptInvItem *scii, const char *newname); +int InventoryItem_GetID(ScriptInvItem *scii); +ScriptInvItem *GetInvAtLocation(int xx, int yy); +void InventoryItem_GetName(ScriptInvItem *iitem, char *buff); +const char* InventoryItem_GetName_New(ScriptInvItem *invitem); +int InventoryItem_GetGraphic(ScriptInvItem *iitem); +void InventoryItem_RunInteraction(ScriptInvItem *iitem, int mood); +int InventoryItem_CheckInteractionAvailable(ScriptInvItem *iitem, int mood); +int InventoryItem_GetProperty(ScriptInvItem *scii, const char *property); +void InventoryItem_GetPropertyText(ScriptInvItem *scii, const char *property, char *bufer); +const char* InventoryItem_GetTextProperty(ScriptInvItem *scii, const char *property); + +void set_inv_item_cursorpic(int invItemId, int piccy); + +#endif // __AGS_EE_AC__INVENTORYITEM_H diff --git a/engines/ags/engine/ac/invwindow.cpp b/engines/ags/engine/ac/invwindow.cpp new file mode 100644 index 00000000000..faffb294e01 --- /dev/null +++ b/engines/ags/engine/ac/invwindow.cpp @@ -0,0 +1,658 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/invwindow.h" +#include "ac/common.h" +#include "ac/characterextras.h" +#include "ac/characterinfo.h" +#include "ac/draw.h" +#include "ac/event.h" +#include "ac/gamestate.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_character.h" +#include "ac/global_display.h" +#include "ac/global_room.h" +#include "ac/mouse.h" +#include "ac/sys_events.h" +#include "debug/debug_log.h" +#include "gui/guidialog.h" +#include "main/game_run.h" +#include "platform/base/agsplatformdriver.h" +#include "ac/spritecache.h" +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_character.h" +#include "ac/dynobj/cc_inventory.h" +#include "util/math.h" +#include "media/audio/audio_system.h" +#include "ac/timer.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; +extern GameState play; +extern CharacterExtras *charextra; +extern ScriptInvItem scrInv[MAX_INV]; +extern int mouse_ifacebut_xoffs,mouse_ifacebut_yoffs; +extern SpriteCache spriteset; +extern int mousex,mousey; +extern int evblocknum; +extern CharacterInfo*playerchar; +extern AGSPlatformDriver *platform; +extern CCCharacter ccDynamicCharacter; +extern CCInventory ccDynamicInv; + +int in_inv_screen = 0, inv_screen_newroom = -1; + +// *** INV WINDOW FUNCTIONS + +void InvWindow_SetCharacterToUse(GUIInvWindow *guii, CharacterInfo *chaa) { + if (chaa == nullptr) + guii->CharId = -1; + else + guii->CharId = chaa->index_id; + // reset to top of list + guii->TopItem = 0; + + guis_need_update = 1; +} + +CharacterInfo* InvWindow_GetCharacterToUse(GUIInvWindow *guii) { + if (guii->CharId < 0) + return nullptr; + + return &game.chars[guii->CharId]; +} + +void InvWindow_SetItemWidth(GUIInvWindow *guii, int newwidth) { + guii->ItemWidth = newwidth; + guii->OnResized(); +} + +int InvWindow_GetItemWidth(GUIInvWindow *guii) { + return guii->ItemWidth; +} + +void InvWindow_SetItemHeight(GUIInvWindow *guii, int newhit) { + guii->ItemHeight = newhit; + guii->OnResized(); +} + +int InvWindow_GetItemHeight(GUIInvWindow *guii) { + return guii->ItemHeight; +} + +void InvWindow_SetTopItem(GUIInvWindow *guii, int topitem) { + if (guii->TopItem != topitem) { + guii->TopItem = topitem; + guis_need_update = 1; + } +} + +int InvWindow_GetTopItem(GUIInvWindow *guii) { + return guii->TopItem; +} + +int InvWindow_GetItemsPerRow(GUIInvWindow *guii) { + return guii->ColCount; +} + +int InvWindow_GetItemCount(GUIInvWindow *guii) { + return charextra[guii->GetCharacterId()].invorder_count; +} + +int InvWindow_GetRowCount(GUIInvWindow *guii) { + return guii->RowCount; +} + +void InvWindow_ScrollDown(GUIInvWindow *guii) { + if ((charextra[guii->GetCharacterId()].invorder_count) > + (guii->TopItem + (guii->ColCount * guii->RowCount))) { + guii->TopItem += guii->ColCount; + guis_need_update = 1; + } +} + +void InvWindow_ScrollUp(GUIInvWindow *guii) { + if (guii->TopItem > 0) { + guii->TopItem -= guii->ColCount; + if (guii->TopItem < 0) + guii->TopItem = 0; + + guis_need_update = 1; + } +} + +ScriptInvItem* InvWindow_GetItemAtIndex(GUIInvWindow *guii, int index) { + if ((index < 0) || (index >= charextra[guii->GetCharacterId()].invorder_count)) + return nullptr; + return &scrInv[charextra[guii->GetCharacterId()].invorder[index]]; +} + +//============================================================================= + +int offset_over_inv(GUIInvWindow *inv) { + if (inv->ItemWidth <= 0 || inv->ItemHeight <= 0) + return -1; + int mover = mouse_ifacebut_xoffs / data_to_game_coord(inv->ItemWidth); + // if it's off the edge of the visible items, ignore + if (mover >= inv->ColCount) + return -1; + mover += (mouse_ifacebut_yoffs / data_to_game_coord(inv->ItemHeight)) * inv->ColCount; + if (mover >= inv->ColCount * inv->RowCount) + return -1; + + mover += inv->TopItem; + if ((mover < 0) || (mover >= charextra[inv->GetCharacterId()].invorder_count)) + return -1; + + return charextra[inv->GetCharacterId()].invorder[mover]; +} + +// +// NOTE: This is an old default inventory screen implementation, +// which became completely obsolete after AGS 2.72. +// + +#define ICONSPERLINE 4 + +struct DisplayInvItem { + int num; + int sprnum; +}; + +struct InventoryScreen +{ + static const int ARROWBUTTONWID = 11; + + int BUTTONAREAHEIGHT; + int cmode; + int toret; + int top_item; + int num_visible_items; + int MAX_ITEMAREA_HEIGHT; + int wasonitem; + int bartop; + int barxp; + int numitems; + int widest; + int highest; + int windowwid; + int windowhit; + int windowxp; + int windowyp; + int buttonyp; + DisplayInvItem dii[MAX_INV]; + int btn_look_sprite; + int btn_select_sprite; + int btn_ok_sprite; + + int break_code; + + void Prepare(); + int Redraw(); + void Draw(Bitmap *ds); + void RedrawOverItem(Bitmap *ds, int isonitem); + bool Run(); + void Close(); +}; + +InventoryScreen InvScr; + +void InventoryScreen::Prepare() +{ + BUTTONAREAHEIGHT = get_fixed_pixel_size(30); + cmode=CURS_ARROW; + toret = -1; + top_item = 0; + num_visible_items = 0; + MAX_ITEMAREA_HEIGHT = ((play.GetUIViewport().GetHeight() - BUTTONAREAHEIGHT) - get_fixed_pixel_size(20)); + in_inv_screen++; + inv_screen_newroom = -1; + + // Sprites 2041, 2042 and 2043 were hardcoded in the older versions of + // the engine to be used in the built-in inventory window. + // If they did not exist engine first fell back to sprites 0, 1, 2 instead. + // Fun fact: this fallback does not seem to be intentional, and was a + // coincidental result of SpriteCache incorrectly remembering "last seeked + // sprite" as 2041/2042/2043 while in fact stream was after sprite 0. + if (spriteset[2041] == nullptr || spriteset[2042] == nullptr || spriteset[2043] == nullptr) + debug_script_warn("InventoryScreen: one or more of the inventory screen graphics (sprites 2041, 2042, 2043) does not exist, fallback to sprites 0, 1, 2 instead"); + btn_look_sprite = spriteset[2041] != nullptr ? 2041 : 0; + btn_select_sprite = spriteset[2042] != nullptr ? 2042 : (spriteset[1] != nullptr ? 1 : 0); + btn_ok_sprite = spriteset[2043] != nullptr ? 2043 : (spriteset[2] != nullptr ? 2 : 0); + + break_code = 0; +} + +int InventoryScreen::Redraw() +{ + numitems=0; + widest=0; + highest=0; + if (charextra[game.playercharacter].invorder_count < 0) + update_invorder(); + if (charextra[game.playercharacter].invorder_count == 0) { + DisplayMessage(996); + in_inv_screen--; + return -1; + } + + if (inv_screen_newroom >= 0) { + in_inv_screen--; + NewRoom(inv_screen_newroom); + return -1; + } + + for (int i = 0; i < charextra[game.playercharacter].invorder_count; ++i) { + if (game.invinfo[charextra[game.playercharacter].invorder[i]].name[0]!=0) { + dii[numitems].num = charextra[game.playercharacter].invorder[i]; + dii[numitems].sprnum = game.invinfo[charextra[game.playercharacter].invorder[i]].pic; + int snn=dii[numitems].sprnum; + if (game.SpriteInfos[snn].Width > widest) widest=game.SpriteInfos[snn].Width; + if (game.SpriteInfos[snn].Height > highest) highest= game.SpriteInfos[snn].Height; + numitems++; + } + } + if (numitems != charextra[game.playercharacter].invorder_count) + quit("inconsistent inventory calculations"); + + widest += get_fixed_pixel_size(4); + highest += get_fixed_pixel_size(4); + num_visible_items = (MAX_ITEMAREA_HEIGHT / highest) * ICONSPERLINE; + + windowhit = highest * (numitems/ICONSPERLINE) + get_fixed_pixel_size(4); + if ((numitems%ICONSPERLINE) !=0) windowhit+=highest; + if (windowhit > MAX_ITEMAREA_HEIGHT) { + windowhit = (MAX_ITEMAREA_HEIGHT / highest) * highest + get_fixed_pixel_size(4); + } + windowhit += BUTTONAREAHEIGHT; + + windowwid = widest*ICONSPERLINE + get_fixed_pixel_size(4); + if (windowwid < get_fixed_pixel_size(105)) windowwid = get_fixed_pixel_size(105); + windowxp=play.GetUIViewport().GetWidth()/2-windowwid/2; + windowyp=play.GetUIViewport().GetHeight()/2-windowhit/2; + buttonyp = windowhit - BUTTONAREAHEIGHT; + bartop = get_fixed_pixel_size(2); + barxp = get_fixed_pixel_size(2); + + Bitmap *ds = prepare_gui_screen(windowxp, windowyp, windowwid, windowhit, true); + Draw(ds); + //ags_domouse(DOMOUSE_ENABLE); + set_mouse_cursor(cmode); + wasonitem = -1; + return 0; +} + +void InventoryScreen::Draw(Bitmap *ds) +{ + color_t draw_color = ds->GetCompatibleColor(play.sierra_inv_color); + ds->FillRect(Rect(0,0,windowwid,windowhit), draw_color); + draw_color = ds->GetCompatibleColor(0); + ds->FillRect(Rect(barxp,bartop, windowwid - get_fixed_pixel_size(2),buttonyp-1), draw_color); + for (int i = top_item; i < numitems; ++i) { + if (i >= top_item + num_visible_items) + break; + Bitmap *spof=spriteset[dii[i].sprnum]; + wputblock(ds, barxp+1+((i-top_item)%4)*widest+widest/2-spof->GetWidth()/2, + bartop+1+((i-top_item)/4)*highest+highest/2-spof->GetHeight()/2,spof,1); + } +#define BUTTONWID Math::Max(1, game.SpriteInfos[btn_select_sprite].Width) + // Draw select, look and OK buttons + wputblock(ds, 2, buttonyp + get_fixed_pixel_size(2), spriteset[btn_look_sprite], 1); + wputblock(ds, 3+BUTTONWID, buttonyp + get_fixed_pixel_size(2), spriteset[btn_select_sprite], 1); + wputblock(ds, 4+BUTTONWID*2, buttonyp + get_fixed_pixel_size(2), spriteset[btn_ok_sprite], 1); + + // Draw Up and Down buttons if required + Bitmap *arrowblock = BitmapHelper::CreateTransparentBitmap (ARROWBUTTONWID, ARROWBUTTONWID); + draw_color = arrowblock->GetCompatibleColor(0); + if (play.sierra_inv_color == 0) + draw_color = ds->GetCompatibleColor(14); + + arrowblock->DrawLine(Line(ARROWBUTTONWID/2, 2, ARROWBUTTONWID-2, 9), draw_color); + arrowblock->DrawLine(Line(ARROWBUTTONWID/2, 2, 2, 9), draw_color); + arrowblock->DrawLine(Line(2, 9, ARROWBUTTONWID-2, 9), draw_color); + arrowblock->FloodFill(ARROWBUTTONWID/2, 4, draw_color); + + if (top_item > 0) + wputblock(ds, windowwid-ARROWBUTTONWID, buttonyp + get_fixed_pixel_size(2), arrowblock, 1); + if (top_item + num_visible_items < numitems) + arrowblock->FlipBlt(arrowblock, windowwid-ARROWBUTTONWID, buttonyp + get_fixed_pixel_size(4) + ARROWBUTTONWID, Common::kBitmap_VFlip); + delete arrowblock; +} + +void InventoryScreen::RedrawOverItem(Bitmap *ds, int isonitem) +{ + int rectxp=barxp+1+(wasonitem%4)*widest; + int rectyp=bartop+1+((wasonitem - top_item)/4)*highest; + if (wasonitem>=0) + { + color_t draw_color = ds->GetCompatibleColor(0); + ds->DrawRect(Rect(rectxp,rectyp,rectxp+widest-1,rectyp+highest-1), draw_color); + } + if (isonitem>=0) + { + color_t draw_color = ds->GetCompatibleColor(14);//opts.invrectcol); + rectxp=barxp+1+(isonitem%4)*widest; + rectyp=bartop+1+((isonitem - top_item)/4)*highest; + ds->DrawRect(Rect(rectxp,rectyp,rectxp+widest-1,rectyp+highest-1), draw_color); + } +} + +bool InventoryScreen::Run() +{ + int kgn; + if (run_service_key_controls(kgn) && !play.IsIgnoringInput()) + { + return false; // end inventory screen loop + } + + update_audio_system_on_game_loop(); + refresh_gui_screen(); + + // NOTE: this is because old code was working with full game screen + const int mousex = ::mousex - windowxp; + const int mousey = ::mousey - windowyp; + + int isonitem=((mousey-bartop)/highest)*ICONSPERLINE+(mousex-barxp)/widest; + if (mousey<=bartop) isonitem=-1; + else if (isonitem >= 0) isonitem += top_item; + if ((isonitem<0) | (isonitem>=numitems) | (isonitem >= top_item + num_visible_items)) + isonitem=-1; + + int mclick, mwheelz; + if (!run_service_mb_controls(mclick, mwheelz) || play.IsIgnoringInput()) { + mclick = NONE; + } + + if (mclick == LEFT) { + if ((mousey<0) | (mousey>windowhit) | (mousex<0) | (mousex>windowwid)) + return true; // continue inventory screen loop + if (mouseyactiveinv; + playerchar->activeinv = toret; + + //ags_domouse(DOMOUSE_DISABLE); + run_event_block_inv(dii[clickedon].num, 3); + + // if the script didn't change it, then put it back + if (playerchar->activeinv == toret) + playerchar->activeinv = activeinvwas; + + // in case the script did anything to the screen, redraw it + UpdateGameOnce(); + + // They used the active item and lost it + if (playerchar->inv[toret] < 1) { + cmode = CURS_ARROW; + set_mouse_cursor(cmode); + toret = -1; + } + + break_code = Redraw(); + return break_code == 0; + } + toret=dii[clickedon].num; + // int plusng=play.using; play.using=toret; + update_inv_cursor(toret); + set_mouse_cursor(MODE_USE); + cmode=MODE_USE; + // play.using=plusng; + // break; + return true; // continue inventory screen loop + } + else { + if (mousex >= windowwid-ARROWBUTTONWID) { + if (mousey < buttonyp + get_fixed_pixel_size(2) + ARROWBUTTONWID) { + if (top_item > 0) { + top_item -= ICONSPERLINE; + //ags_domouse(DOMOUSE_DISABLE); + + break_code = Redraw(); + return break_code == 0; + } + } + else if ((mousey < buttonyp + get_fixed_pixel_size(4) + ARROWBUTTONWID*2) && (top_item + num_visible_items < numitems)) { + top_item += ICONSPERLINE; + //ags_domouse(DOMOUSE_DISABLE); + + break_code = Redraw(); + return break_code == 0; + } + return true; // continue inventory screen loop + } + + int buton=mousex-2; + if (buton<0) return true; // continue inventory screen loop + buton/=BUTTONWID; + if (buton>=3) return true; // continue inventory screen loop + if (buton==0) { toret=-1; cmode=MODE_LOOK; } + else if (buton==1) { cmode=CURS_ARROW; toret=-1; } + else + { + return false; // end inventory screen loop + } + set_mouse_cursor(cmode); + } + } + else if (mclick == RIGHT) { + if (cmode == CURS_ARROW) + cmode = MODE_LOOK; + else + cmode = CURS_ARROW; + toret = -1; + set_mouse_cursor(cmode); + } + else if (isonitem!=wasonitem) + { + //ags_domouse(DOMOUSE_DISABLE); + RedrawOverItem(get_gui_screen(), isonitem); + //ags_domouse(DOMOUSE_ENABLE); + } + wasonitem=isonitem; + + update_polled_stuff_if_runtime(); + + WaitForNextFrame(); + + return true; // continue inventory screen loop +} + +void InventoryScreen::Close() +{ + clear_gui_screen(); + set_default_cursor(); + invalidate_screen(); + in_inv_screen--; +} + +int __actual_invscreen() +{ + InvScr.Prepare(); + InvScr.break_code = InvScr.Redraw(); + if (InvScr.break_code != 0) + { + return InvScr.break_code; + } + + while (InvScr.Run()); + + if (InvScr.break_code != 0) + { + return InvScr.break_code; + } + + ags_clear_input_buffer(); + + InvScr.Close(); + return InvScr.toret; +} + +int invscreen() { + int selt=__actual_invscreen(); + if (selt<0) return -1; + playerchar->activeinv=selt; + guis_need_update = 1; + set_cursor_mode(MODE_USE); + return selt; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// void (GUIInvWindow *guii) +RuntimeScriptValue Sc_InvWindow_ScrollDown(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(GUIInvWindow, InvWindow_ScrollDown); +} + +// void (GUIInvWindow *guii) +RuntimeScriptValue Sc_InvWindow_ScrollUp(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(GUIInvWindow, InvWindow_ScrollUp); +} + +// CharacterInfo* (GUIInvWindow *guii) +RuntimeScriptValue Sc_InvWindow_GetCharacterToUse(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUIInvWindow, CharacterInfo, ccDynamicCharacter, InvWindow_GetCharacterToUse); +} + +// void (GUIInvWindow *guii, CharacterInfo *chaa) +RuntimeScriptValue Sc_InvWindow_SetCharacterToUse(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(GUIInvWindow, InvWindow_SetCharacterToUse, CharacterInfo); +} + +// ScriptInvItem* (GUIInvWindow *guii, int index) +RuntimeScriptValue Sc_InvWindow_GetItemAtIndex(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT(GUIInvWindow, ScriptInvItem, ccDynamicInv, InvWindow_GetItemAtIndex); +} + +// int (GUIInvWindow *guii) +RuntimeScriptValue Sc_InvWindow_GetItemCount(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIInvWindow, InvWindow_GetItemCount); +} + +// int (GUIInvWindow *guii) +RuntimeScriptValue Sc_InvWindow_GetItemHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIInvWindow, InvWindow_GetItemHeight); +} + +// void (GUIInvWindow *guii, int newhit) +RuntimeScriptValue Sc_InvWindow_SetItemHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIInvWindow, InvWindow_SetItemHeight); +} + +// int (GUIInvWindow *guii) +RuntimeScriptValue Sc_InvWindow_GetItemWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIInvWindow, InvWindow_GetItemWidth); +} + +// void (GUIInvWindow *guii, int newwidth) +RuntimeScriptValue Sc_InvWindow_SetItemWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIInvWindow, InvWindow_SetItemWidth); +} + +// int (GUIInvWindow *guii) +RuntimeScriptValue Sc_InvWindow_GetItemsPerRow(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIInvWindow, InvWindow_GetItemsPerRow); +} + +// int (GUIInvWindow *guii) +RuntimeScriptValue Sc_InvWindow_GetRowCount(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIInvWindow, InvWindow_GetRowCount); +} + +// int (GUIInvWindow *guii) +RuntimeScriptValue Sc_InvWindow_GetTopItem(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIInvWindow, InvWindow_GetTopItem); +} + +// void (GUIInvWindow *guii, int topitem) +RuntimeScriptValue Sc_InvWindow_SetTopItem(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIInvWindow, InvWindow_SetTopItem); +} + + + +void RegisterInventoryWindowAPI() +{ + ccAddExternalObjectFunction("InvWindow::ScrollDown^0", Sc_InvWindow_ScrollDown); + ccAddExternalObjectFunction("InvWindow::ScrollUp^0", Sc_InvWindow_ScrollUp); + ccAddExternalObjectFunction("InvWindow::get_CharacterToUse", Sc_InvWindow_GetCharacterToUse); + ccAddExternalObjectFunction("InvWindow::set_CharacterToUse", Sc_InvWindow_SetCharacterToUse); + ccAddExternalObjectFunction("InvWindow::geti_ItemAtIndex", Sc_InvWindow_GetItemAtIndex); + ccAddExternalObjectFunction("InvWindow::get_ItemCount", Sc_InvWindow_GetItemCount); + ccAddExternalObjectFunction("InvWindow::get_ItemHeight", Sc_InvWindow_GetItemHeight); + ccAddExternalObjectFunction("InvWindow::set_ItemHeight", Sc_InvWindow_SetItemHeight); + ccAddExternalObjectFunction("InvWindow::get_ItemWidth", Sc_InvWindow_GetItemWidth); + ccAddExternalObjectFunction("InvWindow::set_ItemWidth", Sc_InvWindow_SetItemWidth); + ccAddExternalObjectFunction("InvWindow::get_ItemsPerRow", Sc_InvWindow_GetItemsPerRow); + ccAddExternalObjectFunction("InvWindow::get_RowCount", Sc_InvWindow_GetRowCount); + ccAddExternalObjectFunction("InvWindow::get_TopItem", Sc_InvWindow_GetTopItem); + ccAddExternalObjectFunction("InvWindow::set_TopItem", Sc_InvWindow_SetTopItem); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("InvWindow::ScrollDown^0", (void*)InvWindow_ScrollDown); + ccAddExternalFunctionForPlugin("InvWindow::ScrollUp^0", (void*)InvWindow_ScrollUp); + ccAddExternalFunctionForPlugin("InvWindow::get_CharacterToUse", (void*)InvWindow_GetCharacterToUse); + ccAddExternalFunctionForPlugin("InvWindow::set_CharacterToUse", (void*)InvWindow_SetCharacterToUse); + ccAddExternalFunctionForPlugin("InvWindow::geti_ItemAtIndex", (void*)InvWindow_GetItemAtIndex); + ccAddExternalFunctionForPlugin("InvWindow::get_ItemCount", (void*)InvWindow_GetItemCount); + ccAddExternalFunctionForPlugin("InvWindow::get_ItemHeight", (void*)InvWindow_GetItemHeight); + ccAddExternalFunctionForPlugin("InvWindow::set_ItemHeight", (void*)InvWindow_SetItemHeight); + ccAddExternalFunctionForPlugin("InvWindow::get_ItemWidth", (void*)InvWindow_GetItemWidth); + ccAddExternalFunctionForPlugin("InvWindow::set_ItemWidth", (void*)InvWindow_SetItemWidth); + ccAddExternalFunctionForPlugin("InvWindow::get_ItemsPerRow", (void*)InvWindow_GetItemsPerRow); + ccAddExternalFunctionForPlugin("InvWindow::get_RowCount", (void*)InvWindow_GetRowCount); + ccAddExternalFunctionForPlugin("InvWindow::get_TopItem", (void*)InvWindow_GetTopItem); + ccAddExternalFunctionForPlugin("InvWindow::set_TopItem", (void*)InvWindow_SetTopItem); +} diff --git a/engines/ags/engine/ac/invwindow.h b/engines/ags/engine/ac/invwindow.h new file mode 100644 index 00000000000..7c192b11a1a --- /dev/null +++ b/engines/ags/engine/ac/invwindow.h @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__INVWINDOW_H +#define __AGS_EE_AC__INVWINDOW_H + +#include "ac/characterinfo.h" +#include "ac/dynobj/scriptinvitem.h" +#include "gui/guiinv.h" + +using AGS::Common::GUIInvWindow; + +void InvWindow_SetCharacterToUse(GUIInvWindow *guii, CharacterInfo *chaa); +CharacterInfo* InvWindow_GetCharacterToUse(GUIInvWindow *guii); +void InvWindow_SetItemWidth(GUIInvWindow *guii, int newwidth); +int InvWindow_GetItemWidth(GUIInvWindow *guii); +void InvWindow_SetItemHeight(GUIInvWindow *guii, int newhit); +int InvWindow_GetItemHeight(GUIInvWindow *guii); +void InvWindow_SetTopItem(GUIInvWindow *guii, int topitem); +int InvWindow_GetTopItem(GUIInvWindow *guii); +int InvWindow_GetItemsPerRow(GUIInvWindow *guii); +int InvWindow_GetItemCount(GUIInvWindow *guii); +int InvWindow_GetRowCount(GUIInvWindow *guii); +void InvWindow_ScrollDown(GUIInvWindow *guii); +void InvWindow_ScrollUp(GUIInvWindow *guii); +ScriptInvItem* InvWindow_GetItemAtIndex(GUIInvWindow *guii, int index); + +//============================================================================= + +int offset_over_inv(GUIInvWindow *inv); +// NOTE: This function is valid for AGS 2.72 and lower +int invscreen(); + +#endif // __AGS_EE_AC__INVWINDOW_H diff --git a/engines/ags/engine/ac/keycode.cpp b/engines/ags/engine/ac/keycode.cpp new file mode 100644 index 00000000000..ba6b0390a38 --- /dev/null +++ b/engines/ags/engine/ac/keycode.cpp @@ -0,0 +1,142 @@ + +#include "ac/keycode.h" + +#include + +int GetKeyForKeyPressCb(int keycode) +{ + // lower case 'a'..'z' do not exist as keycodes, only ascii. 'A'..'Z' do though! + return (keycode >= 'a' && keycode <= 'z') ? keycode - 32 : keycode; +} + +int PlatformKeyFromAgsKey(int key) +{ + int platformKey = -1; + + switch (key) { + // ctrl-[A-Z] keys are numbered 1-26 for A-Z + case eAGSKeyCodeCtrlA: platformKey = 1; break; + case eAGSKeyCodeCtrlB: platformKey = 2; break; + case eAGSKeyCodeCtrlC: platformKey = 3; break; + case eAGSKeyCodeCtrlD: platformKey = 4; break; + case eAGSKeyCodeCtrlE: platformKey = 5; break; + case eAGSKeyCodeCtrlF: platformKey = 6; break; + case eAGSKeyCodeCtrlG: platformKey = 7; break; + // case eAGSKeyCodeCtrlH: // overlap with backspace + // case eAGSKeyCodeCtrlI: // overlap with tab + case eAGSKeyCodeCtrlJ: platformKey = 10; break; + case eAGSKeyCodeCtrlK: platformKey = 11; break; + case eAGSKeyCodeCtrlL: platformKey = 12; break; + // case eAGSKeyCodeCtrlM: // overlap with return + case eAGSKeyCodeCtrlN: platformKey = 14; break; + case eAGSKeyCodeCtrlO: platformKey = 15; break; + case eAGSKeyCodeCtrlP: platformKey = 16; break; + case eAGSKeyCodeCtrlQ: platformKey = 17; break; + case eAGSKeyCodeCtrlR: platformKey = 18; break; + case eAGSKeyCodeCtrlS: platformKey = 19; break; + case eAGSKeyCodeCtrlT: platformKey = 20; break; + case eAGSKeyCodeCtrlU: platformKey = 21; break; + case eAGSKeyCodeCtrlV: platformKey = 22; break; + case eAGSKeyCodeCtrlW: platformKey = 23; break; + case eAGSKeyCodeCtrlX: platformKey = 24; break; + case eAGSKeyCodeCtrlY: platformKey = 25; break; + case eAGSKeyCodeCtrlZ: platformKey = 26; break; + + case eAGSKeyCodeBackspace: platformKey = (__allegro_KEY_BACKSPACE << 8) | 8; break; + case eAGSKeyCodeTab: platformKey = (__allegro_KEY_TAB << 8) | 9; break; + case eAGSKeyCodeReturn: platformKey = (__allegro_KEY_ENTER << 8) | 13; break; + case eAGSKeyCodeEscape: platformKey = (__allegro_KEY_ESC << 8) | 27; break; + case eAGSKeyCodeSpace: platformKey = (__allegro_KEY_SPACE << 8) | ' '; break; + case eAGSKeyCodeExclamationMark: platformKey = '!'; break; + case eAGSKeyCodeDoubleQuote: platformKey = '"'; break; + case eAGSKeyCodeHash: platformKey = '#'; break; + case eAGSKeyCodeDollar: platformKey = '$'; break; + case eAGSKeyCodePercent: platformKey = '%'; break; + case eAGSKeyCodeAmpersand: platformKey = '&'; break; + case eAGSKeyCodeSingleQuote: platformKey = '\''; break; + case eAGSKeyCodeOpenParenthesis: platformKey = '('; break; + case eAGSKeyCodeCloseParenthesis: platformKey = ')'; break; + case eAGSKeyCodeAsterisk: platformKey = '*'; break; + case eAGSKeyCodePlus: platformKey = '+'; break; + case eAGSKeyCodeComma: platformKey = ','; break; + case eAGSKeyCodeHyphen: platformKey = '-'; break; + case eAGSKeyCodePeriod: platformKey = '.'; break; + case eAGSKeyCodeForwardSlash: platformKey = '/'; break; + case eAGSKeyCodeColon: platformKey = ':'; break; + case eAGSKeyCodeSemiColon: platformKey = ';'; break; + case eAGSKeyCodeLessThan: platformKey = '<'; break; + case eAGSKeyCodeEquals: platformKey = '='; break; + case eAGSKeyCodeGreaterThan: platformKey = '>'; break; + case eAGSKeyCodeQuestionMark: platformKey = '?'; break; + case eAGSKeyCodeAt: platformKey = '@'; break; + case eAGSKeyCodeOpenBracket: platformKey = '['; break; + case eAGSKeyCodeBackSlash: platformKey = '\\'; break; + case eAGSKeyCodeCloseBracket: platformKey = ']'; break; + case eAGSKeyCodeUnderscore: platformKey = '_'; break; + + case eAGSKeyCode0: platformKey = (__allegro_KEY_0 << 8) | '0'; break; + case eAGSKeyCode1: platformKey = (__allegro_KEY_1 << 8) | '1'; break; + case eAGSKeyCode2: platformKey = (__allegro_KEY_2 << 8) | '2'; break; + case eAGSKeyCode3: platformKey = (__allegro_KEY_3 << 8) | '3'; break; + case eAGSKeyCode4: platformKey = (__allegro_KEY_4 << 8) | '4'; break; + case eAGSKeyCode5: platformKey = (__allegro_KEY_5 << 8) | '5'; break; + case eAGSKeyCode6: platformKey = (__allegro_KEY_6 << 8) | '6'; break; + case eAGSKeyCode7: platformKey = (__allegro_KEY_7 << 8) | '7'; break; + case eAGSKeyCode8: platformKey = (__allegro_KEY_8 << 8) | '8'; break; + case eAGSKeyCode9: platformKey = (__allegro_KEY_9 << 8) | '9'; break; + + case eAGSKeyCodeA: platformKey = (__allegro_KEY_A << 8) | 'a'; break; + case eAGSKeyCodeB: platformKey = (__allegro_KEY_B << 8) | 'b'; break; + case eAGSKeyCodeC: platformKey = (__allegro_KEY_C << 8) | 'c'; break; + case eAGSKeyCodeD: platformKey = (__allegro_KEY_D << 8) | 'd'; break; + case eAGSKeyCodeE: platformKey = (__allegro_KEY_E << 8) | 'e'; break; + case eAGSKeyCodeF: platformKey = (__allegro_KEY_F << 8) | 'f'; break; + case eAGSKeyCodeG: platformKey = (__allegro_KEY_G << 8) | 'g'; break; + case eAGSKeyCodeH: platformKey = (__allegro_KEY_H << 8) | 'h'; break; + case eAGSKeyCodeI: platformKey = (__allegro_KEY_I << 8) | 'i'; break; + case eAGSKeyCodeJ: platformKey = (__allegro_KEY_J << 8) | 'j'; break; + case eAGSKeyCodeK: platformKey = (__allegro_KEY_K << 8) | 'k'; break; + case eAGSKeyCodeL: platformKey = (__allegro_KEY_L << 8) | 'l'; break; + case eAGSKeyCodeM: platformKey = (__allegro_KEY_M << 8) | 'm'; break; + case eAGSKeyCodeN: platformKey = (__allegro_KEY_N << 8) | 'n'; break; + case eAGSKeyCodeO: platformKey = (__allegro_KEY_O << 8) | 'o'; break; + case eAGSKeyCodeP: platformKey = (__allegro_KEY_P << 8) | 'p'; break; + case eAGSKeyCodeQ: platformKey = (__allegro_KEY_Q << 8) | 'q'; break; + case eAGSKeyCodeR: platformKey = (__allegro_KEY_R << 8) | 'r'; break; + case eAGSKeyCodeS: platformKey = (__allegro_KEY_S << 8) | 's'; break; + case eAGSKeyCodeT: platformKey = (__allegro_KEY_T << 8) | 't'; break; + case eAGSKeyCodeU: platformKey = (__allegro_KEY_U << 8) | 'u'; break; + case eAGSKeyCodeV: platformKey = (__allegro_KEY_V << 8) | 'v'; break; + case eAGSKeyCodeW: platformKey = (__allegro_KEY_W << 8) | 'w'; break; + case eAGSKeyCodeX: platformKey = (__allegro_KEY_X << 8) | 'x'; break; + case eAGSKeyCodeY: platformKey = (__allegro_KEY_Y << 8) | 'y'; break; + case eAGSKeyCodeZ: platformKey = (__allegro_KEY_Z << 8) | 'z'; break; + + case eAGSKeyCodeF1: platformKey = __allegro_KEY_F1 << 8; break; + case eAGSKeyCodeF2: platformKey = __allegro_KEY_F2 << 8; break; + case eAGSKeyCodeF3: platformKey = __allegro_KEY_F3 << 8; break; + case eAGSKeyCodeF4: platformKey = __allegro_KEY_F4 << 8; break; + case eAGSKeyCodeF5: platformKey = __allegro_KEY_F5 << 8; break; + case eAGSKeyCodeF6: platformKey = __allegro_KEY_F6 << 8; break; + case eAGSKeyCodeF7: platformKey = __allegro_KEY_F7 << 8; break; + case eAGSKeyCodeF8: platformKey = __allegro_KEY_F8 << 8; break; + case eAGSKeyCodeF9: platformKey = __allegro_KEY_F9 << 8; break; + case eAGSKeyCodeF10: platformKey = __allegro_KEY_F10 << 8; break; + case eAGSKeyCodeF11: platformKey = __allegro_KEY_F11 << 8; break; + case eAGSKeyCodeF12: platformKey = __allegro_KEY_F12 << 8; break; + + case eAGSKeyCodeHome: platformKey = __allegro_KEY_HOME << 8; break; + case eAGSKeyCodeUpArrow: platformKey = __allegro_KEY_UP << 8; break; + case eAGSKeyCodePageUp: platformKey = __allegro_KEY_PGUP << 8; break; + case eAGSKeyCodeLeftArrow: platformKey = __allegro_KEY_LEFT << 8; break; + case eAGSKeyCodeNumPad5: platformKey = __allegro_KEY_5_PAD << 8; break; + case eAGSKeyCodeRightArrow: platformKey = __allegro_KEY_RIGHT << 8; break; + case eAGSKeyCodeEnd: platformKey = __allegro_KEY_END << 8; break; + case eAGSKeyCodeDownArrow: platformKey = __allegro_KEY_DOWN << 8; break; + case eAGSKeyCodePageDown: platformKey = __allegro_KEY_PGDN << 8; break; + case eAGSKeyCodeInsert: platformKey = __allegro_KEY_INSERT << 8; break; + case eAGSKeyCodeDelete: platformKey = __allegro_KEY_DEL << 8; break; + } + + return platformKey; +} diff --git a/engines/ags/engine/ac/keycode.h b/engines/ags/engine/ac/keycode.h new file mode 100644 index 00000000000..f2df0ac3b02 --- /dev/null +++ b/engines/ags/engine/ac/keycode.h @@ -0,0 +1,175 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__KEYCODE_H +#define __AGS_EE_AC__KEYCODE_H + +#include "core/platform.h" + +#define EXTENDED_KEY_CODE ('\0') +#define EXTENDED_KEY_CODE_MACOS ('?') + +#define AGS_EXT_KEY_SHIFT 300 + +// These are based on values in agsdefn.sh +enum eAGSKeyCode +{ + eAGSKeyCodeNone = 0, + + eAGSKeyCodeCtrlA = 1, + eAGSKeyCodeCtrlB = 2, + eAGSKeyCodeCtrlC = 3, + eAGSKeyCodeCtrlD = 4, + eAGSKeyCodeCtrlE = 5, + eAGSKeyCodeCtrlF = 6, + eAGSKeyCodeCtrlG = 7, + eAGSKeyCodeCtrlH = 8, + eAGSKeyCodeCtrlI = 9, + eAGSKeyCodeCtrlJ = 10, + eAGSKeyCodeCtrlK = 11, + eAGSKeyCodeCtrlL = 12, + eAGSKeyCodeCtrlM = 13, + eAGSKeyCodeCtrlN = 14, + eAGSKeyCodeCtrlO = 15, + eAGSKeyCodeCtrlP = 16, + eAGSKeyCodeCtrlQ = 17, + eAGSKeyCodeCtrlR = 18, + eAGSKeyCodeCtrlS = 19, + eAGSKeyCodeCtrlT = 20, + eAGSKeyCodeCtrlU = 21, + eAGSKeyCodeCtrlV = 22, + eAGSKeyCodeCtrlW = 23, + eAGSKeyCodeCtrlX = 24, + eAGSKeyCodeCtrlY = 25, + eAGSKeyCodeCtrlZ = 26, + + eAGSKeyCodeBackspace = 8, + eAGSKeyCodeTab = 9, + eAGSKeyCodeReturn = 13, + eAGSKeyCodeEscape = 27, + eAGSKeyCodeSpace = 32, + eAGSKeyCodeExclamationMark = 33, + eAGSKeyCodeDoubleQuote = 34, + eAGSKeyCodeHash = 35, + eAGSKeyCodeDollar = 36, + eAGSKeyCodePercent = 37, + eAGSKeyCodeAmpersand = 38, + eAGSKeyCodeSingleQuote = 39, + eAGSKeyCodeOpenParenthesis = 40, + eAGSKeyCodeCloseParenthesis = 41, + eAGSKeyCodeAsterisk = 42, + eAGSKeyCodePlus = 43, + eAGSKeyCodeComma = 44, + eAGSKeyCodeHyphen = 45, + eAGSKeyCodePeriod = 46, + eAGSKeyCodeForwardSlash = 47, + eAGSKeyCodeColon = 58, + eAGSKeyCodeSemiColon = 59, + eAGSKeyCodeLessThan = 60, + eAGSKeyCodeEquals = 61, + eAGSKeyCodeGreaterThan = 62, + eAGSKeyCodeQuestionMark = 63, + eAGSKeyCodeAt = 64, + eAGSKeyCodeOpenBracket = 91, + eAGSKeyCodeBackSlash = 92, + eAGSKeyCodeCloseBracket = 93, + eAGSKeyCodeUnderscore = 95, + + eAGSKeyCode0 = 48, + eAGSKeyCode1 = 49, + eAGSKeyCode2 = 50, + eAGSKeyCode3 = 51, + eAGSKeyCode4 = 52, + eAGSKeyCode5 = 53, + eAGSKeyCode6 = 54, + eAGSKeyCode7 = 55, + eAGSKeyCode8 = 56, + eAGSKeyCode9 = 57, + + eAGSKeyCodeA = 65, + eAGSKeyCodeB = 66, + eAGSKeyCodeC = 67, + eAGSKeyCodeD = 68, + eAGSKeyCodeE = 69, + eAGSKeyCodeF = 70, + eAGSKeyCodeG = 71, + eAGSKeyCodeH = 72, + eAGSKeyCodeI = 73, + eAGSKeyCodeJ = 74, + eAGSKeyCodeK = 75, + eAGSKeyCodeL = 76, + eAGSKeyCodeM = 77, + eAGSKeyCodeN = 78, + eAGSKeyCodeO = 79, + eAGSKeyCodeP = 80, + eAGSKeyCodeQ = 81, + eAGSKeyCodeR = 82, + eAGSKeyCodeS = 83, + eAGSKeyCodeT = 84, + eAGSKeyCodeU = 85, + eAGSKeyCodeV = 86, + eAGSKeyCodeW = 87, + eAGSKeyCodeX = 88, + eAGSKeyCodeY = 89, + eAGSKeyCodeZ = 90, + + eAGSKeyCodeF1 = AGS_EXT_KEY_SHIFT + 59, + eAGSKeyCodeF2 = AGS_EXT_KEY_SHIFT + 60, + eAGSKeyCodeF3 = AGS_EXT_KEY_SHIFT + 61, + eAGSKeyCodeF4 = AGS_EXT_KEY_SHIFT + 62, + eAGSKeyCodeF5 = AGS_EXT_KEY_SHIFT + 63, + eAGSKeyCodeF6 = AGS_EXT_KEY_SHIFT + 64, + eAGSKeyCodeF7 = AGS_EXT_KEY_SHIFT + 65, + eAGSKeyCodeF8 = AGS_EXT_KEY_SHIFT + 66, + eAGSKeyCodeF9 = AGS_EXT_KEY_SHIFT + 67, + eAGSKeyCodeF10 = AGS_EXT_KEY_SHIFT + 68, + eAGSKeyCodeF11 = AGS_EXT_KEY_SHIFT + 133, + eAGSKeyCodeF12 = AGS_EXT_KEY_SHIFT + 134, + + eAGSKeyCodeHome = AGS_EXT_KEY_SHIFT + 71, + eAGSKeyCodeUpArrow = AGS_EXT_KEY_SHIFT + 72, + eAGSKeyCodePageUp = AGS_EXT_KEY_SHIFT + 73, + eAGSKeyCodeLeftArrow = AGS_EXT_KEY_SHIFT + 75, + eAGSKeyCodeNumPad5 = AGS_EXT_KEY_SHIFT + 76, + eAGSKeyCodeRightArrow = AGS_EXT_KEY_SHIFT + 77, + eAGSKeyCodeEnd = AGS_EXT_KEY_SHIFT + 79, + eAGSKeyCodeDownArrow = AGS_EXT_KEY_SHIFT + 80, + eAGSKeyCodePageDown = AGS_EXT_KEY_SHIFT + 81, + eAGSKeyCodeInsert = AGS_EXT_KEY_SHIFT + 82, + eAGSKeyCodeDelete = AGS_EXT_KEY_SHIFT + 83, + + eAGSKeyCodeAltTab = AGS_EXT_KEY_SHIFT + 99, + + // These are only used by debugging and abort keys. + // They're based on allegro4 codes so I won't expand here. + eAGSKeyCodeAltV = 322, + eAGSKeyCodeAltX = 324 +}; + +#define AGS_KEYCODE_INSERT (eAGSKeyCodeInsert) +#define AGS_KEYCODE_DELETE (eAGSKeyCodeDelete) +#define AGS_KEYCODE_ALT_TAB (eAGSKeyCodeAltTab) +#define READKEY_CODE_ALT_TAB 0x4000 + +// Gets a key code for "on_key_press" script callback +int GetKeyForKeyPressCb(int keycode); + +// Allegro4 "platform" keycode from an AGS keycode. +// Returns -1 if not found. +int PlatformKeyFromAgsKey(int key); + +#endif // __AGS_EE_AC__KEYCODE_H diff --git a/engines/ags/engine/ac/label.cpp b/engines/ags/engine/ac/label.cpp new file mode 100644 index 00000000000..d7d61850bc0 --- /dev/null +++ b/engines/ags/engine/ac/label.cpp @@ -0,0 +1,172 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/label.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_translation.h" +#include "ac/string.h" + +extern GameSetupStruct game; + +// ** LABEL FUNCTIONS + +const char* Label_GetText_New(GUILabel *labl) { + return CreateNewScriptString(labl->GetText()); +} + +void Label_GetText(GUILabel *labl, char *buffer) { + strcpy(buffer, labl->GetText()); +} + +void Label_SetText(GUILabel *labl, const char *newtx) { + newtx = get_translation(newtx); + + if (strcmp(labl->GetText(), newtx)) { + guis_need_update = 1; + labl->SetText(newtx); + } +} + +int Label_GetTextAlignment(GUILabel *labl) +{ + return labl->TextAlignment; +} + +void Label_SetTextAlignment(GUILabel *labl, int align) +{ + if (labl->TextAlignment != align) { + labl->TextAlignment = (HorAlignment)align; + guis_need_update = 1; + } +} + +int Label_GetColor(GUILabel *labl) { + return labl->TextColor; +} + +void Label_SetColor(GUILabel *labl, int colr) { + if (labl->TextColor != colr) { + labl->TextColor = colr; + guis_need_update = 1; + } +} + +int Label_GetFont(GUILabel *labl) { + return labl->Font; +} + +void Label_SetFont(GUILabel *guil, int fontnum) { + if ((fontnum < 0) || (fontnum >= game.numfonts)) + quit("!SetLabelFont: invalid font number."); + + if (fontnum != guil->Font) { + guil->Font = fontnum; + guis_need_update = 1; + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// void (GUILabel *labl, char *buffer) +RuntimeScriptValue Sc_Label_GetText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(GUILabel, Label_GetText, char); +} + +// void (GUILabel *labl, const char *newtx) +RuntimeScriptValue Sc_Label_SetText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(GUILabel, Label_SetText, const char); +} + +RuntimeScriptValue Sc_Label_GetTextAlignment(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUILabel, Label_GetTextAlignment); +} + +RuntimeScriptValue Sc_Label_SetTextAlignment(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUILabel, Label_SetTextAlignment); +} + + +// int (GUILabel *labl) +RuntimeScriptValue Sc_Label_GetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUILabel, Label_GetFont); +} + +// void (GUILabel *guil, int fontnum) +RuntimeScriptValue Sc_Label_SetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUILabel, Label_SetFont); +} + +// const char* (GUILabel *labl) +RuntimeScriptValue Sc_Label_GetText_New(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUILabel, const char, myScriptStringImpl, Label_GetText_New); +} + +// int (GUILabel *labl) +RuntimeScriptValue Sc_Label_GetColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUILabel, Label_GetColor); +} + +// void (GUILabel *labl, int colr) +RuntimeScriptValue Sc_Label_SetColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUILabel, Label_SetColor); +} + + + +void RegisterLabelAPI() +{ + ccAddExternalObjectFunction("Label::GetText^1", Sc_Label_GetText); + ccAddExternalObjectFunction("Label::SetText^1", Sc_Label_SetText); + ccAddExternalObjectFunction("Label::get_TextAlignment", Sc_Label_GetTextAlignment); + ccAddExternalObjectFunction("Label::set_TextAlignment", Sc_Label_SetTextAlignment); + ccAddExternalObjectFunction("Label::get_Font", Sc_Label_GetFont); + ccAddExternalObjectFunction("Label::set_Font", Sc_Label_SetFont); + ccAddExternalObjectFunction("Label::get_Text", Sc_Label_GetText_New); + ccAddExternalObjectFunction("Label::set_Text", Sc_Label_SetText); + ccAddExternalObjectFunction("Label::get_TextColor", Sc_Label_GetColor); + ccAddExternalObjectFunction("Label::set_TextColor", Sc_Label_SetColor); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Label::GetText^1", (void*)Label_GetText); + ccAddExternalFunctionForPlugin("Label::SetText^1", (void*)Label_SetText); + ccAddExternalFunctionForPlugin("Label::get_Font", (void*)Label_GetFont); + ccAddExternalFunctionForPlugin("Label::set_Font", (void*)Label_SetFont); + ccAddExternalFunctionForPlugin("Label::get_Text", (void*)Label_GetText_New); + ccAddExternalFunctionForPlugin("Label::set_Text", (void*)Label_SetText); + ccAddExternalFunctionForPlugin("Label::get_TextColor", (void*)Label_GetColor); + ccAddExternalFunctionForPlugin("Label::set_TextColor", (void*)Label_SetColor); +} diff --git a/engines/ags/engine/ac/label.h b/engines/ags/engine/ac/label.h new file mode 100644 index 00000000000..684e4268cc3 --- /dev/null +++ b/engines/ags/engine/ac/label.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__LABEL_H +#define __AGS_EE_AC__LABEL_H + +#include "gui/guilabel.h" + +using AGS::Common::GUILabel; + +const char* Label_GetText_New(GUILabel *labl); +void Label_GetText(GUILabel *labl, char *buffer); +void Label_SetText(GUILabel *labl, const char *newtx); +int Label_GetColor(GUILabel *labl); +void Label_SetColor(GUILabel *labl, int colr); +int Label_GetFont(GUILabel *labl); +void Label_SetFont(GUILabel *guil, int fontnum); + +#endif // __AGS_EE_AC__LABEL_H diff --git a/engines/ags/engine/ac/lipsync.h b/engines/ags/engine/ac/lipsync.h new file mode 100644 index 00000000000..5272f5e11f0 --- /dev/null +++ b/engines/ags/engine/ac/lipsync.h @@ -0,0 +1,25 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_LIPSYNC_H +#define __AC_LIPSYNC_H + +struct SpeechLipSyncLine { + char filename[14]; + int *endtimeoffs; + short*frame; + short numPhonemes; +}; + +#endif // __AC_LIPSYNC_H \ No newline at end of file diff --git a/engines/ags/engine/ac/listbox.cpp b/engines/ags/engine/ac/listbox.cpp new file mode 100644 index 00000000000..c6fe065c9b1 --- /dev/null +++ b/engines/ags/engine/ac/listbox.cpp @@ -0,0 +1,674 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/listbox.h" +#include "ac/common.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_game.h" +#include "ac/path_helper.h" +#include "ac/string.h" +#include "gui/guimain.h" +#include "debug/debug_log.h" + +using namespace AGS::Common; + +extern GameState play; +extern GameSetupStruct game; + +// *** LIST BOX FUNCTIONS + +int ListBox_AddItem(GUIListBox *lbb, const char *text) { + if (lbb->AddItem(text) < 0) + return 0; + + guis_need_update = 1; + return 1; +} + +int ListBox_InsertItemAt(GUIListBox *lbb, int index, const char *text) { + if (lbb->InsertItem(index, text) < 0) + return 0; + + guis_need_update = 1; + return 1; +} + +void ListBox_Clear(GUIListBox *listbox) { + listbox->Clear(); + guis_need_update = 1; +} + +void FillDirList(std::set &files, const String &path) +{ + al_ffblk dfb; + int dun = al_findfirst(path, &dfb, FA_SEARCH); + while (!dun) { + files.insert(dfb.name); + dun = al_findnext(&dfb); + } + al_findclose(&dfb); +} + +void ListBox_FillDirList(GUIListBox *listbox, const char *filemask) { + listbox->Clear(); + guis_need_update = 1; + + ResolvedPath rp; + if (!ResolveScriptPath(filemask, true, rp)) + return; + + std::set files; + FillDirList(files, rp.FullPath); + if (!rp.AltPath.IsEmpty() && rp.AltPath.Compare(rp.FullPath) != 0) + FillDirList(files, rp.AltPath); + + for (std::set::const_iterator it = files.begin(); it != files.end(); ++it) + { + listbox->AddItem(*it); + } +} + +int ListBox_GetSaveGameSlots(GUIListBox *listbox, int index) { + if ((index < 0) || (index >= listbox->ItemCount)) + quit("!ListBox.SaveGameSlot: index out of range"); + + return listbox->SavedGameIndex[index]; +} + +int ListBox_FillSaveGameList(GUIListBox *listbox) { + listbox->Clear(); + + int numsaves=0; + int bufix=0; + al_ffblk ffb; + long filedates[MAXSAVEGAMES]; + char buff[200]; + + String svg_dir = get_save_game_directory(); + String searchPath = String::FromFormat("%s""agssave.*", svg_dir.GetCStr()); + + int don = al_findfirst(searchPath, &ffb, FA_SEARCH); + while (!don) { + bufix=0; + if (numsaves >= MAXSAVEGAMES) + break; + // only list games .000 to .099 (to allow higher slots for other perposes) + if (strstr(ffb.name,".0")==nullptr) { + don = al_findnext(&ffb); + continue; + } + const char *numberExtension = strstr(ffb.name, ".0") + 1; + int saveGameSlot = atoi(numberExtension); + GetSaveSlotDescription(saveGameSlot, buff); + listbox->AddItem(buff); + listbox->SavedGameIndex[numsaves] = saveGameSlot; + filedates[numsaves]=(long int)ffb.time; + numsaves++; + don = al_findnext(&ffb); + } + al_findclose(&ffb); + + int nn; + for (nn=0;nnItems[kk]; + listbox->Items[kk] = listbox->Items[kk+1]; + listbox->Items[kk+1] = tempptr; + int numtem = listbox->SavedGameIndex[kk]; + listbox->SavedGameIndex[kk] = listbox->SavedGameIndex[kk+1]; + listbox->SavedGameIndex[kk+1] = numtem; + long numted=filedates[kk]; filedates[kk]=filedates[kk+1]; + filedates[kk+1]=numted; + } + } + } + + // update the global savegameindex[] array for backward compatibilty + for (nn = 0; nn < numsaves; nn++) { + play.filenumbers[nn] = listbox->SavedGameIndex[nn]; + } + + guis_need_update = 1; + listbox->SetSvgIndex(true); + + if (numsaves >= MAXSAVEGAMES) + return 1; + return 0; +} + +int ListBox_GetItemAtLocation(GUIListBox *listbox, int x, int y) { + + if (!guis[listbox->ParentId].IsDisplayed()) + return -1; + + data_to_game_coords(&x, &y); + x = (x - listbox->X) - guis[listbox->ParentId].X; + y = (y - listbox->Y) - guis[listbox->ParentId].Y; + + if ((x < 0) || (y < 0) || (x >= listbox->Width) || (y >= listbox->Height)) + return -1; + + return listbox->GetItemAt(x, y); +} + +char *ListBox_GetItemText(GUIListBox *listbox, int index, char *buffer) { + if ((index < 0) || (index >= listbox->ItemCount)) + quit("!ListBoxGetItemText: invalid item specified"); + strncpy(buffer, listbox->Items[index],198); + buffer[199] = 0; + return buffer; +} + +const char* ListBox_GetItems(GUIListBox *listbox, int index) { + if ((index < 0) || (index >= listbox->ItemCount)) + quit("!ListBox.Items: invalid index specified"); + + return CreateNewScriptString(listbox->Items[index]); +} + +void ListBox_SetItemText(GUIListBox *listbox, int index, const char *newtext) { + if ((index < 0) || (index >= listbox->ItemCount)) + quit("!ListBoxSetItemText: invalid item specified"); + + if (strcmp(listbox->Items[index], newtext)) { + listbox->SetItemText(index, newtext); + guis_need_update = 1; + } +} + +void ListBox_RemoveItem(GUIListBox *listbox, int itemIndex) { + + if ((itemIndex < 0) || (itemIndex >= listbox->ItemCount)) + quit("!ListBoxRemove: invalid listindex specified"); + + listbox->RemoveItem(itemIndex); + guis_need_update = 1; +} + +int ListBox_GetItemCount(GUIListBox *listbox) { + return listbox->ItemCount; +} + +int ListBox_GetFont(GUIListBox *listbox) { + return listbox->Font; +} + +void ListBox_SetFont(GUIListBox *listbox, int newfont) { + + if ((newfont < 0) || (newfont >= game.numfonts)) + quit("!ListBox.Font: invalid font number."); + + if (newfont != listbox->Font) { + listbox->SetFont(newfont); + guis_need_update = 1; + } + +} + +bool ListBox_GetShowBorder(GUIListBox *listbox) { + return listbox->IsBorderShown(); +} + +void ListBox_SetShowBorder(GUIListBox *listbox, bool newValue) { + if (listbox->IsBorderShown() != newValue) + { + listbox->SetShowBorder(newValue); + guis_need_update = 1; + } +} + +bool ListBox_GetShowScrollArrows(GUIListBox *listbox) { + return listbox->AreArrowsShown(); +} + +void ListBox_SetShowScrollArrows(GUIListBox *listbox, bool newValue) { + if (listbox->AreArrowsShown() != newValue) + { + listbox->SetShowArrows(newValue); + guis_need_update = 1; + } +} + +int ListBox_GetHideBorder(GUIListBox *listbox) { + return !ListBox_GetShowBorder(listbox); +} + +void ListBox_SetHideBorder(GUIListBox *listbox, int newValue) { + ListBox_SetShowBorder(listbox, !newValue); +} + +int ListBox_GetHideScrollArrows(GUIListBox *listbox) { + return !ListBox_GetShowScrollArrows(listbox); +} + +void ListBox_SetHideScrollArrows(GUIListBox *listbox, int newValue) { + ListBox_SetShowScrollArrows(listbox, !newValue); +} + +int ListBox_GetSelectedBackColor(GUIListBox *listbox) { + return listbox->SelectedBgColor; +} + +void ListBox_SetSelectedBackColor(GUIListBox *listbox, int colr) { + if (listbox->SelectedBgColor != colr) { + listbox->SelectedBgColor = colr; + guis_need_update = 1; + } +} + +int ListBox_GetSelectedTextColor(GUIListBox *listbox) { + return listbox->SelectedTextColor; +} + +void ListBox_SetSelectedTextColor(GUIListBox *listbox, int colr) { + if (listbox->SelectedTextColor != colr) { + listbox->SelectedTextColor = colr; + guis_need_update = 1; + } +} + +int ListBox_GetTextAlignment(GUIListBox *listbox) { + return listbox->TextAlignment; +} + +void ListBox_SetTextAlignment(GUIListBox *listbox, int align) { + if (listbox->TextAlignment != align) { + listbox->TextAlignment = (HorAlignment)align; + guis_need_update = 1; + } +} + +int ListBox_GetTextColor(GUIListBox *listbox) { + return listbox->TextColor; +} + +void ListBox_SetTextColor(GUIListBox *listbox, int colr) { + if (listbox->TextColor != colr) { + listbox->TextColor = colr; + guis_need_update = 1; + } +} + +int ListBox_GetSelectedIndex(GUIListBox *listbox) { + if ((listbox->SelectedItem < 0) || (listbox->SelectedItem >= listbox->ItemCount)) + return -1; + return listbox->SelectedItem; +} + +void ListBox_SetSelectedIndex(GUIListBox *guisl, int newsel) { + + if (newsel >= guisl->ItemCount) + newsel = -1; + + if (guisl->SelectedItem != newsel) { + guisl->SelectedItem = newsel; + if (newsel >= 0) { + if (newsel < guisl->TopItem) + guisl->TopItem = newsel; + if (newsel >= guisl->TopItem + guisl->VisibleItemCount) + guisl->TopItem = (newsel - guisl->VisibleItemCount) + 1; + } + guis_need_update = 1; + } + +} + +int ListBox_GetTopItem(GUIListBox *listbox) { + return listbox->TopItem; +} + +void ListBox_SetTopItem(GUIListBox *guisl, int item) { + if ((guisl->ItemCount == 0) && (item == 0)) + ; // allow resetting an empty box to the top + else if ((item >= guisl->ItemCount) || (item < 0)) + quit("!ListBoxSetTopItem: tried to set top to beyond top or bottom of list"); + + guisl->TopItem = item; + guis_need_update = 1; +} + +int ListBox_GetRowCount(GUIListBox *listbox) { + return listbox->VisibleItemCount; +} + +void ListBox_ScrollDown(GUIListBox *listbox) { + if (listbox->TopItem + listbox->VisibleItemCount < listbox->ItemCount) { + listbox->TopItem++; + guis_need_update = 1; + } +} + +void ListBox_ScrollUp(GUIListBox *listbox) { + if (listbox->TopItem > 0) { + listbox->TopItem--; + guis_need_update = 1; + } +} + + +GUIListBox* is_valid_listbox (int guin, int objn) { + if ((guin<0) | (guin>=game.numgui)) quit("!ListBox: invalid GUI number"); + if ((objn<0) | (objn>=guis[guin].GetControlCount())) quit("!ListBox: invalid object number"); + if (guis[guin].GetControlType(objn)!=kGUIListBox) + quit("!ListBox: specified control is not a list box"); + guis_need_update = 1; + return (GUIListBox*)guis[guin].GetControl(objn); +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// int (GUIListBox *lbb, const char *text) +RuntimeScriptValue Sc_ListBox_AddItem(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(GUIListBox, ListBox_AddItem, const char); +} + +// void (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_Clear(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(GUIListBox, ListBox_Clear); +} + +// void (GUIListBox *listbox, const char *filemask) +RuntimeScriptValue Sc_ListBox_FillDirList(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(GUIListBox, ListBox_FillDirList, const char); +} + +// int (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_FillSaveGameList(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_FillSaveGameList); +} + +// int (GUIListBox *listbox, int x, int y) +RuntimeScriptValue Sc_ListBox_GetItemAtLocation(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT2(GUIListBox, ListBox_GetItemAtLocation); +} + +// char *(GUIListBox *listbox, int index, char *buffer) +RuntimeScriptValue Sc_ListBox_GetItemText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT_POBJ(GUIListBox, char, myScriptStringImpl, ListBox_GetItemText, char); +} + +// int (GUIListBox *lbb, int index, const char *text) +RuntimeScriptValue Sc_ListBox_InsertItemAt(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT_POBJ(GUIListBox, ListBox_InsertItemAt, const char); +} + +// void (GUIListBox *listbox, int itemIndex) +RuntimeScriptValue Sc_ListBox_RemoveItem(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_RemoveItem); +} + +// void (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_ScrollDown(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(GUIListBox, ListBox_ScrollDown); +} + +// void (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_ScrollUp(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(GUIListBox, ListBox_ScrollUp); +} + +// void (GUIListBox *listbox, int index, const char *newtext) +RuntimeScriptValue Sc_ListBox_SetItemText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT_POBJ(GUIListBox, ListBox_SetItemText, const char); +} + +// int (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_GetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetFont); +} + +// void (GUIListBox *listbox, int newfont) +RuntimeScriptValue Sc_ListBox_SetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_SetFont); +} + +RuntimeScriptValue Sc_ListBox_GetShowBorder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(GUIListBox, ListBox_GetShowBorder); +} + +RuntimeScriptValue Sc_ListBox_SetShowBorder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PBOOL(GUIListBox, ListBox_SetShowBorder); +} + +RuntimeScriptValue Sc_ListBox_GetShowScrollArrows(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(GUIListBox, ListBox_GetShowScrollArrows); +} + +RuntimeScriptValue Sc_ListBox_SetShowScrollArrows(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PBOOL(GUIListBox, ListBox_SetShowScrollArrows); +} + +// int (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_GetHideBorder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetHideBorder); +} + +// void (GUIListBox *listbox, int newValue) +RuntimeScriptValue Sc_ListBox_SetHideBorder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_SetHideBorder); +} + +// int (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_GetHideScrollArrows(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetHideScrollArrows); +} + +// void (GUIListBox *listbox, int newValue) +RuntimeScriptValue Sc_ListBox_SetHideScrollArrows(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_SetHideScrollArrows); +} + +// int (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_GetItemCount(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetItemCount); +} + +// const char* (GUIListBox *listbox, int index) +RuntimeScriptValue Sc_ListBox_GetItems(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT(GUIListBox, const char, myScriptStringImpl, ListBox_GetItems); +} + +// int (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_GetRowCount(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetRowCount); +} + +// int (GUIListBox *listbox, int index) +RuntimeScriptValue Sc_ListBox_GetSaveGameSlots(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT(GUIListBox, ListBox_GetSaveGameSlots); +} + +RuntimeScriptValue Sc_ListBox_GetSelectedBackColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetSelectedBackColor); +} + +// void (GUIListBox *guisl, int newsel) +RuntimeScriptValue Sc_ListBox_SetSelectedBackColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_SetSelectedBackColor); +} + +RuntimeScriptValue Sc_ListBox_GetSelectedTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetSelectedTextColor); +} + +// void (GUIListBox *guisl, int newsel) +RuntimeScriptValue Sc_ListBox_SetSelectedTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_SetSelectedTextColor); +} + +RuntimeScriptValue Sc_ListBox_GetTextAlignment(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetTextAlignment); +} + +// void (GUIListBox *guisl, int newsel) +RuntimeScriptValue Sc_ListBox_SetTextAlignment(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_SetTextAlignment); +} + +RuntimeScriptValue Sc_ListBox_GetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetTextColor); +} + +// void (GUIListBox *guisl, int newsel) +RuntimeScriptValue Sc_ListBox_SetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_SetTextColor); +} + +// int (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_GetSelectedIndex(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetSelectedIndex); +} + +// void (GUIListBox *guisl, int newsel) +RuntimeScriptValue Sc_ListBox_SetSelectedIndex(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_SetSelectedIndex); +} + +// int (GUIListBox *listbox) +RuntimeScriptValue Sc_ListBox_GetTopItem(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUIListBox, ListBox_GetTopItem); +} + +// void (GUIListBox *guisl, int item) +RuntimeScriptValue Sc_ListBox_SetTopItem(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUIListBox, ListBox_SetTopItem); +} + + + +void RegisterListBoxAPI() +{ + ccAddExternalObjectFunction("ListBox::AddItem^1", Sc_ListBox_AddItem); + ccAddExternalObjectFunction("ListBox::Clear^0", Sc_ListBox_Clear); + ccAddExternalObjectFunction("ListBox::FillDirList^1", Sc_ListBox_FillDirList); + ccAddExternalObjectFunction("ListBox::FillSaveGameList^0", Sc_ListBox_FillSaveGameList); + ccAddExternalObjectFunction("ListBox::GetItemAtLocation^2", Sc_ListBox_GetItemAtLocation); + ccAddExternalObjectFunction("ListBox::GetItemText^2", Sc_ListBox_GetItemText); + ccAddExternalObjectFunction("ListBox::InsertItemAt^2", Sc_ListBox_InsertItemAt); + ccAddExternalObjectFunction("ListBox::RemoveItem^1", Sc_ListBox_RemoveItem); + ccAddExternalObjectFunction("ListBox::ScrollDown^0", Sc_ListBox_ScrollDown); + ccAddExternalObjectFunction("ListBox::ScrollUp^0", Sc_ListBox_ScrollUp); + ccAddExternalObjectFunction("ListBox::SetItemText^2", Sc_ListBox_SetItemText); + ccAddExternalObjectFunction("ListBox::get_Font", Sc_ListBox_GetFont); + ccAddExternalObjectFunction("ListBox::set_Font", Sc_ListBox_SetFont); + ccAddExternalObjectFunction("ListBox::get_ShowBorder", Sc_ListBox_GetShowBorder); + ccAddExternalObjectFunction("ListBox::set_ShowBorder", Sc_ListBox_SetShowBorder); + ccAddExternalObjectFunction("ListBox::get_ShowScrollArrows", Sc_ListBox_GetShowScrollArrows); + ccAddExternalObjectFunction("ListBox::set_ShowScrollArrows", Sc_ListBox_SetShowScrollArrows); + // old "inverted" properties + ccAddExternalObjectFunction("ListBox::get_HideBorder", Sc_ListBox_GetHideBorder); + ccAddExternalObjectFunction("ListBox::set_HideBorder", Sc_ListBox_SetHideBorder); + ccAddExternalObjectFunction("ListBox::get_HideScrollArrows", Sc_ListBox_GetHideScrollArrows); + ccAddExternalObjectFunction("ListBox::set_HideScrollArrows", Sc_ListBox_SetHideScrollArrows); + // + ccAddExternalObjectFunction("ListBox::get_ItemCount", Sc_ListBox_GetItemCount); + ccAddExternalObjectFunction("ListBox::geti_Items", Sc_ListBox_GetItems); + ccAddExternalObjectFunction("ListBox::seti_Items", Sc_ListBox_SetItemText); + ccAddExternalObjectFunction("ListBox::get_RowCount", Sc_ListBox_GetRowCount); + ccAddExternalObjectFunction("ListBox::geti_SaveGameSlots", Sc_ListBox_GetSaveGameSlots); + ccAddExternalObjectFunction("ListBox::get_SelectedBackColor", Sc_ListBox_GetSelectedBackColor); + ccAddExternalObjectFunction("ListBox::set_SelectedBackColor", Sc_ListBox_SetSelectedBackColor); + ccAddExternalObjectFunction("ListBox::get_SelectedIndex", Sc_ListBox_GetSelectedIndex); + ccAddExternalObjectFunction("ListBox::set_SelectedIndex", Sc_ListBox_SetSelectedIndex); + ccAddExternalObjectFunction("ListBox::get_SelectedTextColor", Sc_ListBox_GetSelectedTextColor); + ccAddExternalObjectFunction("ListBox::set_SelectedTextColor", Sc_ListBox_SetSelectedTextColor); + ccAddExternalObjectFunction("ListBox::get_TextAlignment", Sc_ListBox_GetTextAlignment); + ccAddExternalObjectFunction("ListBox::set_TextAlignment", Sc_ListBox_SetTextAlignment); + ccAddExternalObjectFunction("ListBox::get_TextColor", Sc_ListBox_GetTextColor); + ccAddExternalObjectFunction("ListBox::set_TextColor", Sc_ListBox_SetTextColor); + ccAddExternalObjectFunction("ListBox::get_TopItem", Sc_ListBox_GetTopItem); + ccAddExternalObjectFunction("ListBox::set_TopItem", Sc_ListBox_SetTopItem); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("ListBox::AddItem^1", (void*)ListBox_AddItem); + ccAddExternalFunctionForPlugin("ListBox::Clear^0", (void*)ListBox_Clear); + ccAddExternalFunctionForPlugin("ListBox::FillDirList^1", (void*)ListBox_FillDirList); + ccAddExternalFunctionForPlugin("ListBox::FillSaveGameList^0", (void*)ListBox_FillSaveGameList); + ccAddExternalFunctionForPlugin("ListBox::GetItemAtLocation^2", (void*)ListBox_GetItemAtLocation); + ccAddExternalFunctionForPlugin("ListBox::GetItemText^2", (void*)ListBox_GetItemText); + ccAddExternalFunctionForPlugin("ListBox::InsertItemAt^2", (void*)ListBox_InsertItemAt); + ccAddExternalFunctionForPlugin("ListBox::RemoveItem^1", (void*)ListBox_RemoveItem); + ccAddExternalFunctionForPlugin("ListBox::ScrollDown^0", (void*)ListBox_ScrollDown); + ccAddExternalFunctionForPlugin("ListBox::ScrollUp^0", (void*)ListBox_ScrollUp); + ccAddExternalFunctionForPlugin("ListBox::SetItemText^2", (void*)ListBox_SetItemText); + ccAddExternalFunctionForPlugin("ListBox::get_Font", (void*)ListBox_GetFont); + ccAddExternalFunctionForPlugin("ListBox::set_Font", (void*)ListBox_SetFont); + ccAddExternalFunctionForPlugin("ListBox::get_HideBorder", (void*)ListBox_GetHideBorder); + ccAddExternalFunctionForPlugin("ListBox::set_HideBorder", (void*)ListBox_SetHideBorder); + ccAddExternalFunctionForPlugin("ListBox::get_HideScrollArrows", (void*)ListBox_GetHideScrollArrows); + ccAddExternalFunctionForPlugin("ListBox::set_HideScrollArrows", (void*)ListBox_SetHideScrollArrows); + ccAddExternalFunctionForPlugin("ListBox::get_ItemCount", (void*)ListBox_GetItemCount); + ccAddExternalFunctionForPlugin("ListBox::geti_Items", (void*)ListBox_GetItems); + ccAddExternalFunctionForPlugin("ListBox::seti_Items", (void*)ListBox_SetItemText); + ccAddExternalFunctionForPlugin("ListBox::get_RowCount", (void*)ListBox_GetRowCount); + ccAddExternalFunctionForPlugin("ListBox::geti_SaveGameSlots", (void*)ListBox_GetSaveGameSlots); + ccAddExternalFunctionForPlugin("ListBox::get_SelectedIndex", (void*)ListBox_GetSelectedIndex); + ccAddExternalFunctionForPlugin("ListBox::set_SelectedIndex", (void*)ListBox_SetSelectedIndex); + ccAddExternalFunctionForPlugin("ListBox::get_TopItem", (void*)ListBox_GetTopItem); + ccAddExternalFunctionForPlugin("ListBox::set_TopItem", (void*)ListBox_SetTopItem); +} diff --git a/engines/ags/engine/ac/listbox.h b/engines/ags/engine/ac/listbox.h new file mode 100644 index 00000000000..24ba70af11a --- /dev/null +++ b/engines/ags/engine/ac/listbox.h @@ -0,0 +1,53 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__LISTBOX_H +#define __AGS_EE_AC__LISTBOX_H + +#include "gui/guilistbox.h" + +using AGS::Common::GUIListBox; + +int ListBox_AddItem(GUIListBox *lbb, const char *text); +int ListBox_InsertItemAt(GUIListBox *lbb, int index, const char *text); +void ListBox_Clear(GUIListBox *listbox); +void ListBox_FillDirList(GUIListBox *listbox, const char *filemask); +int ListBox_GetSaveGameSlots(GUIListBox *listbox, int index); +int ListBox_FillSaveGameList(GUIListBox *listbox); +int ListBox_GetItemAtLocation(GUIListBox *listbox, int x, int y); +char *ListBox_GetItemText(GUIListBox *listbox, int index, char *buffer); +const char* ListBox_GetItems(GUIListBox *listbox, int index); +void ListBox_SetItemText(GUIListBox *listbox, int index, const char *newtext); +void ListBox_RemoveItem(GUIListBox *listbox, int itemIndex); +int ListBox_GetItemCount(GUIListBox *listbox); +int ListBox_GetFont(GUIListBox *listbox); +void ListBox_SetFont(GUIListBox *listbox, int newfont); +int ListBox_GetHideBorder(GUIListBox *listbox); +void ListBox_SetHideBorder(GUIListBox *listbox, int newValue); +int ListBox_GetHideScrollArrows(GUIListBox *listbox); +void ListBox_SetHideScrollArrows(GUIListBox *listbox, int newValue); +int ListBox_GetSelectedIndex(GUIListBox *listbox); +void ListBox_SetSelectedIndex(GUIListBox *guisl, int newsel); +int ListBox_GetTopItem(GUIListBox *listbox); +void ListBox_SetTopItem(GUIListBox *guisl, int item); +int ListBox_GetRowCount(GUIListBox *listbox); +void ListBox_ScrollDown(GUIListBox *listbox); +void ListBox_ScrollUp(GUIListBox *listbox); + +GUIListBox* is_valid_listbox (int guin, int objn); + +#endif // __AGS_EE_AC__LISTBOX_H diff --git a/engines/ags/engine/ac/math.cpp b/engines/ags/engine/ac/math.cpp new file mode 100644 index 00000000000..5c81231643e --- /dev/null +++ b/engines/ags/engine/ac/math.cpp @@ -0,0 +1,318 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/math.h" +#include "ac/common.h" // quit +#include "util/math.h" + +int FloatToInt(float value, int roundDirection) +{ + if (value >= 0.0) { + if (roundDirection == eRoundDown) + return static_cast(value); + else if (roundDirection == eRoundNearest) + return static_cast(value + 0.5); + else if (roundDirection == eRoundUp) + return static_cast(value + 0.999999); + else + quit("!FloatToInt: invalid round direction"); + } + else { + // negative number + if (roundDirection == eRoundUp) + return static_cast(value); // this just truncates + else if (roundDirection == eRoundNearest) + return static_cast(value - 0.5); + else if (roundDirection == eRoundDown) + return static_cast(value - 0.999999); + else + quit("!FloatToInt: invalid round direction"); + } + return 0; +} + +float IntToFloat(int value) +{ + return static_cast(value); +} + +float StringToFloat(const char *theString) +{ + return static_cast(atof(theString)); +} + +float Math_Cos(float value) +{ + return cos(value); +} + +float Math_Sin(float value) +{ + return sin(value); +} + +float Math_Tan(float value) +{ + return tan(value); +} + +float Math_ArcCos(float value) +{ + return acos(value); +} + +float Math_ArcSin(float value) +{ + return asin(value); +} + +float Math_ArcTan(float value) +{ + return atan(value); +} + +float Math_ArcTan2(float yval, float xval) +{ + return atan2(yval, xval); +} + +float Math_Log(float value) +{ + return log(value); +} + +float Math_Log10(float value) +{ + return ::log10(value); +} + +float Math_Exp(float value) +{ + return exp(value); +} + +float Math_Cosh(float value) +{ + return cosh(value); +} + +float Math_Sinh(float value) +{ + return sinh(value); +} + +float Math_Tanh(float value) +{ + return tanh(value); +} + +float Math_RaiseToPower(float base, float exp) +{ + return ::pow(base, exp); +} + +float Math_DegreesToRadians(float value) +{ + return static_cast(value * (M_PI / 180.0)); +} + +float Math_RadiansToDegrees(float value) +{ + return static_cast(value * (180.0 / M_PI)); +} + +float Math_GetPi() +{ + return static_cast(M_PI); +} + +float Math_Sqrt(float value) +{ + if (value < 0.0) + quit("!Sqrt: cannot perform square root of negative number"); + + return ::sqrt(value); +} + +int __Rand(int upto) +{ + upto++; + if (upto < 1) + quit("!Random: invalid parameter passed -- must be at least 0."); + return rand()%upto; +} + + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// float (float value) +RuntimeScriptValue Sc_Math_ArcCos(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_ArcCos); +} + +// float (float value) +RuntimeScriptValue Sc_Math_ArcSin(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_ArcSin); +} + +// float (float value) +RuntimeScriptValue Sc_Math_ArcTan(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_ArcTan); +} + +// float (SCRIPT_FLOAT(yval), SCRIPT_FLOAT(xval)) +RuntimeScriptValue Sc_Math_ArcTan2(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT2(Math_ArcTan2); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Cos(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Cos); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Cosh(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Cosh); +} + +// float (float value) +RuntimeScriptValue Sc_Math_DegreesToRadians(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_DegreesToRadians); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Exp(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Exp); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Log(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Log); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Log10(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Log10); +} + +// float (float value) +RuntimeScriptValue Sc_Math_RadiansToDegrees(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_RadiansToDegrees); +} + +// float (SCRIPT_FLOAT(base), SCRIPT_FLOAT(exp)) +RuntimeScriptValue Sc_Math_RaiseToPower(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT2(Math_RaiseToPower); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Sin(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Sin); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Sinh(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Sinh); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Sqrt(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Sqrt); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Tan(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Tan); +} + +// float (float value) +RuntimeScriptValue Sc_Math_Tanh(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT_PFLOAT(Math_Tanh); +} + +// float () +RuntimeScriptValue Sc_Math_GetPi(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT(Math_GetPi); +} + + +void RegisterMathAPI() +{ + ccAddExternalStaticFunction("Maths::ArcCos^1", Sc_Math_ArcCos); + ccAddExternalStaticFunction("Maths::ArcSin^1", Sc_Math_ArcSin); + ccAddExternalStaticFunction("Maths::ArcTan^1", Sc_Math_ArcTan); + ccAddExternalStaticFunction("Maths::ArcTan2^2", Sc_Math_ArcTan2); + ccAddExternalStaticFunction("Maths::Cos^1", Sc_Math_Cos); + ccAddExternalStaticFunction("Maths::Cosh^1", Sc_Math_Cosh); + ccAddExternalStaticFunction("Maths::DegreesToRadians^1", Sc_Math_DegreesToRadians); + ccAddExternalStaticFunction("Maths::Exp^1", Sc_Math_Exp); + ccAddExternalStaticFunction("Maths::Log^1", Sc_Math_Log); + ccAddExternalStaticFunction("Maths::Log10^1", Sc_Math_Log10); + ccAddExternalStaticFunction("Maths::RadiansToDegrees^1", Sc_Math_RadiansToDegrees); + ccAddExternalStaticFunction("Maths::RaiseToPower^2", Sc_Math_RaiseToPower); + ccAddExternalStaticFunction("Maths::Sin^1", Sc_Math_Sin); + ccAddExternalStaticFunction("Maths::Sinh^1", Sc_Math_Sinh); + ccAddExternalStaticFunction("Maths::Sqrt^1", Sc_Math_Sqrt); + ccAddExternalStaticFunction("Maths::Tan^1", Sc_Math_Tan); + ccAddExternalStaticFunction("Maths::Tanh^1", Sc_Math_Tanh); + ccAddExternalStaticFunction("Maths::get_Pi", Sc_Math_GetPi); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Maths::ArcCos^1", (void*)Math_ArcCos); + ccAddExternalFunctionForPlugin("Maths::ArcSin^1", (void*)Math_ArcSin); + ccAddExternalFunctionForPlugin("Maths::ArcTan^1", (void*)Math_ArcTan); + ccAddExternalFunctionForPlugin("Maths::ArcTan2^2", (void*)Math_ArcTan2); + ccAddExternalFunctionForPlugin("Maths::Cos^1", (void*)Math_Cos); + ccAddExternalFunctionForPlugin("Maths::Cosh^1", (void*)Math_Cosh); + ccAddExternalFunctionForPlugin("Maths::DegreesToRadians^1", (void*)Math_DegreesToRadians); + ccAddExternalFunctionForPlugin("Maths::Exp^1", (void*)Math_Exp); + ccAddExternalFunctionForPlugin("Maths::Log^1", (void*)Math_Log); + ccAddExternalFunctionForPlugin("Maths::Log10^1", (void*)Math_Log10); + ccAddExternalFunctionForPlugin("Maths::RadiansToDegrees^1", (void*)Math_RadiansToDegrees); + ccAddExternalFunctionForPlugin("Maths::RaiseToPower^2", (void*)Math_RaiseToPower); + ccAddExternalFunctionForPlugin("Maths::Sin^1", (void*)Math_Sin); + ccAddExternalFunctionForPlugin("Maths::Sinh^1", (void*)Math_Sinh); + ccAddExternalFunctionForPlugin("Maths::Sqrt^1", (void*)Math_Sqrt); + ccAddExternalFunctionForPlugin("Maths::Tan^1", (void*)Math_Tan); + ccAddExternalFunctionForPlugin("Maths::Tanh^1", (void*)Math_Tanh); + ccAddExternalFunctionForPlugin("Maths::get_Pi", (void*)Math_GetPi); +} diff --git a/engines/ags/engine/ac/math.h b/engines/ags/engine/ac/math.h new file mode 100644 index 00000000000..872b445c5b4 --- /dev/null +++ b/engines/ags/engine/ac/math.h @@ -0,0 +1,55 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__MATH_H +#define __AGS_EE_AC__MATH_H + +#include "core/types.h" + +enum RoundDirections { + eRoundDown = 0, + eRoundNearest = 1, + eRoundUp = 2 +}; + + +int FloatToInt(float value, int roundDirection); +float IntToFloat(int value); +float StringToFloat(const char *theString); +float Math_Cos(float value); +float Math_Sin(float value); +float Math_Tan(float value); +float Math_ArcCos(float value); +float Math_ArcSin(float value); +float Math_ArcTan(float value); +float Math_ArcTan2(float yval, float xval); +float Math_Log(float value); +float Math_Log10(float value); +float Math_Exp(float value); +float Math_Cosh(float value); +float Math_Sinh(float value); +float Math_Tanh(float value); +float Math_RaiseToPower(float base, float exp); +float Math_DegreesToRadians(float value); +float Math_RadiansToDegrees(float value); +float Math_GetPi(); +float Math_Sqrt(float value); + +int __Rand(int upto); +#define Random __Rand + +#endif // __AGS_EE_AC__MATH_H diff --git a/engines/ags/engine/ac/mouse.cpp b/engines/ags/engine/ac/mouse.cpp new file mode 100644 index 00000000000..eceb5e680ce --- /dev/null +++ b/engines/ags/engine/ac/mouse.cpp @@ -0,0 +1,664 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/mouse.h" +#include "ac/common.h" +#include "ac/characterinfo.h" +#include "ac/draw.h" +#include "ac/dynobj/scriptmouse.h" +#include "ac/dynobj/scriptsystem.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_mouse.h" +#include "ac/global_plugin.h" +#include "ac/global_screen.h" +#include "ac/system.h" +#include "ac/viewframe.h" +#include "debug/debug_log.h" +#include "gui/guibutton.h" +#include "gui/guimain.h" +#include "device/mousew32.h" +#include "ac/spritecache.h" +#include "gfx/graphicsdriver.h" +#include "gfx/gfxfilter.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern GameState play; +extern ScriptSystem scsystem; +extern Bitmap *mousecurs[MAXCURSORS]; +extern SpriteCache spriteset; +extern CharacterInfo*playerchar; +extern IGraphicsDriver *gfxDriver; + +extern void ags_domouse(int str); +extern int misbuttondown(int buno); + +ScriptMouse scmouse; +int cur_mode,cur_cursor; +int mouse_frame=0,mouse_delay=0; +int lastmx=-1,lastmy=-1; +char alpha_blend_cursor = 0; +Bitmap *dotted_mouse_cursor = nullptr; +IDriverDependantBitmap *mouseCursor = nullptr; +Bitmap *blank_mouse_cursor = nullptr; + +// The Mouse:: functions are static so the script doesn't pass +// in an object parameter +void Mouse_SetVisible(int isOn) { + if (isOn) + ShowMouseCursor(); + else + HideMouseCursor(); +} + +int Mouse_GetVisible() { + if (play.mouse_cursor_hidden) + return 0; + return 1; +} + +void SetMouseBounds(int x1, int y1, int x2, int y2) +{ + int xmax = game_to_data_coord(play.GetMainViewport().GetWidth()) - 1; + int ymax = game_to_data_coord(play.GetMainViewport().GetHeight()) - 1; + if ((x1 == 0) && (y1 == 0) && (x2 == 0) && (y2 == 0)) + { + x2 = xmax; + y2 = ymax; + } + else + { + if (x1 < 0 || x1 > xmax || x2 < 0 || x2 > xmax || x1 > x2 || y1 < 0 || y1 > ymax || y2 < 0 || y2 > ymax || y1 > y2) + debug_script_warn("SetMouseBounds: arguments are out of range and will be corrected: (%d,%d)-(%d,%d), range is (%d,%d)-(%d,%d)", + x1, y1, x2, y2, 0, 0, xmax, ymax); + x1 = Math::Clamp(x1, 0, xmax); + x2 = Math::Clamp(x2, x1, xmax); + y1 = Math::Clamp(y1, 0, ymax); + y2 = Math::Clamp(y2, y1, ymax); + } + + debug_script_log("Mouse bounds constrained to (%d,%d)-(%d,%d)", x1, y1, x2, y2); + data_to_game_coords(&x1, &y1); + data_to_game_round_up(&x2, &y2); + + play.mboundx1 = x1; + play.mboundx2 = x2; + play.mboundy1 = y1; + play.mboundy2 = y2; + Mouse::SetMoveLimit(Rect(x1, y1, x2, y2)); +} + +// mouse cursor functions: +// set_mouse_cursor: changes visual appearance to specified cursor +void set_mouse_cursor(int newcurs) { + const int hotspotx = game.mcurs[newcurs].hotx, hotspoty = game.mcurs[newcurs].hoty; + msethotspot(hotspotx, hotspoty); + + // if it's same cursor and there's animation in progress, then don't assign a new pic just yet + if (newcurs == cur_cursor && game.mcurs[newcurs].view >= 0 && + (mouse_frame > 0 || mouse_delay > 0)) + { + return; + } + + // reset animation timing only if it's another cursor + if (newcurs != cur_cursor) + { + cur_cursor = newcurs; + mouse_frame = 0; + mouse_delay = 0; + } + + // Assign new pic + set_new_cursor_graphic(game.mcurs[newcurs].pic); + delete dotted_mouse_cursor; + dotted_mouse_cursor = nullptr; + + // If it's inventory cursor, draw hotspot crosshair sprite upon it + if ((newcurs == MODE_USE) && (game.mcurs[newcurs].pic > 0) && + ((game.hotdot > 0) || (game.invhotdotsprite > 0)) ) { + // If necessary, create a copy of the cursor and put the hotspot + // dot onto it + dotted_mouse_cursor = BitmapHelper::CreateBitmapCopy(mousecurs[0]); + + if (game.invhotdotsprite > 0) { + draw_sprite_slot_support_alpha(dotted_mouse_cursor, + (game.SpriteInfos[game.mcurs[newcurs].pic].Flags & SPF_ALPHACHANNEL) != 0, + hotspotx - game.SpriteInfos[game.invhotdotsprite].Width / 2, + hotspoty - game.SpriteInfos[game.invhotdotsprite].Height / 2, + game.invhotdotsprite); + } + else { + putpixel_compensate (dotted_mouse_cursor, hotspotx, hotspoty, MakeColor(game.hotdot)); + + if (game.hotdotouter > 0) { + int outercol = MakeColor(game.hotdotouter); + + putpixel_compensate (dotted_mouse_cursor, hotspotx + get_fixed_pixel_size(1), hotspoty, outercol); + putpixel_compensate (dotted_mouse_cursor, hotspotx, hotspoty + get_fixed_pixel_size(1), outercol); + putpixel_compensate (dotted_mouse_cursor, hotspotx - get_fixed_pixel_size(1), hotspoty, outercol); + putpixel_compensate (dotted_mouse_cursor, hotspotx, hotspoty - get_fixed_pixel_size(1), outercol); + } + } + mousecurs[0] = dotted_mouse_cursor; + update_cached_mouse_cursor(); + } +} + +// set_default_cursor: resets visual appearance to current mode (walk, look, etc) +void set_default_cursor() { + set_mouse_cursor(cur_mode); +} + +// permanently change cursor graphic +void ChangeCursorGraphic (int curs, int newslot) { + if ((curs < 0) || (curs >= game.numcursors)) + quit("!ChangeCursorGraphic: invalid mouse cursor"); + + if ((curs == MODE_USE) && (game.options[OPT_FIXEDINVCURSOR] == 0)) + debug_script_warn("Mouse.ChangeModeGraphic should not be used on the Inventory cursor when the cursor is linked to the active inventory item"); + + game.mcurs[curs].pic = newslot; + spriteset.Precache(newslot); + if (curs == cur_mode) + set_mouse_cursor (curs); +} + +int Mouse_GetModeGraphic(int curs) { + if ((curs < 0) || (curs >= game.numcursors)) + quit("!Mouse.GetModeGraphic: invalid mouse cursor"); + + return game.mcurs[curs].pic; +} + +void ChangeCursorHotspot (int curs, int x, int y) { + if ((curs < 0) || (curs >= game.numcursors)) + quit("!ChangeCursorHotspot: invalid mouse cursor"); + game.mcurs[curs].hotx = data_to_game_coord(x); + game.mcurs[curs].hoty = data_to_game_coord(y); + if (curs == cur_cursor) + set_mouse_cursor (cur_cursor); +} + +void Mouse_ChangeModeView(int curs, int newview) { + if ((curs < 0) || (curs >= game.numcursors)) + quit("!Mouse.ChangeModeView: invalid mouse cursor"); + + newview--; + + game.mcurs[curs].view = newview; + + if (newview >= 0) + { + precache_view(newview); + } + + if (curs == cur_cursor) + mouse_delay = 0; // force update +} + +void SetNextCursor () { + set_cursor_mode (find_next_enabled_cursor(cur_mode + 1)); +} + +void SetPreviousCursor() { + set_cursor_mode(find_previous_enabled_cursor(cur_mode - 1)); +} + +// set_cursor_mode: changes mode and appearance +void set_cursor_mode(int newmode) { + if ((newmode < 0) || (newmode >= game.numcursors)) + quit("!SetCursorMode: invalid cursor mode specified"); + + guis_need_update = 1; + if (game.mcurs[newmode].flags & MCF_DISABLED) { + find_next_enabled_cursor(newmode); + return; } + if (newmode == MODE_USE) { + if (playerchar->activeinv == -1) { + find_next_enabled_cursor(0); + return; + } + update_inv_cursor(playerchar->activeinv); + } + cur_mode=newmode; + set_default_cursor(); + + debug_script_log("Cursor mode set to %d", newmode); +} + +void enable_cursor_mode(int modd) { + game.mcurs[modd].flags&=~MCF_DISABLED; + // now search the interfaces for related buttons to re-enable + int uu,ww; + + for (uu=0;uuClickAction[kMouseLeft]!=kGUIAction_SetMode) continue; + if (gbpt->ClickData[kMouseLeft]!=modd) continue; + gbpt->SetEnabled(true); + } + } + guis_need_update = 1; +} + +void disable_cursor_mode(int modd) { + game.mcurs[modd].flags|=MCF_DISABLED; + // now search the interfaces for related buttons to kill + int uu,ww; + + for (uu=0;uuClickAction[kMouseLeft]!=kGUIAction_SetMode) continue; + if (gbpt->ClickData[kMouseLeft]!=modd) continue; + gbpt->SetEnabled(false); + } + } + if (cur_mode==modd) find_next_enabled_cursor(modd); + guis_need_update = 1; +} + +void RefreshMouse() { + ags_domouse(DOMOUSE_NOCURSOR); + scmouse.x = game_to_data_coord(mousex); + scmouse.y = game_to_data_coord(mousey); +} + +void SetMousePosition (int newx, int newy) { + const Rect &viewport = play.GetMainViewport(); + + if (newx < 0) + newx = 0; + if (newy < 0) + newy = 0; + if (newx >= viewport.GetWidth()) + newx = viewport.GetWidth() - 1; + if (newy >= viewport.GetHeight()) + newy = viewport.GetHeight() - 1; + + data_to_game_coords(&newx, &newy); + Mouse::SetPosition(Point(newx, newy)); + RefreshMouse(); +} + +int GetCursorMode() { + return cur_mode; +} + +int IsButtonDown(int which) { + if ((which < 1) || (which > 3)) + quit("!IsButtonDown: only works with eMouseLeft, eMouseRight, eMouseMiddle"); + if (misbuttondown(which-1)) + return 1; + return 0; +} + +int IsModeEnabled(int which) { + return (which < 0) || (which >= game.numcursors) ? 0 : + which == MODE_USE ? playerchar->activeinv > 0 : + (game.mcurs[which].flags & MCF_DISABLED) == 0; +} + +void Mouse_EnableControl(bool on) +{ + usetup.mouse_ctrl_enabled = on; // remember setting in config + + bool is_windowed = scsystem.windowed != 0; + // Whether mouse movement should be controlled by the engine - this is + // determined based on related config option. + bool should_control_mouse = usetup.mouse_ctrl_when == kMouseCtrl_Always || + (usetup.mouse_ctrl_when == kMouseCtrl_Fullscreen && !is_windowed); + // Whether mouse movement control is supported by the engine - this is + // determined on per platform basis. Some builds may not have such + // capability, e.g. because of how backend library implements mouse utils. + bool can_control_mouse = platform->IsMouseControlSupported(is_windowed); + // The resulting choice is made based on two aforementioned factors. + on &= should_control_mouse && can_control_mouse; + if (on) + Mouse::EnableControl(!is_windowed); + else + Mouse::DisableControl(); +} + +//============================================================================= + +int GetMouseCursor() { + return cur_cursor; +} + +void update_script_mouse_coords() { + scmouse.x = game_to_data_coord(mousex); + scmouse.y = game_to_data_coord(mousey); +} + +void update_inv_cursor(int invnum) { + + if ((game.options[OPT_FIXEDINVCURSOR]==0) && (invnum > 0)) { + int cursorSprite = game.invinfo[invnum].cursorPic; + + // Fall back to the inventory pic if no cursor pic is defined. + if (cursorSprite == 0) + cursorSprite = game.invinfo[invnum].pic; + + game.mcurs[MODE_USE].pic = cursorSprite; + // all cursor images must be pre-cached + spriteset.Precache(cursorSprite); + + if ((game.invinfo[invnum].hotx > 0) || (game.invinfo[invnum].hoty > 0)) { + // if the hotspot was set (unfortunately 0,0 isn't a valid co-ord) + game.mcurs[MODE_USE].hotx=game.invinfo[invnum].hotx; + game.mcurs[MODE_USE].hoty=game.invinfo[invnum].hoty; + } + else { + game.mcurs[MODE_USE].hotx = game.SpriteInfos[cursorSprite].Width / 2; + game.mcurs[MODE_USE].hoty = game.SpriteInfos[cursorSprite].Height / 2; + } + } +} + +void update_cached_mouse_cursor() +{ + if (mouseCursor != nullptr) + gfxDriver->DestroyDDB(mouseCursor); + mouseCursor = gfxDriver->CreateDDBFromBitmap(mousecurs[0], alpha_blend_cursor != 0); +} + +void set_new_cursor_graphic (int spriteslot) { + mousecurs[0] = spriteset[spriteslot]; + + // It looks like spriteslot 0 can be used in games with version 2.72 and lower. + // The NULL check should ensure that the sprite is valid anyway. + if (((spriteslot < 1) && (loaded_game_file_version > kGameVersion_272)) || (mousecurs[0] == nullptr)) + { + if (blank_mouse_cursor == nullptr) + { + blank_mouse_cursor = BitmapHelper::CreateTransparentBitmap(1, 1, game.GetColorDepth()); + } + mousecurs[0] = blank_mouse_cursor; + } + + if (game.SpriteInfos[spriteslot].Flags & SPF_ALPHACHANNEL) + alpha_blend_cursor = 1; + else + alpha_blend_cursor = 0; + + update_cached_mouse_cursor(); +} + +bool is_standard_cursor_enabled(int curs) { + if ((game.mcurs[curs].flags & MCF_DISABLED) == 0) { + // inventory cursor, and they have an active item + if (curs == MODE_USE) + { + if (playerchar->activeinv > 0) + return true; + } + // standard cursor that's not disabled, go with it + else if (game.mcurs[curs].flags & MCF_STANDARD) + return true; + } + return false; +} + +int find_next_enabled_cursor(int startwith) { + if (startwith >= game.numcursors) + startwith = 0; + int testing=startwith; + do { + if (is_standard_cursor_enabled(testing)) break; + testing++; + if (testing >= game.numcursors) testing=0; + } while (testing!=startwith); + + if (testing!=startwith) + set_cursor_mode(testing); + + return testing; +} + +int find_previous_enabled_cursor(int startwith) { + if (startwith < 0) + startwith = game.numcursors - 1; + int testing = startwith; + do { + if (is_standard_cursor_enabled(testing)) break; + testing--; + if (testing < 0) testing = game.numcursors - 1; + } while (testing != startwith); + + if (testing != startwith) + set_cursor_mode(testing); + + return testing; +} + + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/global_game.h" + +// void (int curs, int newslot) +RuntimeScriptValue Sc_ChangeCursorGraphic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(ChangeCursorGraphic); +} + +// void (int curs, int x, int y) +RuntimeScriptValue Sc_ChangeCursorHotspot(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT3(ChangeCursorHotspot); +} + +// void (int curs, int newview) +RuntimeScriptValue Sc_Mouse_ChangeModeView(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(Mouse_ChangeModeView); +} + +// void (int modd) +RuntimeScriptValue Sc_disable_cursor_mode(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(disable_cursor_mode); +} + +// void (int modd) +RuntimeScriptValue Sc_enable_cursor_mode(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(enable_cursor_mode); +} + +// int (int curs) +RuntimeScriptValue Sc_Mouse_GetModeGraphic(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(Mouse_GetModeGraphic); +} + +// int (int which) +RuntimeScriptValue Sc_IsButtonDown(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsButtonDown); +} + +// int (int which) +RuntimeScriptValue Sc_IsModeEnabled(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_PINT(IsModeEnabled); +} + +// void (); +RuntimeScriptValue Sc_SaveCursorForLocationChange(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(SaveCursorForLocationChange); +} + +// void () +RuntimeScriptValue Sc_SetNextCursor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(SetNextCursor); +} + +// void () +RuntimeScriptValue Sc_SetPreviousCursor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(SetPreviousCursor); +} + +// void (int x1, int y1, int x2, int y2) +RuntimeScriptValue Sc_SetMouseBounds(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT4(SetMouseBounds); +} + +// void (int newx, int newy) +RuntimeScriptValue Sc_SetMousePosition(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT2(SetMousePosition); +} + +// void () +RuntimeScriptValue Sc_RefreshMouse(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(RefreshMouse); +} + +// void () +RuntimeScriptValue Sc_set_default_cursor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID(set_default_cursor); +} + +// void (int newcurs) +RuntimeScriptValue Sc_set_mouse_cursor(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(set_mouse_cursor); +} + +// int () +RuntimeScriptValue Sc_GetCursorMode(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetCursorMode); +} + +// void (int newmode) +RuntimeScriptValue Sc_set_cursor_mode(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(set_cursor_mode); +} + +// int () +RuntimeScriptValue Sc_Mouse_GetVisible(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(Mouse_GetVisible); +} + +// void (int isOn) +RuntimeScriptValue Sc_Mouse_SetVisible(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(Mouse_SetVisible); +} + +RuntimeScriptValue Sc_Mouse_Click(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(PluginSimulateMouseClick); +} + +RuntimeScriptValue Sc_Mouse_GetControlEnabled(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_BOOL(Mouse::IsControlEnabled); +} + +RuntimeScriptValue Sc_Mouse_SetControlEnabled(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PBOOL(Mouse_EnableControl); +} + + +RuntimeScriptValue Sc_Mouse_GetSpeed(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_FLOAT(Mouse::GetSpeed); +} + +RuntimeScriptValue Sc_Mouse_SetSpeed(const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_VARIABLE_VALUE("Mouse::Speed"); + Mouse::SetSpeed(params[0].FValue); + return RuntimeScriptValue(); +} + +void RegisterMouseAPI() +{ + ccAddExternalStaticFunction("Mouse::ChangeModeGraphic^2", Sc_ChangeCursorGraphic); + ccAddExternalStaticFunction("Mouse::ChangeModeHotspot^3", Sc_ChangeCursorHotspot); + ccAddExternalStaticFunction("Mouse::ChangeModeView^2", Sc_Mouse_ChangeModeView); + ccAddExternalStaticFunction("Mouse::Click^1", Sc_Mouse_Click); + ccAddExternalStaticFunction("Mouse::DisableMode^1", Sc_disable_cursor_mode); + ccAddExternalStaticFunction("Mouse::EnableMode^1", Sc_enable_cursor_mode); + ccAddExternalStaticFunction("Mouse::GetModeGraphic^1", Sc_Mouse_GetModeGraphic); + ccAddExternalStaticFunction("Mouse::IsButtonDown^1", Sc_IsButtonDown); + ccAddExternalStaticFunction("Mouse::IsModeEnabled^1", Sc_IsModeEnabled); + ccAddExternalStaticFunction("Mouse::SaveCursorUntilItLeaves^0", Sc_SaveCursorForLocationChange); + ccAddExternalStaticFunction("Mouse::SelectNextMode^0", Sc_SetNextCursor); + ccAddExternalStaticFunction("Mouse::SelectPreviousMode^0", Sc_SetPreviousCursor); + ccAddExternalStaticFunction("Mouse::SetBounds^4", Sc_SetMouseBounds); + ccAddExternalStaticFunction("Mouse::SetPosition^2", Sc_SetMousePosition); + ccAddExternalStaticFunction("Mouse::Update^0", Sc_RefreshMouse); + ccAddExternalStaticFunction("Mouse::UseDefaultGraphic^0", Sc_set_default_cursor); + ccAddExternalStaticFunction("Mouse::UseModeGraphic^1", Sc_set_mouse_cursor); + ccAddExternalStaticFunction("Mouse::get_ControlEnabled", Sc_Mouse_GetControlEnabled); + ccAddExternalStaticFunction("Mouse::set_ControlEnabled", Sc_Mouse_SetControlEnabled); + ccAddExternalStaticFunction("Mouse::get_Mode", Sc_GetCursorMode); + ccAddExternalStaticFunction("Mouse::set_Mode", Sc_set_cursor_mode); + ccAddExternalStaticFunction("Mouse::get_Speed", Sc_Mouse_GetSpeed); + ccAddExternalStaticFunction("Mouse::set_Speed", Sc_Mouse_SetSpeed); + ccAddExternalStaticFunction("Mouse::get_Visible", Sc_Mouse_GetVisible); + ccAddExternalStaticFunction("Mouse::set_Visible", Sc_Mouse_SetVisible); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Mouse::ChangeModeGraphic^2", (void*)ChangeCursorGraphic); + ccAddExternalFunctionForPlugin("Mouse::ChangeModeHotspot^3", (void*)ChangeCursorHotspot); + ccAddExternalFunctionForPlugin("Mouse::ChangeModeView^2", (void*)Mouse_ChangeModeView); + ccAddExternalFunctionForPlugin("Mouse::DisableMode^1", (void*)disable_cursor_mode); + ccAddExternalFunctionForPlugin("Mouse::EnableMode^1", (void*)enable_cursor_mode); + ccAddExternalFunctionForPlugin("Mouse::GetModeGraphic^1", (void*)Mouse_GetModeGraphic); + ccAddExternalFunctionForPlugin("Mouse::IsButtonDown^1", (void*)IsButtonDown); + ccAddExternalFunctionForPlugin("Mouse::IsModeEnabled^1", (void*)IsModeEnabled); + ccAddExternalFunctionForPlugin("Mouse::SaveCursorUntilItLeaves^0", (void*)SaveCursorForLocationChange); + ccAddExternalFunctionForPlugin("Mouse::SelectNextMode^0", (void*)SetNextCursor); + ccAddExternalFunctionForPlugin("Mouse::SelectPreviousMode^0", (void*)SetPreviousCursor); + ccAddExternalFunctionForPlugin("Mouse::SetBounds^4", (void*)SetMouseBounds); + ccAddExternalFunctionForPlugin("Mouse::SetPosition^2", (void*)SetMousePosition); + ccAddExternalFunctionForPlugin("Mouse::Update^0", (void*)RefreshMouse); + ccAddExternalFunctionForPlugin("Mouse::UseDefaultGraphic^0", (void*)set_default_cursor); + ccAddExternalFunctionForPlugin("Mouse::UseModeGraphic^1", (void*)set_mouse_cursor); + ccAddExternalFunctionForPlugin("Mouse::get_Mode", (void*)GetCursorMode); + ccAddExternalFunctionForPlugin("Mouse::set_Mode", (void*)set_cursor_mode); + ccAddExternalFunctionForPlugin("Mouse::get_Visible", (void*)Mouse_GetVisible); + ccAddExternalFunctionForPlugin("Mouse::set_Visible", (void*)Mouse_SetVisible); +} diff --git a/engines/ags/engine/ac/mouse.h b/engines/ags/engine/ac/mouse.h new file mode 100644 index 00000000000..97e7467f096 --- /dev/null +++ b/engines/ags/engine/ac/mouse.h @@ -0,0 +1,76 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__MOUSE_H +#define __AGS_EE_AC__MOUSE_H + +#include "ac/dynobj/scriptmouse.h" + +#define DOMOUSE_UPDATE 0 +#define DOMOUSE_ENABLE 1 +#define DOMOUSE_DISABLE 2 +#define DOMOUSE_NOCURSOR 5 +// are these mouse buttons? ;/ +// note: also defined in ac_cscidialog as const ints +#define NONE -1 +#define LEFT 0 +#define RIGHT 1 + +void Mouse_SetVisible(int isOn); +int Mouse_GetVisible(); +int Mouse_GetModeGraphic(int curs); +void Mouse_ChangeModeView(int curs, int newview); +// The Mouse:: functions are static so the script doesn't pass +// in an object parameter +void SetMousePosition (int newx, int newy); +int GetCursorMode(); +void SetNextCursor (); +// permanently change cursor graphic +void ChangeCursorGraphic (int curs, int newslot); +void ChangeCursorHotspot (int curs, int x, int y); +int IsButtonDown(int which); +void SetMouseBounds (int x1, int y1, int x2, int y2); +void RefreshMouse(); +// mouse cursor functions: +// set_mouse_cursor: changes visual appearance to specified cursor +void set_mouse_cursor(int newcurs); +// set_default_cursor: resets visual appearance to current mode (walk, look, etc); +void set_default_cursor(); +// set_cursor_mode: changes mode and appearance +void set_cursor_mode(int newmode); +void enable_cursor_mode(int modd); +void disable_cursor_mode(int modd); + +// Try to enable or disable mouse speed control by the engine +void Mouse_EnableControl(bool on); + +//============================================================================= + +int GetMouseCursor(); +void update_script_mouse_coords(); +void update_inv_cursor(int invnum); +void update_cached_mouse_cursor(); +void set_new_cursor_graphic (int spriteslot); +int find_next_enabled_cursor(int startwith); +int find_previous_enabled_cursor(int startwith); + +extern ScriptMouse scmouse; + +extern int cur_mode; +extern int cur_cursor; + +#endif // __AGS_EE_AC__MOUSE_H diff --git a/engines/ags/engine/ac/movelist.cpp b/engines/ags/engine/ac/movelist.cpp new file mode 100644 index 00000000000..47b9ca2b7f5 --- /dev/null +++ b/engines/ags/engine/ac/movelist.cpp @@ -0,0 +1,84 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/movelist.h" +#include "ac/common.h" +#include "util/stream.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +void MoveList::ReadFromFile_Legacy(Stream *in) +{ + in->ReadArrayOfInt32(pos, MAXNEEDSTAGES_LEGACY); + numstage = in->ReadInt32(); + in->ReadArrayOfInt32(xpermove, MAXNEEDSTAGES_LEGACY); + in->ReadArrayOfInt32(ypermove, MAXNEEDSTAGES_LEGACY); + fromx = in->ReadInt32(); + fromy = in->ReadInt32(); + onstage = in->ReadInt32(); + onpart = in->ReadInt32(); + lastx = in->ReadInt32(); + lasty = in->ReadInt32(); + doneflag = in->ReadInt8(); + direct = in->ReadInt8(); +} + +HSaveError MoveList::ReadFromFile(Stream *in, int32_t cmp_ver) +{ + if (cmp_ver < 1) + { + ReadFromFile_Legacy(in); + return HSaveError::None(); + } + + numstage = in->ReadInt32(); + // TODO: reimplement MoveList stages as vector to avoid these limits + if (numstage > MAXNEEDSTAGES) + { + return new SavegameError(kSvgErr_IncompatibleEngine, + String::FromFormat("Incompatible number of movelist steps (count: %d, max : %d).", numstage, MAXNEEDSTAGES)); + } + + fromx = in->ReadInt32(); + fromy = in->ReadInt32(); + onstage = in->ReadInt32(); + onpart = in->ReadInt32(); + lastx = in->ReadInt32(); + lasty = in->ReadInt32(); + doneflag = in->ReadInt8(); + direct = in->ReadInt8(); + + in->ReadArrayOfInt32(pos, numstage); + in->ReadArrayOfInt32(xpermove, numstage); + in->ReadArrayOfInt32(ypermove, numstage); + return HSaveError::None(); +} + +void MoveList::WriteToFile(Stream *out) +{ + out->WriteInt32(numstage); + out->WriteInt32(fromx); + out->WriteInt32(fromy); + out->WriteInt32(onstage); + out->WriteInt32(onpart); + out->WriteInt32(lastx); + out->WriteInt32(lasty); + out->WriteInt8(doneflag); + out->WriteInt8(direct); + + out->WriteArrayOfInt32(pos, numstage); + out->WriteArrayOfInt32(xpermove, numstage); + out->WriteArrayOfInt32(ypermove, numstage); +} diff --git a/engines/ags/engine/ac/movelist.h b/engines/ags/engine/ac/movelist.h new file mode 100644 index 00000000000..2f860dc3a81 --- /dev/null +++ b/engines/ags/engine/ac/movelist.h @@ -0,0 +1,43 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MOVE_H +#define __AC_MOVE_H + +#include "util/wgt2allg.h" // fixed type +#include "game/savegame.h" + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +#define MAXNEEDSTAGES 256 +#define MAXNEEDSTAGES_LEGACY 40 + +struct MoveList { + int pos[MAXNEEDSTAGES]; + int numstage; + fixed xpermove[MAXNEEDSTAGES], ypermove[MAXNEEDSTAGES]; + int fromx, fromy; + int onstage, onpart; + int lastx, lasty; + char doneflag; + char direct; // MoveCharDirect was used or not + + void ReadFromFile_Legacy(Common::Stream *in); + AGS::Engine::HSaveError ReadFromFile(Common::Stream *in, int32_t cmp_ver); + void WriteToFile(Common::Stream *out); +}; + +#endif // __AC_MOVE_H diff --git a/engines/ags/engine/ac/object.cpp b/engines/ags/engine/ac/object.cpp new file mode 100644 index 00000000000..b33eaed3df7 --- /dev/null +++ b/engines/ags/engine/ac/object.cpp @@ -0,0 +1,1070 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/object.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/draw.h" +#include "ac/character.h" +#include "ac/global_object.h" +#include "ac/global_translation.h" +#include "ac/objectcache.h" +#include "ac/properties.h" +#include "ac/room.h" +#include "ac/roomstatus.h" +#include "ac/runtime_defines.h" +#include "ac/string.h" +#include "ac/system.h" +#include "ac/view.h" +#include "ac/walkablearea.h" +#include "debug/debug_log.h" +#include "main/game_run.h" +#include "ac/route_finder.h" +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" +#include "gfx/gfx_def.h" +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_object.h" +#include "ac/movelist.h" + +using namespace AGS::Common; + + +extern ScriptObject scrObj[MAX_ROOM_OBJECTS]; +extern RoomStatus*croom; +extern RoomObject*objs; +extern ViewStruct*views; +extern RoomStruct thisroom; +extern ObjectCache objcache[MAX_ROOM_OBJECTS]; +extern MoveList *mls; +extern GameSetupStruct game; +extern Bitmap *walkable_areas_temp; +extern IGraphicsDriver *gfxDriver; +extern CCObject ccDynamicObject; + + +int Object_IsCollidingWithObject(ScriptObject *objj, ScriptObject *obj2) { + return AreObjectsColliding(objj->id, obj2->id); +} + +ScriptObject *GetObjectAtScreen(int xx, int yy) { + int hsnum = GetObjectIDAtScreen(xx, yy); + if (hsnum < 0) + return nullptr; + return &scrObj[hsnum]; +} + +ScriptObject *GetObjectAtRoom(int x, int y) +{ + int hsnum = GetObjectIDAtRoom(x, y); + if (hsnum < 0) + return nullptr; + return &scrObj[hsnum]; +} + +AGS_INLINE int is_valid_object(int obtest) { + if ((obtest < 0) || (obtest >= croom->numobj)) return 0; + return 1; +} + +void Object_Tint(ScriptObject *objj, int red, int green, int blue, int saturation, int luminance) { + SetObjectTint(objj->id, red, green, blue, saturation, luminance); +} + +void Object_RemoveTint(ScriptObject *objj) { + RemoveObjectTint(objj->id); +} + +void Object_SetView(ScriptObject *objj, int view, int loop, int frame) { + if (game.options[OPT_BASESCRIPTAPI] < kScriptAPI_v351) + { // Previous version of SetView had negative loop and frame mean "use latest values" + auto &obj = objs[objj->id]; + if (loop < 0) loop = obj.loop; + if (frame < 0) frame = obj.frame; + const int vidx = view - 1; + if (vidx < 0 || vidx >= game.numviews) quit("!Object_SetView: invalid view number used"); + loop = Math::Clamp(loop, 0, (int)views[vidx].numLoops - 1); + frame = Math::Clamp(frame, 0, (int)views[vidx].loops[loop].numFrames - 1); + } + SetObjectFrame(objj->id, view, loop, frame); +} + +void Object_SetTransparency(ScriptObject *objj, int trans) { + SetObjectTransparency(objj->id, trans); +} + +int Object_GetTransparency(ScriptObject *objj) { + if (!is_valid_object(objj->id)) + quit("!Object.Transparent: invalid object number specified"); + + return GfxDef::LegacyTrans255ToTrans100(objs[objj->id].transparent); +} + +void Object_SetBaseline(ScriptObject *objj, int basel) { + SetObjectBaseline(objj->id, basel); +} + +int Object_GetBaseline(ScriptObject *objj) { + return GetObjectBaseline(objj->id); +} + +void Object_AnimateFrom(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction, int sframe) { + if (direction == FORWARDS) + direction = 0; + else if (direction == BACKWARDS) + direction = 1; + else + quit("!Object.Animate: Invalid DIRECTION parameter"); + + if ((blocking == BLOCKING) || (blocking == 1)) + blocking = 1; + else if ((blocking == IN_BACKGROUND) || (blocking == 0)) + blocking = 0; + else + quit("!Object.Animate: Invalid BLOCKING parameter"); + + AnimateObjectImpl(objj->id, loop, delay, repeat, direction, blocking, sframe); +} + +void Object_Animate(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction) { + Object_AnimateFrom(objj, loop, delay, repeat, blocking, direction, 0); +} + +void Object_StopAnimating(ScriptObject *objj) { + if (!is_valid_object(objj->id)) + quit("!Object.StopAnimating: invalid object number"); + + if (objs[objj->id].cycling) { + objs[objj->id].cycling = 0; + objs[objj->id].wait = 0; + } +} + +void Object_MergeIntoBackground(ScriptObject *objj) { + MergeObject(objj->id); +} + +void Object_StopMoving(ScriptObject *objj) { + StopObjectMoving(objj->id); +} + +void Object_SetVisible(ScriptObject *objj, int onoroff) { + if (onoroff) + ObjectOn(objj->id); + else + ObjectOff(objj->id); +} + +int Object_GetView(ScriptObject *objj) { + if (objs[objj->id].view < 0) + return 0; + return objs[objj->id].view + 1; +} + +int Object_GetLoop(ScriptObject *objj) { + if (objs[objj->id].view < 0) + return 0; + return objs[objj->id].loop; +} + +int Object_GetFrame(ScriptObject *objj) { + if (objs[objj->id].view < 0) + return 0; + return objs[objj->id].frame; +} + +int Object_GetVisible(ScriptObject *objj) { + return IsObjectOn(objj->id); +} + +void Object_SetGraphic(ScriptObject *objj, int slott) { + SetObjectGraphic(objj->id, slott); +} + +int Object_GetGraphic(ScriptObject *objj) { + return GetObjectGraphic(objj->id); +} + +int GetObjectX (int objj) { + if (!is_valid_object(objj)) quit("!GetObjectX: invalid object number"); + return objs[objj].x; +} + +int Object_GetX(ScriptObject *objj) { + return GetObjectX(objj->id); +} + +int Object_GetY(ScriptObject *objj) { + return GetObjectY(objj->id); +} + +int Object_GetAnimating(ScriptObject *objj) { + return IsObjectAnimating(objj->id); +} + +int Object_GetMoving(ScriptObject *objj) { + return IsObjectMoving(objj->id); +} + +bool Object_HasExplicitLight(ScriptObject *obj) +{ + return objs[obj->id].has_explicit_light(); +} + +bool Object_HasExplicitTint(ScriptObject *obj) +{ + return objs[obj->id].has_explicit_tint(); +} + +int Object_GetLightLevel(ScriptObject *obj) +{ + return objs[obj->id].has_explicit_light() ? objs[obj->id].tint_light : 0; +} + +void Object_SetLightLevel(ScriptObject *objj, int light_level) +{ + int obj = objj->id; + if (!is_valid_object(obj)) + quit("!SetObjectTint: invalid object number specified"); + + objs[obj].tint_light = light_level; + objs[obj].flags &= ~OBJF_HASTINT; + objs[obj].flags |= OBJF_HASLIGHT; +} + +int Object_GetTintRed(ScriptObject *obj) +{ + return objs[obj->id].has_explicit_tint() ? objs[obj->id].tint_r : 0; +} + +int Object_GetTintGreen(ScriptObject *obj) +{ + return objs[obj->id].has_explicit_tint() ? objs[obj->id].tint_g : 0; +} + +int Object_GetTintBlue(ScriptObject *obj) +{ + return objs[obj->id].has_explicit_tint() ? objs[obj->id].tint_b : 0; +} + +int Object_GetTintSaturation(ScriptObject *obj) +{ + return objs[obj->id].has_explicit_tint() ? objs[obj->id].tint_level : 0; +} + +int Object_GetTintLuminance(ScriptObject *obj) +{ + return objs[obj->id].has_explicit_tint() ? ((objs[obj->id].tint_light * 10) / 25) : 0; +} + +void Object_SetPosition(ScriptObject *objj, int xx, int yy) { + SetObjectPosition(objj->id, xx, yy); +} + +void Object_SetX(ScriptObject *objj, int xx) { + SetObjectPosition(objj->id, xx, objs[objj->id].y); +} + +void Object_SetY(ScriptObject *objj, int yy) { + SetObjectPosition(objj->id, objs[objj->id].x, yy); +} + +void Object_GetName(ScriptObject *objj, char *buffer) { + GetObjectName(objj->id, buffer); +} + +const char* Object_GetName_New(ScriptObject *objj) { + if (!is_valid_object(objj->id)) + quit("!Object.Name: invalid object number"); + + return CreateNewScriptString(get_translation(thisroom.Objects[objj->id].Name)); +} + +bool Object_IsInteractionAvailable(ScriptObject *oobj, int mood) { + + play.check_interaction_only = 1; + RunObjectInteraction(oobj->id, mood); + int ciwas = play.check_interaction_only; + play.check_interaction_only = 0; + return (ciwas == 2); +} + +void Object_Move(ScriptObject *objj, int x, int y, int speed, int blocking, int direct) { + if ((direct == ANYWHERE) || (direct == 1)) + direct = 1; + else if ((direct == WALKABLE_AREAS) || (direct == 0)) + direct = 0; + else + quit("Object.Move: invalid DIRECT parameter"); + + move_object(objj->id, x, y, speed, direct); + + if ((blocking == BLOCKING) || (blocking == 1)) + GameLoopUntilValueIsZero(&objs[objj->id].moving); + else if ((blocking != IN_BACKGROUND) && (blocking != 0)) + quit("Object.Move: invalid BLOCKING paramter"); +} + +void Object_SetClickable(ScriptObject *objj, int clik) { + SetObjectClickable(objj->id, clik); +} + +int Object_GetClickable(ScriptObject *objj) { + if (!is_valid_object(objj->id)) + quit("!Object.Clickable: Invalid object specified"); + + if (objs[objj->id].flags & OBJF_NOINTERACT) + return 0; + return 1; +} + +void Object_SetManualScaling(ScriptObject *objj, bool on) +{ + if (on) objs[objj->id].flags &= ~OBJF_USEROOMSCALING; + else objs[objj->id].flags |= OBJF_USEROOMSCALING; + // clear the cache + objcache[objj->id].ywas = -9999; +} + +void Object_SetIgnoreScaling(ScriptObject *objj, int newval) { + if (!is_valid_object(objj->id)) + quit("!Object.IgnoreScaling: Invalid object specified"); + if (newval) + objs[objj->id].zoom = 100; // compatibility, for before manual scaling existed + Object_SetManualScaling(objj, newval != 0); +} + +int Object_GetIgnoreScaling(ScriptObject *objj) { + if (!is_valid_object(objj->id)) + quit("!Object.IgnoreScaling: Invalid object specified"); + + if (objs[objj->id].flags & OBJF_USEROOMSCALING) + return 0; + return 1; +} + +int Object_GetScaling(ScriptObject *objj) { + return objs[objj->id].zoom; +} + +void Object_SetScaling(ScriptObject *objj, int zoomlevel) { + if ((objs[objj->id].flags & OBJF_USEROOMSCALING) != 0) + { + debug_script_warn("Object.Scaling: cannot set property unless ManualScaling is enabled"); + return; + } + int zoom_fixed = Math::Clamp(zoomlevel, 1, (int)(INT16_MAX)); // RoomObject.zoom is int16 + if (zoomlevel != zoom_fixed) + debug_script_warn("Object.Scaling: scaling level must be between 1 and %d%%", (int)(INT16_MAX)); + objs[objj->id].zoom = zoom_fixed; +} + +void Object_SetSolid(ScriptObject *objj, int solid) { + objs[objj->id].flags &= ~OBJF_SOLID; + if (solid) + objs[objj->id].flags |= OBJF_SOLID; +} + +int Object_GetSolid(ScriptObject *objj) { + if (objs[objj->id].flags & OBJF_SOLID) + return 1; + return 0; +} + +void Object_SetBlockingWidth(ScriptObject *objj, int bwid) { + objs[objj->id].blocking_width = bwid; +} + +int Object_GetBlockingWidth(ScriptObject *objj) { + return objs[objj->id].blocking_width; +} + +void Object_SetBlockingHeight(ScriptObject *objj, int bhit) { + objs[objj->id].blocking_height = bhit; +} + +int Object_GetBlockingHeight(ScriptObject *objj) { + return objs[objj->id].blocking_height; +} + +int Object_GetID(ScriptObject *objj) { + return objj->id; +} + +void Object_SetIgnoreWalkbehinds(ScriptObject *chaa, int clik) { + SetObjectIgnoreWalkbehinds(chaa->id, clik); +} + +int Object_GetIgnoreWalkbehinds(ScriptObject *chaa) { + if (!is_valid_object(chaa->id)) + quit("!Object.IgnoreWalkbehinds: Invalid object specified"); + + if (objs[chaa->id].flags & OBJF_NOWALKBEHINDS) + return 1; + return 0; +} + +void move_object(int objj,int tox,int toy,int spee,int ignwal) { + + if (!is_valid_object(objj)) + quit("!MoveObject: invalid object number"); + + // AGS <= 2.61 uses MoveObject with spp=-1 internally instead of SetObjectPosition + if ((loaded_game_file_version <= kGameVersion_261) && (spee == -1)) + { + objs[objj].x = tox; + objs[objj].y = toy; + return; + } + + debug_script_log("Object %d start move to %d,%d", objj, tox, toy); + + int objX = room_to_mask_coord(objs[objj].x); + int objY = room_to_mask_coord(objs[objj].y); + tox = room_to_mask_coord(tox); + toy = room_to_mask_coord(toy); + + set_route_move_speed(spee, spee); + set_color_depth(8); + int mslot=find_route(objX, objY, tox, toy, prepare_walkable_areas(-1), objj+1, 1, ignwal); + set_color_depth(game.GetColorDepth()); + if (mslot>0) { + objs[objj].moving = mslot; + mls[mslot].direct = ignwal; + convert_move_path_to_room_resolution(&mls[mslot]); + } +} + +void Object_RunInteraction(ScriptObject *objj, int mode) { + RunObjectInteraction(objj->id, mode); +} + +int Object_GetProperty (ScriptObject *objj, const char *property) { + return GetObjectProperty(objj->id, property); +} + +void Object_GetPropertyText(ScriptObject *objj, const char *property, char *bufer) { + GetObjectPropertyText(objj->id, property, bufer); +} + +const char* Object_GetTextProperty(ScriptObject *objj, const char *property) +{ + return get_text_property_dynamic_string(thisroom.Objects[objj->id].Properties, croom->objProps[objj->id], property); +} + +bool Object_SetProperty(ScriptObject *objj, const char *property, int value) +{ + return set_int_property(croom->objProps[objj->id], property, value); +} + +bool Object_SetTextProperty(ScriptObject *objj, const char *property, const char *value) +{ + return set_text_property(croom->objProps[objj->id], property, value); +} + +void get_object_blocking_rect(int objid, int *x1, int *y1, int *width, int *y2) { + RoomObject *tehobj = &objs[objid]; + int cwidth, fromx; + + if (tehobj->blocking_width < 1) + cwidth = game_to_data_coord(tehobj->last_width) - 4; + else + cwidth = tehobj->blocking_width; + + fromx = tehobj->x + (game_to_data_coord(tehobj->last_width) / 2) - cwidth / 2; + if (fromx < 0) { + cwidth += fromx; + fromx = 0; + } + if (fromx + cwidth >= mask_to_room_coord(walkable_areas_temp->GetWidth())) + cwidth = mask_to_room_coord(walkable_areas_temp->GetWidth()) - fromx; + + if (x1) + *x1 = fromx; + if (width) + *width = cwidth; + if (y1) { + if (tehobj->blocking_height > 0) + *y1 = tehobj->y - tehobj->blocking_height / 2; + else + *y1 = tehobj->y - 2; + } + if (y2) { + if (tehobj->blocking_height > 0) + *y2 = tehobj->y + tehobj->blocking_height / 2; + else + *y2 = tehobj->y + 3; + } +} + +int isposinbox(int mmx,int mmy,int lf,int tp,int rt,int bt) { + if ((mmx>=lf) & (mmx<=rt) & (mmy>=tp) & (mmy<=bt)) return TRUE; + else return FALSE; +} + +// xx,yy is the position in room co-ordinates that we are checking +// arx,ary is the sprite x/y co-ordinates +int is_pos_in_sprite(int xx,int yy,int arx,int ary, Bitmap *sprit, int spww,int sphh, int flipped) { + if (spww==0) spww = game_to_data_coord(sprit->GetWidth()) - 1; + if (sphh==0) sphh = game_to_data_coord(sprit->GetHeight()) - 1; + + if (isposinbox(xx,yy,arx,ary,arx+spww,ary+sphh)==FALSE) + return FALSE; + + if (game.options[OPT_PIXPERFECT]) + { + // if it's transparent, or off the edge of the sprite, ignore + int xpos = data_to_game_coord(xx - arx); + int ypos = data_to_game_coord(yy - ary); + + if (gfxDriver->HasAcceleratedTransform()) + { + // hardware acceleration, so the sprite in memory will not have + // been stretched, it will be original size. Thus, adjust our + // calculations to compensate + data_to_game_coords(&spww, &sphh); + + if (spww != sprit->GetWidth()) + xpos = (xpos * sprit->GetWidth()) / spww; + if (sphh != sprit->GetHeight()) + ypos = (ypos * sprit->GetHeight()) / sphh; + } + + if (flipped) + xpos = (sprit->GetWidth() - 1) - xpos; + + int gpcol = my_getpixel(sprit, xpos, ypos); + + if ((gpcol == sprit->GetMaskColor()) || (gpcol == -1)) + return FALSE; + } + return TRUE; +} + +// X and Y co-ordinates must be in native format (TODO: find out if this comment is still true) +int check_click_on_object(int roomx, int roomy, int mood) +{ + int aa = GetObjectIDAtRoom(roomx, roomy); + if (aa < 0) return 0; + RunObjectInteraction(aa, mood); + return 1; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// void (ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction) +RuntimeScriptValue Sc_Object_Animate(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5(ScriptObject, Object_Animate); +} + +RuntimeScriptValue Sc_Object_AnimateFrom(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT6(ScriptObject, Object_AnimateFrom); +} + +// int (ScriptObject *objj, ScriptObject *obj2) +RuntimeScriptValue Sc_Object_IsCollidingWithObject(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(ScriptObject, Object_IsCollidingWithObject, ScriptObject); +} + +// void (ScriptObject *objj, char *buffer) +RuntimeScriptValue Sc_Object_GetName(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(ScriptObject, Object_GetName, char); +} + +// int (ScriptObject *objj, const char *property) +RuntimeScriptValue Sc_Object_GetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(ScriptObject, Object_GetProperty, const char); +} + +// void (ScriptObject *objj, const char *property, char *bufer) +RuntimeScriptValue Sc_Object_GetPropertyText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ2(ScriptObject, Object_GetPropertyText, const char, char); +} + +//const char* (ScriptObject *objj, const char *property) +RuntimeScriptValue Sc_Object_GetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_POBJ(ScriptObject, const char, myScriptStringImpl, Object_GetTextProperty, const char); +} + +RuntimeScriptValue Sc_Object_SetProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ_PINT(ScriptObject, Object_SetProperty, const char); +} + +RuntimeScriptValue Sc_Object_SetTextProperty(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ2(ScriptObject, Object_SetTextProperty, const char, const char); +} + +// void (ScriptObject *objj) +RuntimeScriptValue Sc_Object_MergeIntoBackground(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptObject, Object_MergeIntoBackground); +} + +RuntimeScriptValue Sc_Object_IsInteractionAvailable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_PINT(ScriptObject, Object_IsInteractionAvailable); +} + +// void (ScriptObject *objj, int x, int y, int speed, int blocking, int direct) +RuntimeScriptValue Sc_Object_Move(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5(ScriptObject, Object_Move); +} + +// void (ScriptObject *objj) +RuntimeScriptValue Sc_Object_RemoveTint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptObject, Object_RemoveTint); +} + +// void (ScriptObject *objj, int mode) +RuntimeScriptValue Sc_Object_RunInteraction(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_RunInteraction); +} + +RuntimeScriptValue Sc_Object_HasExplicitLight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(ScriptObject, Object_HasExplicitLight); +} + +RuntimeScriptValue Sc_Object_HasExplicitTint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(ScriptObject, Object_HasExplicitTint); +} + +RuntimeScriptValue Sc_Object_GetLightLevel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetLightLevel); +} + +RuntimeScriptValue Sc_Object_SetLightLevel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetLightLevel); +} + +RuntimeScriptValue Sc_Object_GetTintBlue(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetTintBlue); +} + +RuntimeScriptValue Sc_Object_GetTintGreen(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetTintGreen); +} + +RuntimeScriptValue Sc_Object_GetTintRed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetTintRed); +} + +RuntimeScriptValue Sc_Object_GetTintSaturation(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetTintSaturation); +} + +RuntimeScriptValue Sc_Object_GetTintLuminance(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetTintLuminance); +} + +// void (ScriptObject *objj, int xx, int yy) +RuntimeScriptValue Sc_Object_SetPosition(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(ScriptObject, Object_SetPosition); +} + +// void (ScriptObject *objj, int view, int loop, int frame) +RuntimeScriptValue Sc_Object_SetView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT3(ScriptObject, Object_SetView); +} + +// void (ScriptObject *objj) +RuntimeScriptValue Sc_Object_StopAnimating(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptObject, Object_StopAnimating); +} + +// void (ScriptObject *objj) +RuntimeScriptValue Sc_Object_StopMoving(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptObject, Object_StopMoving); +} + +// void (ScriptObject *objj, int red, int green, int blue, int saturation, int luminance) +RuntimeScriptValue Sc_Object_Tint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5(ScriptObject, Object_Tint); +} + +RuntimeScriptValue Sc_GetObjectAtRoom(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(ScriptObject, ccDynamicObject, GetObjectAtRoom); +} + +// ScriptObject *(int xx, int yy) +RuntimeScriptValue Sc_GetObjectAtScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(ScriptObject, ccDynamicObject, GetObjectAtScreen); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetAnimating(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetAnimating); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetBaseline(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetBaseline); +} + +// void (ScriptObject *objj, int basel) +RuntimeScriptValue Sc_Object_SetBaseline(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetBaseline); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetBlockingHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetBlockingHeight); +} + +// void (ScriptObject *objj, int bhit) +RuntimeScriptValue Sc_Object_SetBlockingHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetBlockingHeight); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetBlockingWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetBlockingWidth); +} + +// void (ScriptObject *objj, int bwid) +RuntimeScriptValue Sc_Object_SetBlockingWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetBlockingWidth); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetClickable); +} + +// void (ScriptObject *objj, int clik) +RuntimeScriptValue Sc_Object_SetClickable(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetClickable); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetFrame); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetGraphic); +} + +// void (ScriptObject *objj, int slott) +RuntimeScriptValue Sc_Object_SetGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetGraphic); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetID); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetIgnoreScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetIgnoreScaling); +} + +// void (ScriptObject *objj, int newval) +RuntimeScriptValue Sc_Object_SetIgnoreScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetIgnoreScaling); +} + +// int (ScriptObject *chaa) +RuntimeScriptValue Sc_Object_GetIgnoreWalkbehinds(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetIgnoreWalkbehinds); +} + +// void (ScriptObject *chaa, int clik) +RuntimeScriptValue Sc_Object_SetIgnoreWalkbehinds(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetIgnoreWalkbehinds); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetLoop); +} + +RuntimeScriptValue Sc_Object_SetManualScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PBOOL(ScriptObject, Object_SetManualScaling); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetMoving(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetMoving); +} + +// const char* (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetName_New(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptObject, const char, myScriptStringImpl, Object_GetName_New); +} + +RuntimeScriptValue Sc_Object_GetScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetScaling); +} + +RuntimeScriptValue Sc_Object_SetScaling(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetScaling); +} + + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetSolid(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetSolid); +} + +// void (ScriptObject *objj, int solid) +RuntimeScriptValue Sc_Object_SetSolid(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetSolid); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetTransparency); +} + +// void (ScriptObject *objj, int trans) +RuntimeScriptValue Sc_Object_SetTransparency(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetTransparency); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetView); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetVisible(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetVisible); +} + +// void (ScriptObject *objj, int onoroff) +RuntimeScriptValue Sc_Object_SetVisible(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetVisible); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetX); +} + +// void (ScriptObject *objj, int xx) +RuntimeScriptValue Sc_Object_SetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetX); +} + +// int (ScriptObject *objj) +RuntimeScriptValue Sc_Object_GetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptObject, Object_GetY); +} + +// void (ScriptObject *objj, int yy) +RuntimeScriptValue Sc_Object_SetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptObject, Object_SetY); +} + + + +void RegisterObjectAPI() +{ + ccAddExternalObjectFunction("Object::Animate^5", Sc_Object_Animate); + ccAddExternalObjectFunction("Object::Animate^6", Sc_Object_AnimateFrom); + ccAddExternalObjectFunction("Object::IsCollidingWithObject^1", Sc_Object_IsCollidingWithObject); + ccAddExternalObjectFunction("Object::GetName^1", Sc_Object_GetName); + ccAddExternalObjectFunction("Object::GetProperty^1", Sc_Object_GetProperty); + ccAddExternalObjectFunction("Object::GetPropertyText^2", Sc_Object_GetPropertyText); + ccAddExternalObjectFunction("Object::GetTextProperty^1", Sc_Object_GetTextProperty); + ccAddExternalObjectFunction("Object::SetProperty^2", Sc_Object_SetProperty); + ccAddExternalObjectFunction("Object::SetTextProperty^2", Sc_Object_SetTextProperty); + ccAddExternalObjectFunction("Object::IsInteractionAvailable^1", Sc_Object_IsInteractionAvailable); + ccAddExternalObjectFunction("Object::MergeIntoBackground^0", Sc_Object_MergeIntoBackground); + ccAddExternalObjectFunction("Object::Move^5", Sc_Object_Move); + ccAddExternalObjectFunction("Object::RemoveTint^0", Sc_Object_RemoveTint); + ccAddExternalObjectFunction("Object::RunInteraction^1", Sc_Object_RunInteraction); + ccAddExternalObjectFunction("Object::SetLightLevel^1", Sc_Object_SetLightLevel); + ccAddExternalObjectFunction("Object::SetPosition^2", Sc_Object_SetPosition); + ccAddExternalObjectFunction("Object::SetView^3", Sc_Object_SetView); + ccAddExternalObjectFunction("Object::StopAnimating^0", Sc_Object_StopAnimating); + ccAddExternalObjectFunction("Object::StopMoving^0", Sc_Object_StopMoving); + ccAddExternalObjectFunction("Object::Tint^5", Sc_Object_Tint); + + // static + ccAddExternalStaticFunction("Object::GetAtRoomXY^2", Sc_GetObjectAtRoom); + ccAddExternalStaticFunction("Object::GetAtScreenXY^2", Sc_GetObjectAtScreen); + + ccAddExternalObjectFunction("Object::get_Animating", Sc_Object_GetAnimating); + ccAddExternalObjectFunction("Object::get_Baseline", Sc_Object_GetBaseline); + ccAddExternalObjectFunction("Object::set_Baseline", Sc_Object_SetBaseline); + ccAddExternalObjectFunction("Object::get_BlockingHeight", Sc_Object_GetBlockingHeight); + ccAddExternalObjectFunction("Object::set_BlockingHeight", Sc_Object_SetBlockingHeight); + ccAddExternalObjectFunction("Object::get_BlockingWidth", Sc_Object_GetBlockingWidth); + ccAddExternalObjectFunction("Object::set_BlockingWidth", Sc_Object_SetBlockingWidth); + ccAddExternalObjectFunction("Object::get_Clickable", Sc_Object_GetClickable); + ccAddExternalObjectFunction("Object::set_Clickable", Sc_Object_SetClickable); + ccAddExternalObjectFunction("Object::get_Frame", Sc_Object_GetFrame); + ccAddExternalObjectFunction("Object::get_Graphic", Sc_Object_GetGraphic); + ccAddExternalObjectFunction("Object::set_Graphic", Sc_Object_SetGraphic); + ccAddExternalObjectFunction("Object::get_ID", Sc_Object_GetID); + ccAddExternalObjectFunction("Object::get_IgnoreScaling", Sc_Object_GetIgnoreScaling); + ccAddExternalObjectFunction("Object::set_IgnoreScaling", Sc_Object_SetIgnoreScaling); + ccAddExternalObjectFunction("Object::get_IgnoreWalkbehinds", Sc_Object_GetIgnoreWalkbehinds); + ccAddExternalObjectFunction("Object::set_IgnoreWalkbehinds", Sc_Object_SetIgnoreWalkbehinds); + ccAddExternalObjectFunction("Object::get_Loop", Sc_Object_GetLoop); + ccAddExternalObjectFunction("Object::get_ManualScaling", Sc_Object_GetIgnoreScaling); + ccAddExternalObjectFunction("Object::set_ManualScaling", Sc_Object_SetManualScaling); + ccAddExternalObjectFunction("Object::get_Moving", Sc_Object_GetMoving); + ccAddExternalObjectFunction("Object::get_Name", Sc_Object_GetName_New); + ccAddExternalObjectFunction("Object::get_Scaling", Sc_Object_GetScaling); + ccAddExternalObjectFunction("Object::set_Scaling", Sc_Object_SetScaling); + ccAddExternalObjectFunction("Object::get_Solid", Sc_Object_GetSolid); + ccAddExternalObjectFunction("Object::set_Solid", Sc_Object_SetSolid); + ccAddExternalObjectFunction("Object::get_Transparency", Sc_Object_GetTransparency); + ccAddExternalObjectFunction("Object::set_Transparency", Sc_Object_SetTransparency); + ccAddExternalObjectFunction("Object::get_View", Sc_Object_GetView); + ccAddExternalObjectFunction("Object::get_Visible", Sc_Object_GetVisible); + ccAddExternalObjectFunction("Object::set_Visible", Sc_Object_SetVisible); + ccAddExternalObjectFunction("Object::get_X", Sc_Object_GetX); + ccAddExternalObjectFunction("Object::set_X", Sc_Object_SetX); + ccAddExternalObjectFunction("Object::get_Y", Sc_Object_GetY); + ccAddExternalObjectFunction("Object::set_Y", Sc_Object_SetY); + + ccAddExternalObjectFunction("Object::get_HasExplicitLight", Sc_Object_HasExplicitLight); + ccAddExternalObjectFunction("Object::get_HasExplicitTint", Sc_Object_HasExplicitTint); + ccAddExternalObjectFunction("Object::get_LightLevel", Sc_Object_GetLightLevel); + ccAddExternalObjectFunction("Object::set_LightLevel", Sc_Object_SetLightLevel); + ccAddExternalObjectFunction("Object::get_TintBlue", Sc_Object_GetTintBlue); + ccAddExternalObjectFunction("Object::get_TintGreen", Sc_Object_GetTintGreen); + ccAddExternalObjectFunction("Object::get_TintRed", Sc_Object_GetTintRed); + ccAddExternalObjectFunction("Object::get_TintSaturation", Sc_Object_GetTintSaturation); + ccAddExternalObjectFunction("Object::get_TintLuminance", Sc_Object_GetTintLuminance); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Object::Animate^5", (void*)Object_Animate); + ccAddExternalFunctionForPlugin("Object::IsCollidingWithObject^1", (void*)Object_IsCollidingWithObject); + ccAddExternalFunctionForPlugin("Object::GetName^1", (void*)Object_GetName); + ccAddExternalFunctionForPlugin("Object::GetProperty^1", (void*)Object_GetProperty); + ccAddExternalFunctionForPlugin("Object::GetPropertyText^2", (void*)Object_GetPropertyText); + ccAddExternalFunctionForPlugin("Object::GetTextProperty^1", (void*)Object_GetTextProperty); + ccAddExternalFunctionForPlugin("Object::MergeIntoBackground^0", (void*)Object_MergeIntoBackground); + ccAddExternalFunctionForPlugin("Object::Move^5", (void*)Object_Move); + ccAddExternalFunctionForPlugin("Object::RemoveTint^0", (void*)Object_RemoveTint); + ccAddExternalFunctionForPlugin("Object::RunInteraction^1", (void*)Object_RunInteraction); + ccAddExternalFunctionForPlugin("Object::SetPosition^2", (void*)Object_SetPosition); + ccAddExternalFunctionForPlugin("Object::SetView^3", (void*)Object_SetView); + ccAddExternalFunctionForPlugin("Object::StopAnimating^0", (void*)Object_StopAnimating); + ccAddExternalFunctionForPlugin("Object::StopMoving^0", (void*)Object_StopMoving); + ccAddExternalFunctionForPlugin("Object::Tint^5", (void*)Object_Tint); + ccAddExternalFunctionForPlugin("Object::GetAtRoomXY^2", (void*)GetObjectAtRoom); + ccAddExternalFunctionForPlugin("Object::GetAtScreenXY^2", (void*)GetObjectAtScreen); + ccAddExternalFunctionForPlugin("Object::get_Animating", (void*)Object_GetAnimating); + ccAddExternalFunctionForPlugin("Object::get_Baseline", (void*)Object_GetBaseline); + ccAddExternalFunctionForPlugin("Object::set_Baseline", (void*)Object_SetBaseline); + ccAddExternalFunctionForPlugin("Object::get_BlockingHeight", (void*)Object_GetBlockingHeight); + ccAddExternalFunctionForPlugin("Object::set_BlockingHeight", (void*)Object_SetBlockingHeight); + ccAddExternalFunctionForPlugin("Object::get_BlockingWidth", (void*)Object_GetBlockingWidth); + ccAddExternalFunctionForPlugin("Object::set_BlockingWidth", (void*)Object_SetBlockingWidth); + ccAddExternalFunctionForPlugin("Object::get_Clickable", (void*)Object_GetClickable); + ccAddExternalFunctionForPlugin("Object::set_Clickable", (void*)Object_SetClickable); + ccAddExternalFunctionForPlugin("Object::get_Frame", (void*)Object_GetFrame); + ccAddExternalFunctionForPlugin("Object::get_Graphic", (void*)Object_GetGraphic); + ccAddExternalFunctionForPlugin("Object::set_Graphic", (void*)Object_SetGraphic); + ccAddExternalFunctionForPlugin("Object::get_ID", (void*)Object_GetID); + ccAddExternalFunctionForPlugin("Object::get_IgnoreScaling", (void*)Object_GetIgnoreScaling); + ccAddExternalFunctionForPlugin("Object::set_IgnoreScaling", (void*)Object_SetIgnoreScaling); + ccAddExternalFunctionForPlugin("Object::get_IgnoreWalkbehinds", (void*)Object_GetIgnoreWalkbehinds); + ccAddExternalFunctionForPlugin("Object::set_IgnoreWalkbehinds", (void*)Object_SetIgnoreWalkbehinds); + ccAddExternalFunctionForPlugin("Object::get_Loop", (void*)Object_GetLoop); + ccAddExternalFunctionForPlugin("Object::get_Moving", (void*)Object_GetMoving); + ccAddExternalFunctionForPlugin("Object::get_Name", (void*)Object_GetName_New); + ccAddExternalFunctionForPlugin("Object::get_Solid", (void*)Object_GetSolid); + ccAddExternalFunctionForPlugin("Object::set_Solid", (void*)Object_SetSolid); + ccAddExternalFunctionForPlugin("Object::get_Transparency", (void*)Object_GetTransparency); + ccAddExternalFunctionForPlugin("Object::set_Transparency", (void*)Object_SetTransparency); + ccAddExternalFunctionForPlugin("Object::get_View", (void*)Object_GetView); + ccAddExternalFunctionForPlugin("Object::get_Visible", (void*)Object_GetVisible); + ccAddExternalFunctionForPlugin("Object::set_Visible", (void*)Object_SetVisible); + ccAddExternalFunctionForPlugin("Object::get_X", (void*)Object_GetX); + ccAddExternalFunctionForPlugin("Object::set_X", (void*)Object_SetX); + ccAddExternalFunctionForPlugin("Object::get_Y", (void*)Object_GetY); + ccAddExternalFunctionForPlugin("Object::set_Y", (void*)Object_SetY); +} diff --git a/engines/ags/engine/ac/object.h b/engines/ags/engine/ac/object.h new file mode 100644 index 00000000000..0e044fab95e --- /dev/null +++ b/engines/ags/engine/ac/object.h @@ -0,0 +1,89 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// [IKM] 2012-06-25: This bugs me that type is called 'Object'; in modern +// world of programming 'object' is usually a base class; should not we +// rename this to RoomObject one day? +//============================================================================= +#ifndef __AGS_EE_AC__OBJECT_H +#define __AGS_EE_AC__OBJECT_H + +#include "ac/common_defines.h" +#include "ac/dynobj/scriptobject.h" + +namespace AGS { namespace Common { class Bitmap; } } +using namespace AGS; // FIXME later + +extern AGS_INLINE int is_valid_object(int obtest); +int Object_IsCollidingWithObject(ScriptObject *objj, ScriptObject *obj2); +ScriptObject *GetObjectAtScreen(int xx, int yy); +void Object_Tint(ScriptObject *objj, int red, int green, int blue, int saturation, int luminance); +void Object_RemoveTint(ScriptObject *objj); +void Object_SetView(ScriptObject *objj, int view, int loop, int frame); +void Object_SetTransparency(ScriptObject *objj, int trans); +int Object_GetTransparency(ScriptObject *objj); +void Object_SetBaseline(ScriptObject *objj, int basel); +int Object_GetBaseline(ScriptObject *objj); +void Object_Animate(ScriptObject *objj, int loop, int delay, int repeat, int blocking, int direction); +void Object_StopAnimating(ScriptObject *objj); +void Object_MergeIntoBackground(ScriptObject *objj); +void Object_StopMoving(ScriptObject *objj); +void Object_SetVisible(ScriptObject *objj, int onoroff); +int Object_GetView(ScriptObject *objj); +int Object_GetLoop(ScriptObject *objj); +int Object_GetFrame(ScriptObject *objj); +int Object_GetVisible(ScriptObject *objj); +void Object_SetGraphic(ScriptObject *objj, int slott); +int Object_GetGraphic(ScriptObject *objj); +int Object_GetX(ScriptObject *objj); +int Object_GetY(ScriptObject *objj); +int Object_GetAnimating(ScriptObject *objj); +int Object_GetMoving(ScriptObject *objj); +void Object_SetPosition(ScriptObject *objj, int xx, int yy); +void Object_SetX(ScriptObject *objj, int xx); +void Object_SetY(ScriptObject *objj, int yy); +void Object_GetName(ScriptObject *objj, char *buffer); +const char* Object_GetName_New(ScriptObject *objj); +bool Object_IsInteractionAvailable(ScriptObject *oobj, int mood); +void Object_Move(ScriptObject *objj, int x, int y, int speed, int blocking, int direct); +void Object_SetClickable(ScriptObject *objj, int clik); +int Object_GetClickable(ScriptObject *objj); +void Object_SetIgnoreScaling(ScriptObject *objj, int newval); +int Object_GetIgnoreScaling(ScriptObject *objj); +void Object_SetSolid(ScriptObject *objj, int solid); +int Object_GetSolid(ScriptObject *objj); +void Object_SetBlockingWidth(ScriptObject *objj, int bwid); +int Object_GetBlockingWidth(ScriptObject *objj); +void Object_SetBlockingHeight(ScriptObject *objj, int bhit); +int Object_GetBlockingHeight(ScriptObject *objj); +int Object_GetID(ScriptObject *objj); +void Object_SetIgnoreWalkbehinds(ScriptObject *chaa, int clik); +int Object_GetIgnoreWalkbehinds(ScriptObject *chaa); +void Object_RunInteraction(ScriptObject *objj, int mode); + +int Object_GetProperty (ScriptObject *objj, const char *property); +void Object_GetPropertyText(ScriptObject *objj, const char *property, char *bufer); +const char* Object_GetTextProperty(ScriptObject *objj, const char *property); + +void move_object(int objj,int tox,int toy,int spee,int ignwal); +void get_object_blocking_rect(int objid, int *x1, int *y1, int *width, int *y2); +int isposinbox(int mmx,int mmy,int lf,int tp,int rt,int bt); +int is_pos_in_sprite(int xx,int yy,int arx,int ary, Common::Bitmap *sprit, int spww,int sphh, int flipped = 0); +// X and Y co-ordinates must be in native format +// X and Y are ROOM coordinates +int check_click_on_object(int roomx, int roomy, int mood); + +#endif // __AGS_EE_AC__OBJECT_H + diff --git a/engines/ags/engine/ac/objectcache.h b/engines/ags/engine/ac/objectcache.h new file mode 100644 index 00000000000..78ddcede186 --- /dev/null +++ b/engines/ags/engine/ac/objectcache.h @@ -0,0 +1,31 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__OBJECTCACHE_H +#define __AGS_EE_AC__OBJECTCACHE_H + +// stores cached object info +struct ObjectCache { + Common::Bitmap *image; + int sppic; + short tintredwas, tintgrnwas, tintbluwas, tintamntwas, tintlightwas; + short lightlevwas, mirroredWas, zoomWas; + // The following are used to determine if the character has moved + int xwas, ywas; +}; + +#endif // __AGS_EE_AC__OBJECTCACHE_H diff --git a/engines/ags/engine/ac/overlay.cpp b/engines/ags/engine/ac/overlay.cpp new file mode 100644 index 00000000000..3904b795b1a --- /dev/null +++ b/engines/ags/engine/ac/overlay.cpp @@ -0,0 +1,396 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/overlay.h" +#include "ac/common.h" +#include "ac/view.h" +#include "ac/character.h" +#include "ac/characterextras.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_overlay.h" +#include "ac/global_translation.h" +#include "ac/runtime_defines.h" +#include "ac/screenoverlay.h" +#include "ac/string.h" +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" +#include "script/runtimescriptvalue.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern int displayed_room; +extern int face_talking; +extern ViewStruct*views; +extern CharacterExtras *charextra; +extern IGraphicsDriver *gfxDriver; + + + +std::vector screenover; +int is_complete_overlay=0,is_text_overlay=0; + +void Overlay_Remove(ScriptOverlay *sco) { + sco->Remove(); +} + +void Overlay_SetText(ScriptOverlay *scover, int wii, int fontid, int text_color, const char *text) { + int ovri=find_overlay_of_type(scover->overlayId); + if (ovri<0) + quit("!Overlay.SetText: invalid overlay ID specified"); + int xx = game_to_data_coord(screenover[ovri].x) - scover->borderWidth; + int yy = game_to_data_coord(screenover[ovri].y) - scover->borderHeight; + + RemoveOverlay(scover->overlayId); + const int disp_type = scover->overlayId; + + if (CreateTextOverlay(xx, yy, wii, fontid, text_color, get_translation(text), disp_type) != scover->overlayId) + quit("SetTextOverlay internal error: inconsistent type ids"); +} + +int Overlay_GetX(ScriptOverlay *scover) { + int ovri = find_overlay_of_type(scover->overlayId); + if (ovri < 0) + quit("!invalid overlay ID specified"); + + int tdxp, tdyp; + get_overlay_position(screenover[ovri], &tdxp, &tdyp); + + return game_to_data_coord(tdxp); +} + +void Overlay_SetX(ScriptOverlay *scover, int newx) { + int ovri = find_overlay_of_type(scover->overlayId); + if (ovri < 0) + quit("!invalid overlay ID specified"); + + screenover[ovri].x = data_to_game_coord(newx); +} + +int Overlay_GetY(ScriptOverlay *scover) { + int ovri = find_overlay_of_type(scover->overlayId); + if (ovri < 0) + quit("!invalid overlay ID specified"); + + int tdxp, tdyp; + get_overlay_position(screenover[ovri], &tdxp, &tdyp); + + return game_to_data_coord(tdyp); +} + +void Overlay_SetY(ScriptOverlay *scover, int newy) { + int ovri = find_overlay_of_type(scover->overlayId); + if (ovri < 0) + quit("!invalid overlay ID specified"); + + screenover[ovri].y = data_to_game_coord(newy); +} + +int Overlay_GetValid(ScriptOverlay *scover) { + if (scover->overlayId == -1) + return 0; + + if (!IsOverlayValid(scover->overlayId)) { + scover->overlayId = -1; + return 0; + } + + return 1; +} + +ScriptOverlay* Overlay_CreateGraphical(int x, int y, int slot, int transparent) { + ScriptOverlay *sco = new ScriptOverlay(); + sco->overlayId = CreateGraphicOverlay(x, y, slot, transparent); + sco->borderHeight = 0; + sco->borderWidth = 0; + sco->isBackgroundSpeech = 0; + + ccRegisterManagedObject(sco, sco); + return sco; +} + +ScriptOverlay* Overlay_CreateTextual(int x, int y, int width, int font, int colour, const char* text) { + ScriptOverlay *sco = new ScriptOverlay(); + + data_to_game_coords(&x, &y); + width = data_to_game_coord(width); + + sco->overlayId = CreateTextOverlayCore(x, y, width, font, colour, text, DISPLAYTEXT_NORMALOVERLAY, 0); + + int ovri = find_overlay_of_type(sco->overlayId); + sco->borderWidth = game_to_data_coord(screenover[ovri].x - x); + sco->borderHeight = game_to_data_coord(screenover[ovri].y - y); + sco->isBackgroundSpeech = 0; + + ccRegisterManagedObject(sco, sco); + return sco; +} + +//============================================================================= + +void dispose_overlay(ScreenOverlay &over) +{ + delete over.pic; + over.pic = nullptr; + if (over.bmp != nullptr) + gfxDriver->DestroyDDB(over.bmp); + over.bmp = nullptr; + // if the script didn't actually use the Overlay* return + // value, dispose of the pointer + if (over.associatedOverlayHandle) + ccAttemptDisposeObject(over.associatedOverlayHandle); +} + +void remove_screen_overlay_index(size_t over_idx) +{ + ScreenOverlay &over = screenover[over_idx]; + dispose_overlay(over); + if (over.type==OVER_COMPLETE) is_complete_overlay--; + if (over.type==OVER_TEXTMSG) is_text_overlay--; + screenover.erase(screenover.begin() + over_idx); + // if an overlay before the sierra-style speech one is removed, + // update the index + if (face_talking >= 0 && (size_t)face_talking > over_idx) + face_talking--; +} + +void remove_screen_overlay(int type) +{ + for (size_t i = 0; i < screenover.size();) + { + if (type < 0 || screenover[i].type == type) + remove_screen_overlay_index(i); + else + i++; + } +} + +int find_overlay_of_type(int type) +{ + for (size_t i = 0; i < screenover.size(); ++i) + { + if (screenover[i].type == type) return i; + } + return -1; +} + +size_t add_screen_overlay(int x, int y, int type, Bitmap *piccy, bool alphaChannel) +{ + return add_screen_overlay(x, y, type, piccy, 0, 0, alphaChannel); +} + +size_t add_screen_overlay(int x, int y, int type, Common::Bitmap *piccy, int pic_offx, int pic_offy, bool alphaChannel) +{ + if (type==OVER_COMPLETE) is_complete_overlay++; + if (type==OVER_TEXTMSG) is_text_overlay++; + if (type==OVER_CUSTOM) { + // find an unused custom ID; TODO: find a better approach! + for (int id = OVER_CUSTOM + 1; id < screenover.size() + OVER_CUSTOM + 1; ++id) { + if (find_overlay_of_type(id) == -1) { type=id; break; } + } + } + ScreenOverlay over; + over.pic=piccy; + over.bmp = gfxDriver->CreateDDBFromBitmap(piccy, alphaChannel); + over.x=x; + over.y=y; + over._offsetX = pic_offx; + over._offsetY = pic_offy; + over.type=type; + over.timeout=0; + over.bgSpeechForChar = -1; + over.associatedOverlayHandle = 0; + over.hasAlphaChannel = alphaChannel; + over.positionRelativeToScreen = true; + screenover.push_back(over); + return screenover.size() - 1; +} + + + +void get_overlay_position(const ScreenOverlay &over, int *x, int *y) { + int tdxp, tdyp; + const Rect &ui_view = play.GetUIViewport(); + + if (over.x == OVR_AUTOPLACE) { + // auto place on character + int charid = over.y; + + auto view = FindNearestViewport(charid); + const int charpic = views[game.chars[charid].view].loops[game.chars[charid].loop].frames[0].pic; + const int height = (charextra[charid].height < 1) ? game.SpriteInfos[charpic].Height : charextra[charid].height; + Point screenpt = view->RoomToScreen( + data_to_game_coord(game.chars[charid].x), + data_to_game_coord(game.chars[charid].get_effective_y()) - height).first; + tdxp = screenpt.X - over.pic->GetWidth() / 2; + if (tdxp < 0) tdxp = 0; + tdyp = screenpt.Y - get_fixed_pixel_size(5); + tdyp -= over.pic->GetHeight(); + if (tdyp < 5) tdyp = 5; + + if ((tdxp + over.pic->GetWidth()) >= ui_view.GetWidth()) + tdxp = (ui_view.GetWidth() - over.pic->GetWidth()) - 1; + if (game.chars[charid].room != displayed_room) { + tdxp = ui_view.GetWidth()/2 - over.pic->GetWidth()/2; + tdyp = ui_view.GetHeight()/2 - over.pic->GetHeight()/2; + } + } + else { + // Note: the internal offset is only needed when x,y coordinates are specified + // and only in the case where the overlay is using a GUI. See issue #1098 + tdxp = over.x + over._offsetX; + tdyp = over.y + over._offsetY; + + if (!over.positionRelativeToScreen) + { + Point tdxy = play.RoomToScreen(tdxp, tdyp); + tdxp = tdxy.X; + tdyp = tdxy.Y; + } + } + *x = tdxp; + *y = tdyp; +} + +void recreate_overlay_ddbs() +{ + for (auto &over : screenover) + { + if (over.bmp) + gfxDriver->DestroyDDB(over.bmp); + if (over.pic) + over.bmp = gfxDriver->CreateDDBFromBitmap(over.pic, false); + else + over.bmp = nullptr; + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// ScriptOverlay* (int x, int y, int slot, int transparent) +RuntimeScriptValue Sc_Overlay_CreateGraphical(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT4(ScriptOverlay, Overlay_CreateGraphical); +} + +// ScriptOverlay* (int x, int y, int width, int font, int colour, const char* text, ...) +RuntimeScriptValue Sc_Overlay_CreateTextual(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(Overlay_CreateTextual, 6); + ScriptOverlay *overlay = Overlay_CreateTextual(params[0].IValue, params[1].IValue, params[2].IValue, + params[3].IValue, params[4].IValue, scsf_buffer); + return RuntimeScriptValue().SetDynamicObject(overlay, overlay); +} + +// void (ScriptOverlay *scover, int wii, int fontid, int clr, char*texx, ...) +RuntimeScriptValue Sc_Overlay_SetText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_SCRIPT_SPRINTF(Overlay_SetText, 4); + Overlay_SetText((ScriptOverlay*)self, params[0].IValue, params[1].IValue, params[2].IValue, scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + +// void (ScriptOverlay *sco) +RuntimeScriptValue Sc_Overlay_Remove(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptOverlay, Overlay_Remove); +} + +// int (ScriptOverlay *scover) +RuntimeScriptValue Sc_Overlay_GetValid(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptOverlay, Overlay_GetValid); +} + +// int (ScriptOverlay *scover) +RuntimeScriptValue Sc_Overlay_GetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptOverlay, Overlay_GetX); +} + +// void (ScriptOverlay *scover, int newx) +RuntimeScriptValue Sc_Overlay_SetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptOverlay, Overlay_SetX); +} + +// int (ScriptOverlay *scover) +RuntimeScriptValue Sc_Overlay_GetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptOverlay, Overlay_GetY); +} + +// void (ScriptOverlay *scover, int newy) +RuntimeScriptValue Sc_Overlay_SetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptOverlay, Overlay_SetY); +} + +//============================================================================= +// +// Exclusive API for Plugins +// +//============================================================================= + +// ScriptOverlay* (int x, int y, int width, int font, int colour, const char* text, ...) +ScriptOverlay* ScPl_Overlay_CreateTextual(int x, int y, int width, int font, int colour, const char *text, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(text); + return Overlay_CreateTextual(x, y, width, font, colour, scsf_buffer); +} + +// void (ScriptOverlay *scover, int wii, int fontid, int clr, char*texx, ...) +void ScPl_Overlay_SetText(ScriptOverlay *scover, int wii, int fontid, int clr, char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + Overlay_SetText(scover, wii, fontid, clr, scsf_buffer); +} + + +void RegisterOverlayAPI() +{ + ccAddExternalStaticFunction("Overlay::CreateGraphical^4", Sc_Overlay_CreateGraphical); + ccAddExternalStaticFunction("Overlay::CreateTextual^106", Sc_Overlay_CreateTextual); + ccAddExternalObjectFunction("Overlay::SetText^104", Sc_Overlay_SetText); + ccAddExternalObjectFunction("Overlay::Remove^0", Sc_Overlay_Remove); + ccAddExternalObjectFunction("Overlay::get_Valid", Sc_Overlay_GetValid); + ccAddExternalObjectFunction("Overlay::get_X", Sc_Overlay_GetX); + ccAddExternalObjectFunction("Overlay::set_X", Sc_Overlay_SetX); + ccAddExternalObjectFunction("Overlay::get_Y", Sc_Overlay_GetY); + ccAddExternalObjectFunction("Overlay::set_Y", Sc_Overlay_SetY); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Overlay::CreateGraphical^4", (void*)Overlay_CreateGraphical); + ccAddExternalFunctionForPlugin("Overlay::CreateTextual^106", (void*)ScPl_Overlay_CreateTextual); + ccAddExternalFunctionForPlugin("Overlay::SetText^104", (void*)ScPl_Overlay_SetText); + ccAddExternalFunctionForPlugin("Overlay::Remove^0", (void*)Overlay_Remove); + ccAddExternalFunctionForPlugin("Overlay::get_Valid", (void*)Overlay_GetValid); + ccAddExternalFunctionForPlugin("Overlay::get_X", (void*)Overlay_GetX); + ccAddExternalFunctionForPlugin("Overlay::set_X", (void*)Overlay_SetX); + ccAddExternalFunctionForPlugin("Overlay::get_Y", (void*)Overlay_GetY); + ccAddExternalFunctionForPlugin("Overlay::set_Y", (void*)Overlay_SetY); +} diff --git a/engines/ags/engine/ac/overlay.h b/engines/ags/engine/ac/overlay.h new file mode 100644 index 00000000000..073ae9c46db --- /dev/null +++ b/engines/ags/engine/ac/overlay.h @@ -0,0 +1,51 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__OVERLAY_H +#define __AGS_EE_AC__OVERLAY_H +#include +#include "ac/screenoverlay.h" +#include "ac/dynobj/scriptoverlay.h" + +namespace AGS { namespace Common { class Bitmap; } } +using namespace AGS; // FIXME later + +void Overlay_Remove(ScriptOverlay *sco); +void Overlay_SetText(ScriptOverlay *scover, int wii, int fontid, int clr, const char*text); +int Overlay_GetX(ScriptOverlay *scover); +void Overlay_SetX(ScriptOverlay *scover, int newx); +int Overlay_GetY(ScriptOverlay *scover); +void Overlay_SetY(ScriptOverlay *scover, int newy); +int Overlay_GetValid(ScriptOverlay *scover); +ScriptOverlay* Overlay_CreateGraphical(int x, int y, int slot, int transparent); +ScriptOverlay* Overlay_CreateTextual(int x, int y, int width, int font, int colour, const char* text); + +int find_overlay_of_type(int type); +void remove_screen_overlay(int type); +// Calculates overlay position in screen coordinates +void get_overlay_position(const ScreenOverlay &over, int *x, int *y); +size_t add_screen_overlay(int x,int y,int type,Common::Bitmap *piccy, bool alphaChannel = false); +size_t add_screen_overlay(int x, int y, int type, Common::Bitmap *piccy, int pic_offx, int pic_offy, bool alphaChannel = false); +void remove_screen_overlay_index(size_t over_idx); +void recreate_overlay_ddbs(); + +extern int is_complete_overlay; +extern int is_text_overlay; // blocking text overlay on screen + +extern std::vector screenover; + +#endif // __AGS_EE_AC__OVERLAY_H diff --git a/engines/ags/engine/ac/parser.cpp b/engines/ags/engine/ac/parser.cpp new file mode 100644 index 00000000000..6225249a336 --- /dev/null +++ b/engines/ags/engine/ac/parser.cpp @@ -0,0 +1,345 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include //isalnum() +#include +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/parser.h" +#include "ac/string.h" +#include "ac/wordsdictionary.h" +#include "debug/debug_log.h" +#include "util/string.h" +#include "util/string_compat.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; +extern GameState play; + +int Parser_FindWordID(const char *wordToFind) +{ + return find_word_in_dictionary(wordToFind); +} + +const char* Parser_SaidUnknownWord() { + if (play.bad_parsed_word[0] == 0) + return nullptr; + return CreateNewScriptString(play.bad_parsed_word); +} + +void ParseText (const char*text) { + parse_sentence (text, &play.num_parsed_words, play.parsed_words, nullptr, 0); +} + +// Said: call with argument for example "get apple"; we then check +// word by word if it matches (using dictonary ID equivalence to match +// synonyms). Returns 1 if it does, 0 if not. +int Said (const char *checkwords) { + int numword = 0; + short words[MAX_PARSED_WORDS]; + return parse_sentence (checkwords, &numword, &words[0], play.parsed_words, play.num_parsed_words); +} + +//============================================================================= + +int find_word_in_dictionary (const char *lookfor) { + int j; + if (game.dict == nullptr) + return -1; + + for (j = 0; j < game.dict->num_words; j++) { + if (ags_stricmp(lookfor, game.dict->word[j]) == 0) { + return game.dict->wordnum[j]; + } + } + if (lookfor[0] != 0) { + // If the word wasn't found, but it ends in 'S', see if there's + // a non-plural version + const char *ptat = &lookfor[strlen(lookfor)-1]; + char lastletter = *ptat; + if ((lastletter == 's') || (lastletter == 'S') || (lastletter == '\'')) { + String singular = lookfor; + singular.ClipRight(1); + return find_word_in_dictionary(singular); + } + } + return -1; +} + +int is_valid_word_char(char theChar) { + if ((isalnum((unsigned char)theChar)) || (theChar == '\'') || (theChar == '-')) { + return 1; + } + return 0; +} + +int FindMatchingMultiWordWord(char *thisword, const char **text) { + // see if there are any multi-word words + // that match -- if so, use them + const char *tempptr = *text; + char tempword[150] = ""; + if (thisword != nullptr) + strcpy(tempword, thisword); + + int bestMatchFound = -1, word; + const char *tempptrAtBestMatch = tempptr; + + do { + // extract and concat the next word + strcat(tempword, " "); + while (tempptr[0] == ' ') tempptr++; + char chbuffer[2]; + while (is_valid_word_char(tempptr[0])) { + sprintf(chbuffer, "%c", tempptr[0]); + strcat(tempword, chbuffer); + tempptr++; + } + // is this it? + word = find_word_in_dictionary(tempword); + // take the longest match we find + if (word >= 0) { + bestMatchFound = word; + tempptrAtBestMatch = tempptr; + } + + } while (tempptr[0] == ' '); + + word = bestMatchFound; + + if (word >= 0) { + // yes, a word like "pick up" was found + *text = tempptrAtBestMatch; + if (thisword != nullptr) + strcpy(thisword, tempword); + } + + return word; +} + +// parse_sentence: pass compareto as NULL to parse the sentence, or +// compareto as non-null to check if it matches the passed sentence +int parse_sentence (const char *src_text, int *numwords, short*wordarray, short*compareto, int comparetonum) { + char thisword[150] = "\0"; + int i = 0, comparing = 0; + char in_optional = 0, do_word_now = 0; + int optional_start = 0; + + numwords[0] = 0; + if (compareto == nullptr) + play.bad_parsed_word[0] = 0; + + String uniform_text = src_text; + uniform_text.MakeLower(); + const char *text = uniform_text.GetCStr(); + while (1) { + if ((compareto != nullptr) && (compareto[comparing] == RESTOFLINE)) + return 1; + + if ((text[0] == ']') && (compareto != nullptr)) { + if (!in_optional) + quitprintf("!Said: unexpected ']'\nText: %s", src_text); + do_word_now = 1; + } + + if (is_valid_word_char(text[0])) { + // Part of a word, add it on + thisword[i] = text[0]; + i++; + } + else if ((text[0] == '[') && (compareto != nullptr)) { + if (in_optional) + quitprintf("!Said: nested optional words\nText: %s", src_text); + + in_optional = 1; + optional_start = comparing; + } + else if ((thisword[0] != 0) || ((text[0] == 0) && (i > 0)) || (do_word_now == 1)) { + // End of word, so process it + thisword[i] = 0; + i = 0; + int word = -1; + + if (text[0] == ' ') { + word = FindMatchingMultiWordWord(thisword, &text); + } + + if (word < 0) { + // just a normal word + word = find_word_in_dictionary(thisword); + } + + // "look rol" + if (word == RESTOFLINE) + return 1; + if (compareto) { + // check string is longer than user input + if (comparing >= comparetonum) { + if (in_optional) { + // eg. "exit [door]" - there's no more user input + // but the optional word is still there + if (do_word_now) { + in_optional = 0; + do_word_now = 0; + } + thisword[0] = 0; + text++; + continue; + } + return 0; + } + if (word <= 0) + quitprintf("!Said: supplied word '%s' is not in dictionary or is an ignored word\nText: %s", thisword, src_text); + if (word == ANYWORD) { } + else if (word != compareto[comparing]) { + // words don't match - if a comma then a list of possibles, + // so allow retry + if (text[0] == ',') + comparing--; + else { + // words don't match + if (in_optional) { + // inside an optional clause, so skip it + while (text[0] != ']') { + if (text[0] == 0) + quitprintf("!Said: unterminated [optional]\nText: %s", src_text); + text++; + } + // -1 because it's about to be ++'d again + comparing = optional_start - 1; + } + // words don't match outside an optional clause, abort + else + return 0; + } + } + else if (text[0] == ',') { + // this alternative matched, but there are more + // so skip the other alternatives + int continueSearching = 1; + while (continueSearching) { + + const char *textStart = &text[1]; + + while ((text[0] == ',') || (isalnum((unsigned char)text[0]) != 0)) + text++; + + continueSearching = 0; + + if (text[0] == ' ') { + strcpy(thisword, textStart); + thisword[text - textStart] = 0; + // forward past any multi-word alternatives + if (FindMatchingMultiWordWord(thisword, &text) >= 0) + continueSearching = 1; + } + } + + if ((text[0] == ']') && (in_optional)) { + // [go,move] we just matched "go", so skip over "move" + in_optional = 0; + text++; + } + + // go back cos it'll be ++'d in a minute + text--; + } + comparing++; + } + else if (word != 0) { + // it's not an ignore word (it's a known word, or an unknown + // word, so save its index) + wordarray[numwords[0]] = word; + numwords[0]++; + if (numwords[0] >= MAX_PARSED_WORDS) + return 0; + // if it's an unknown word, store it for use in messages like + // "you can't use the word 'xxx' in this game" + if ((word < 0) && (play.bad_parsed_word[0] == 0)) + strcpy(play.bad_parsed_word, thisword); + } + + if (do_word_now) { + in_optional = 0; + do_word_now = 0; + } + + thisword[0] = 0; + } + if (text[0] == 0) + break; + text++; + } + // If the user input is longer than the Said string, it's wrong + // eg Said("look door") and they type "look door jibble" + // rol should be used instead to enable this + if (comparing < comparetonum) + return 0; + return 1; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// int (const char *wordToFind) +RuntimeScriptValue Sc_Parser_FindWordID(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(Parser_FindWordID, const char); +} + +// void (char*text) +RuntimeScriptValue Sc_ParseText(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_POBJ(ParseText, /*const*/ char); +} + +// const char* () +RuntimeScriptValue Sc_Parser_SaidUnknownWord(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ(const char, myScriptStringImpl, Parser_SaidUnknownWord); +} + +// int (char*checkwords) +RuntimeScriptValue Sc_Said(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(Said, /*const*/ char); +} + + + +void RegisterParserAPI() +{ + ccAddExternalStaticFunction("Parser::FindWordID^1", Sc_Parser_FindWordID); + ccAddExternalStaticFunction("Parser::ParseText^1", Sc_ParseText); + ccAddExternalStaticFunction("Parser::SaidUnknownWord^0",Sc_Parser_SaidUnknownWord); + ccAddExternalStaticFunction("Parser::Said^1", Sc_Said); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Parser::FindWordID^1", (void*)Parser_FindWordID); + ccAddExternalFunctionForPlugin("Parser::ParseText^1", (void*)ParseText); + ccAddExternalFunctionForPlugin("Parser::SaidUnknownWord^0",(void*)Parser_SaidUnknownWord); + ccAddExternalFunctionForPlugin("Parser::Said^1", (void*)Said); +} diff --git a/engines/ags/engine/ac/parser.h b/engines/ags/engine/ac/parser.h new file mode 100644 index 00000000000..eddeaebe5c5 --- /dev/null +++ b/engines/ags/engine/ac/parser.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__PARSER_H +#define __AGS_EE_AC__PARSER_H + +int Parser_FindWordID(const char *wordToFind); +const char* Parser_SaidUnknownWord(); +void ParseText (const char*text); +int Said (const char*checkwords); + +//============================================================================= + +int find_word_in_dictionary (const char *lookfor); +int is_valid_word_char(char theChar); +int FindMatchingMultiWordWord(char *thisword, const char **text); +int parse_sentence (const char *src_text, int *numwords, short*wordarray, short*compareto, int comparetonum); + +#endif // __AGS_EE_AC__PARSER_H diff --git a/engines/ags/engine/ac/path_helper.h b/engines/ags/engine/ac/path_helper.h new file mode 100644 index 00000000000..57898ac55eb --- /dev/null +++ b/engines/ags/engine/ac/path_helper.h @@ -0,0 +1,75 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Functions related to constructing game and script paths. +// +//============================================================================= +#ifndef __AGS_EE_AC__PATHHELPER_H +#define __AGS_EE_AC__PATHHELPER_H + +#include "util/string.h" + +using AGS::Common::String; + +// Filepath tokens, which are replaced by platform-specific directory names +extern const String UserSavedgamesRootToken; +extern const String GameSavedgamesDirToken; +extern const String GameDataDirToken; + +inline const char *PathOrCurDir(const char *path) +{ + return path ? path : "."; +} + +// Subsitutes illegal characters with '_'. This function uses illegal chars array +// specific to current platform. +void FixupFilename(char *filename); +// Checks if there is a slash after special token in the beginning of the +// file path, and adds one if it is missing. If no token is found, string is +// returned unchanged. +String FixSlashAfterToken(const String &path); +// Creates a directory path by combining absolute path to special directory with +// custom game's directory name. +// If the path is relative, keeps it unmodified (no extra subdir added). +String MakeSpecialSubDir(const String &sp_dir); + +// ResolvedPath describes an actual location pointed by a user path (e.g. from script) +struct ResolvedPath +{ + String BaseDir; // base directory, one of the special path roots + String FullPath;// full path + String AltPath; // alternative full path, for backwards compatibility +}; +// Resolves a file path provided by user (e.g. script) into actual file path, +// by substituting special keywords with actual platform-specific directory names. +// Fills in ResolvedPath object on success. +// Returns 'true' on success, and 'false' if either path is impossible to resolve +// or if the file path is forbidden to be accessed in current situation. +bool ResolveScriptPath(const String &sc_path, bool read_only, ResolvedPath &rp); +// Resolves a user file path for writing, and makes sure all the sub-directories are +// created along the actual path. +// Returns 'true' on success, and 'false' if either path is impossible to resolve, +// forbidden for writing, or if failed to create any subdirectories. +bool ResolveWritePathAndCreateDirs(const String &sc_path, ResolvedPath &rp); + +// Sets an optional path to treat like game's installation directory +void set_install_dir(const String &path, const String &audio_path, const String &voice_path); +// Returns a path to game installation directory (optionally a custom path could be set); +// does not include trailing '/' +String get_install_dir(); +String get_audio_install_dir(); +String get_voice_install_dir(); +void get_install_dir_path(char* buffer, const char *fileName); + +#endif // __AGS_EE_AC__PATHHELPER_H diff --git a/engines/ags/engine/ac/properties.cpp b/engines/ags/engine/ac/properties.cpp new file mode 100644 index 00000000000..d9da238488f --- /dev/null +++ b/engines/ags/engine/ac/properties.cpp @@ -0,0 +1,107 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/properties.h" +#include "ac/string.h" +#include "ac/dynobj/scriptstring.h" +#include "script/runtimescriptvalue.h" +#include "util/string_utils.h" + +using namespace AGS::Common; + +extern GameSetupStruct game; +extern ScriptString myScriptStringImpl; + +// begin custom property functions + +bool get_property_desc(PropertyDesc &desc, const char *property, PropertyType want_type) +{ + PropertySchema::const_iterator sch_it = game.propSchema.find(property); + if (sch_it == game.propSchema.end()) + quit("!GetProperty: no such property found in schema. Make sure you are using the property's name, and not its description, when calling this command."); + + desc = sch_it->second; + if (want_type == kPropertyString && desc.Type != kPropertyString) + quit("!GetTextProperty: need to use GetProperty for a non-text property"); + else if (want_type != kPropertyString && desc.Type == kPropertyString) + quit("!GetProperty: need to use GetTextProperty for a text property"); + return true; +} + +String get_property_value(const StringIMap &st_prop, const StringIMap &rt_prop, const char *property, const String def_val) +{ + // First check runtime properties, then static properties; + // if no matching entry was found, use default schema value + StringIMap::const_iterator it = rt_prop.find(property); + if (it != rt_prop.end()) + return it->second; + it = st_prop.find(property); + if (it != st_prop.end()) + return it->second; + return def_val; +} + +// Get an integer property +int get_int_property(const StringIMap &st_prop, const StringIMap &rt_prop, const char *property) +{ + PropertyDesc desc; + if (!get_property_desc(desc, property, kPropertyInteger)) + return 0; + return StrUtil::StringToInt(get_property_value(st_prop, rt_prop, property, desc.DefaultValue)); +} + +// Get a string property +void get_text_property(const StringIMap &st_prop, const StringIMap &rt_prop, const char *property, char *bufer) +{ + PropertyDesc desc; + if (!get_property_desc(desc, property, kPropertyString)) + return; + + String val = get_property_value(st_prop, rt_prop, property, desc.DefaultValue); + strcpy(bufer, val); +} + +const char* get_text_property_dynamic_string(const StringIMap &st_prop, const StringIMap &rt_prop, const char *property) +{ + PropertyDesc desc; + if (!get_property_desc(desc, property, kPropertyString)) + return nullptr; + + String val = get_property_value(st_prop, rt_prop, property, desc.DefaultValue); + return CreateNewScriptString(val); +} + +bool set_int_property(StringIMap &rt_prop, const char *property, int value) +{ + PropertyDesc desc; + if (get_property_desc(desc, property, kPropertyInteger)) + { + rt_prop[desc.Name] = StrUtil::IntToString(value); + return true; + } + return false; +} + +bool set_text_property(StringIMap &rt_prop, const char *property, const char* value) +{ + PropertyDesc desc; + if (get_property_desc(desc, property, kPropertyString)) + { + rt_prop[desc.Name] = value; + return true; + } + return false; +} diff --git a/engines/ags/engine/ac/properties.h b/engines/ags/engine/ac/properties.h new file mode 100644 index 00000000000..f16831ead86 --- /dev/null +++ b/engines/ags/engine/ac/properties.h @@ -0,0 +1,37 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__PROPERTIES_H +#define __AGS_EE_AC__PROPERTIES_H + +#include "game/customproperties.h" + +using AGS::Common::StringIMap; + +// Getting a property value requires static and runtime property maps. +// Key is first searched in runtime map, if not found - static map is taken, +// which contains original property values for particular game entity. +// Lastly, if the key is still not found, then the default schema value is +// returned for the given property. +int get_int_property(const StringIMap &st_prop, const StringIMap &rt_prop, const char *property); +void get_text_property(const StringIMap &st_prop, const StringIMap &rt_prop, const char *property, char *bufer); +const char* get_text_property_dynamic_string(const StringIMap &st_prop, const StringIMap &rt_prop, const char *property); + +bool set_int_property(StringIMap &rt_prop, const char *property, int value); +bool set_text_property(StringIMap &rt_prop, const char *property, const char* value); + +#endif // __AGS_EE_AC__PROPERTIES_H diff --git a/engines/ags/engine/ac/region.cpp b/engines/ags/engine/ac/region.cpp new file mode 100644 index 00000000000..6210ae38783 --- /dev/null +++ b/engines/ags/engine/ac/region.cpp @@ -0,0 +1,276 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/region.h" +#include "ac/common_defines.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_region.h" +#include "ac/room.h" +#include "ac/roomstatus.h" +#include "ac/dynobj/cc_region.h" +#include "ac/dynobj/scriptdrawingsurface.h" +#include "game/roomstruct.h" +#include "script/runtimescriptvalue.h" + +using namespace AGS::Common; + +extern ScriptRegion scrRegion[MAX_ROOM_REGIONS]; +extern RoomStruct thisroom; +extern RoomStatus*croom; +extern GameSetupStruct game; +extern COLOR_MAP maincoltable; +extern color palette[256]; +extern CCRegion ccDynamicRegion; + + +ScriptRegion *GetRegionAtRoom(int xx, int yy) { + return &scrRegion[GetRegionIDAtRoom(xx, yy)]; +} + +ScriptRegion *GetRegionAtScreen(int x, int y) +{ + VpPoint vpt = play.ScreenToRoomDivDown(x, y); + if (vpt.second < 0) + return nullptr; + return GetRegionAtRoom(vpt.first.X, vpt.first.Y); +} + +void Region_SetLightLevel(ScriptRegion *ssr, int brightness) { + SetAreaLightLevel(ssr->id, brightness); +} + +int Region_GetLightLevel(ScriptRegion *ssr) { + return thisroom.GetRegionLightLevel(ssr->id); +} + +int Region_GetTintEnabled(ScriptRegion *srr) { + if (thisroom.Regions[srr->id].Tint & 0xFF000000) + return 1; + return 0; +} + +int Region_GetTintRed(ScriptRegion *srr) { + + return thisroom.Regions[srr->id].Tint & 0x000000ff; +} + +int Region_GetTintGreen(ScriptRegion *srr) { + + return (thisroom.Regions[srr->id].Tint >> 8) & 0x000000ff; +} + +int Region_GetTintBlue(ScriptRegion *srr) { + + return (thisroom.Regions[srr->id].Tint >> 16) & 0x000000ff; +} + +int Region_GetTintSaturation(ScriptRegion *srr) { + + return (thisroom.Regions[srr->id].Tint >> 24) & 0xFF; +} + +int Region_GetTintLuminance(ScriptRegion *srr) +{ + return thisroom.GetRegionTintLuminance(srr->id); +} + +void Region_Tint(ScriptRegion *srr, int red, int green, int blue, int amount, int luminance) +{ + SetRegionTint(srr->id, red, green, blue, amount, luminance); +} + +void Region_TintNoLum(ScriptRegion *srr, int red, int green, int blue, int amount) +{ + SetRegionTint(srr->id, red, green, blue, amount); +} + +void Region_SetEnabled(ScriptRegion *ssr, int enable) { + if (enable) + EnableRegion(ssr->id); + else + DisableRegion(ssr->id); +} + +int Region_GetEnabled(ScriptRegion *ssr) { + return croom->region_enabled[ssr->id]; +} + +int Region_GetID(ScriptRegion *ssr) { + return ssr->id; +} + +void Region_RunInteraction(ScriptRegion *ssr, int mood) { + RunRegionInteraction(ssr->id, mood); +} + +//============================================================================= + +void generate_light_table() +{ + if (game.color_depth == 1 && color_map == nullptr) + { + create_light_table(&maincoltable, palette, 0, 0, 0, nullptr); + color_map = &maincoltable; + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// ScriptRegion *(int xx, int yy) +RuntimeScriptValue Sc_GetRegionAtRoom(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(ScriptRegion, ccDynamicRegion, GetRegionAtRoom); +} + +RuntimeScriptValue Sc_GetRegionAtScreen(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT2(ScriptRegion, ccDynamicRegion, GetRegionAtScreen); +} + +RuntimeScriptValue Sc_Region_GetDrawingSurface(const RuntimeScriptValue *params, int32_t param_count) +{ + ScriptDrawingSurface* ret_obj = Room_GetDrawingSurfaceForMask(kRoomAreaRegion); + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj); +} + +RuntimeScriptValue Sc_Region_Tint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT5(ScriptRegion, Region_Tint); +} + +// void (ScriptRegion *srr, int red, int green, int blue, int amount) +RuntimeScriptValue Sc_Region_TintNoLum(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(ScriptRegion, Region_TintNoLum); +} + +// void (ScriptRegion *ssr, int mood) +RuntimeScriptValue Sc_Region_RunInteraction(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptRegion, Region_RunInteraction); +} + +// int (ScriptRegion *ssr) +RuntimeScriptValue Sc_Region_GetEnabled(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptRegion, Region_GetEnabled); +} + +// void (ScriptRegion *ssr, int enable) +RuntimeScriptValue Sc_Region_SetEnabled(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptRegion, Region_SetEnabled); +} + +// int (ScriptRegion *ssr) +RuntimeScriptValue Sc_Region_GetID(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptRegion, Region_GetID); +} + +// int (ScriptRegion *ssr) +RuntimeScriptValue Sc_Region_GetLightLevel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptRegion, Region_GetLightLevel); +} + +// void (ScriptRegion *ssr, int brightness) +RuntimeScriptValue Sc_Region_SetLightLevel(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptRegion, Region_SetLightLevel); +} + +// int (ScriptRegion *srr) +RuntimeScriptValue Sc_Region_GetTintEnabled(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptRegion, Region_GetTintEnabled); +} + +// int (ScriptRegion *srr) +RuntimeScriptValue Sc_Region_GetTintBlue(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptRegion, Region_GetTintBlue); +} + +// int (ScriptRegion *srr) +RuntimeScriptValue Sc_Region_GetTintGreen(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptRegion, Region_GetTintGreen); +} + +// int (ScriptRegion *srr) +RuntimeScriptValue Sc_Region_GetTintRed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptRegion, Region_GetTintRed); +} + +// int (ScriptRegion *srr) +RuntimeScriptValue Sc_Region_GetTintSaturation(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptRegion, Region_GetTintSaturation); +} + +RuntimeScriptValue Sc_Region_GetTintLuminance(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptRegion, Region_GetTintLuminance); +} + + + +void RegisterRegionAPI() +{ + ccAddExternalStaticFunction("Region::GetAtRoomXY^2", Sc_GetRegionAtRoom); + ccAddExternalStaticFunction("Region::GetAtScreenXY^2", Sc_GetRegionAtScreen); + ccAddExternalStaticFunction("Region::GetDrawingSurface", Sc_Region_GetDrawingSurface); + ccAddExternalObjectFunction("Region::Tint^4", Sc_Region_TintNoLum); + ccAddExternalObjectFunction("Region::Tint^5", Sc_Region_Tint); + ccAddExternalObjectFunction("Region::RunInteraction^1", Sc_Region_RunInteraction); + ccAddExternalObjectFunction("Region::get_Enabled", Sc_Region_GetEnabled); + ccAddExternalObjectFunction("Region::set_Enabled", Sc_Region_SetEnabled); + ccAddExternalObjectFunction("Region::get_ID", Sc_Region_GetID); + ccAddExternalObjectFunction("Region::get_LightLevel", Sc_Region_GetLightLevel); + ccAddExternalObjectFunction("Region::set_LightLevel", Sc_Region_SetLightLevel); + ccAddExternalObjectFunction("Region::get_TintEnabled", Sc_Region_GetTintEnabled); + ccAddExternalObjectFunction("Region::get_TintBlue", Sc_Region_GetTintBlue); + ccAddExternalObjectFunction("Region::get_TintGreen", Sc_Region_GetTintGreen); + ccAddExternalObjectFunction("Region::get_TintRed", Sc_Region_GetTintRed); + ccAddExternalObjectFunction("Region::get_TintSaturation", Sc_Region_GetTintSaturation); + ccAddExternalObjectFunction("Region::get_TintLuminance", Sc_Region_GetTintLuminance); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Region::GetAtRoomXY^2", (void*)GetRegionAtRoom); + ccAddExternalFunctionForPlugin("Region::GetAtScreenXY^2", (void*)GetRegionAtScreen); + ccAddExternalFunctionForPlugin("Region::Tint^4", (void*)Region_TintNoLum); + ccAddExternalFunctionForPlugin("Region::RunInteraction^1", (void*)Region_RunInteraction); + ccAddExternalFunctionForPlugin("Region::get_Enabled", (void*)Region_GetEnabled); + ccAddExternalFunctionForPlugin("Region::set_Enabled", (void*)Region_SetEnabled); + ccAddExternalFunctionForPlugin("Region::get_ID", (void*)Region_GetID); + ccAddExternalFunctionForPlugin("Region::get_LightLevel", (void*)Region_GetLightLevel); + ccAddExternalFunctionForPlugin("Region::set_LightLevel", (void*)Region_SetLightLevel); + ccAddExternalFunctionForPlugin("Region::get_TintEnabled", (void*)Region_GetTintEnabled); + ccAddExternalFunctionForPlugin("Region::get_TintBlue", (void*)Region_GetTintBlue); + ccAddExternalFunctionForPlugin("Region::get_TintGreen", (void*)Region_GetTintGreen); + ccAddExternalFunctionForPlugin("Region::get_TintRed", (void*)Region_GetTintRed); + ccAddExternalFunctionForPlugin("Region::get_TintSaturation", (void*)Region_GetTintSaturation); +} diff --git a/engines/ags/engine/ac/region.h b/engines/ags/engine/ac/region.h new file mode 100644 index 00000000000..66ca3b3c196 --- /dev/null +++ b/engines/ags/engine/ac/region.h @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__REGION_H +#define __AGS_EE_AC__REGION_H + +#include "ac/dynobj/scriptregion.h" + +ScriptRegion *GetRegionAtRoom(int xx, int yy); +void Region_SetLightLevel(ScriptRegion *ssr, int brightness); +int Region_GetLightLevel(ScriptRegion *ssr); +int Region_GetTintEnabled(ScriptRegion *srr); +int Region_GetTintRed(ScriptRegion *srr); +int Region_GetTintGreen(ScriptRegion *srr); +int Region_GetTintBlue(ScriptRegion *srr); +int Region_GetTintSaturation(ScriptRegion *srr); +int Region_GetTintLuminance(ScriptRegion *srr); +void Region_Tint(ScriptRegion *srr, int red, int green, int blue, int amount, int luminance); +void Region_SetEnabled(ScriptRegion *ssr, int enable); +int Region_GetEnabled(ScriptRegion *ssr); +int Region_GetID(ScriptRegion *ssr); +void Region_RunInteraction(ScriptRegion *ssr, int mood); + +void generate_light_table(); + +#endif // __AGS_EE_AC__REGION_H diff --git a/engines/ags/engine/ac/richgamemedia.cpp b/engines/ags/engine/ac/richgamemedia.cpp new file mode 100644 index 00000000000..a0ed4187d03 --- /dev/null +++ b/engines/ags/engine/ac/richgamemedia.cpp @@ -0,0 +1,35 @@ + +#include "ac/richgamemedia.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +void RICH_GAME_MEDIA_HEADER::ReadFromFile(Stream *in) +{ + dwMagicNumber = in->ReadInt32(); + dwHeaderVersion = in->ReadInt32(); + dwHeaderSize = in->ReadInt32(); + dwThumbnailOffsetLowerDword = in->ReadInt32(); + dwThumbnailOffsetHigherDword = in->ReadInt32(); + dwThumbnailSize = in->ReadInt32(); + in->Read(guidGameId, 16); + in->ReadArrayOfInt16((int16_t*)szGameName, RM_MAXLENGTH); + in->ReadArrayOfInt16((int16_t*)szSaveName, RM_MAXLENGTH); + in->ReadArrayOfInt16((int16_t*)szLevelName, RM_MAXLENGTH); + in->ReadArrayOfInt16((int16_t*)szComments, RM_MAXLENGTH); +} + +void RICH_GAME_MEDIA_HEADER::WriteToFile(Stream *out) +{ + out->WriteInt32(dwMagicNumber); + out->WriteInt32(dwHeaderVersion); + out->WriteInt32(dwHeaderSize); + out->WriteInt32(dwThumbnailOffsetLowerDword); + out->WriteInt32(dwThumbnailOffsetHigherDword); + out->WriteInt32(dwThumbnailSize); + out->Write(guidGameId, 16); + out->WriteArrayOfInt16((int16_t*)szGameName, RM_MAXLENGTH); + out->WriteArrayOfInt16((int16_t*)szSaveName, RM_MAXLENGTH); + out->WriteArrayOfInt16((int16_t*)szLevelName, RM_MAXLENGTH); + out->WriteArrayOfInt16((int16_t*)szComments, RM_MAXLENGTH); +} diff --git a/engines/ags/engine/ac/richgamemedia.h b/engines/ags/engine/ac/richgamemedia.h new file mode 100644 index 00000000000..0206ea841f2 --- /dev/null +++ b/engines/ags/engine/ac/richgamemedia.h @@ -0,0 +1,51 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__RICHGAMEMEDIA_H +#define __AGS_EE_AC__RICHGAMEMEDIA_H + +// Windows Vista Rich Save Games, modified to be platform-agnostic + +#define RM_MAXLENGTH 1024 +#define RM_MAGICNUMBER "RGMH" + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +#pragma pack(push) +#pragma pack(1) +typedef struct _RICH_GAME_MEDIA_HEADER +{ + int dwMagicNumber; + int dwHeaderVersion; + int dwHeaderSize; + int dwThumbnailOffsetLowerDword; + int dwThumbnailOffsetHigherDword; + int dwThumbnailSize; + unsigned char guidGameId[16]; + unsigned short szGameName[RM_MAXLENGTH]; + unsigned short szSaveName[RM_MAXLENGTH]; + unsigned short szLevelName[RM_MAXLENGTH]; + unsigned short szComments[RM_MAXLENGTH]; + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); +} RICH_GAME_MEDIA_HEADER; +#pragma pack(pop) + +#endif // __AGS_EE_AC__RICHGAMEMEDIA_H diff --git a/engines/ags/engine/ac/room.cpp b/engines/ags/engine/ac/room.cpp new file mode 100644 index 00000000000..016d2cbd004 --- /dev/null +++ b/engines/ags/engine/ac/room.cpp @@ -0,0 +1,1248 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include // for toupper + +#include "core/platform.h" +#include "util/string_utils.h" //strlwr() +#include "ac/common.h" +#include "ac/charactercache.h" +#include "ac/characterextras.h" +#include "ac/draw.h" +#include "ac/event.h" +#include "ac/game.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_audio.h" +#include "ac/global_character.h" +#include "ac/global_game.h" +#include "ac/global_object.h" +#include "ac/global_translation.h" +#include "ac/movelist.h" +#include "ac/mouse.h" +#include "ac/objectcache.h" +#include "ac/overlay.h" +#include "ac/properties.h" +#include "ac/region.h" +#include "ac/sys_events.h" +#include "ac/room.h" +#include "ac/roomobject.h" +#include "ac/roomstatus.h" +#include "ac/screen.h" +#include "ac/string.h" +#include "ac/system.h" +#include "ac/walkablearea.h" +#include "ac/walkbehind.h" +#include "ac/dynobj/scriptobject.h" +#include "ac/dynobj/scripthotspot.h" +#include "gui/guidefines.h" +#include "script/cc_instance.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "debug/out.h" +#include "game/room_version.h" +#include "platform/base/agsplatformdriver.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "script/cc_error.h" +#include "script/script.h" +#include "script/script_runtime.h" +#include "ac/spritecache.h" +#include "util/stream.h" +#include "gfx/graphicsdriver.h" +#include "core/assetmanager.h" +#include "ac/dynobj/all_dynamicclasses.h" +#include "gfx/bitmap.h" +#include "gfx/gfxfilter.h" +#include "util/math.h" +#include "media/audio/audio_system.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetup usetup; +extern GameSetupStruct game; +extern GameState play; +extern RoomStatus*croom; +extern RoomStatus troom; // used for non-saveable rooms, eg. intro +extern int displayed_room; +extern RoomObject*objs; +extern ccInstance *roominst; +extern AGSPlatformDriver *platform; +extern int numevents; +extern CharacterCache *charcache; +extern ObjectCache objcache[MAX_ROOM_OBJECTS]; +extern CharacterExtras *charextra; +extern int done_es_error; +extern int our_eip; +extern Bitmap *walkareabackup, *walkable_areas_temp; +extern ScriptObject scrObj[MAX_ROOM_OBJECTS]; +extern SpriteCache spriteset; +extern int in_new_room, new_room_was; // 1 in new room, 2 first time in new room, 3 loading saved game +extern ScriptHotspot scrHotspot[MAX_ROOM_HOTSPOTS]; +extern int in_leaves_screen; +extern CharacterInfo*playerchar; +extern int starting_room; +extern unsigned int loopcounter; +extern IDriverDependantBitmap* roomBackgroundBmp; +extern IGraphicsDriver *gfxDriver; +extern Bitmap *raw_saved_screen; +extern int actSpsCount; +extern Bitmap **actsps; +extern IDriverDependantBitmap* *actspsbmp; +extern Bitmap **actspswb; +extern IDriverDependantBitmap* *actspswbbmp; +extern CachedActSpsData* actspswbcache; +extern color palette[256]; +extern int mouse_z_was; + +extern Bitmap **guibg; +extern IDriverDependantBitmap **guibgbmp; + +extern CCHotspot ccDynamicHotspot; +extern CCObject ccDynamicObject; + +RGB_MAP rgb_table; // for 256-col antialiasing +int new_room_flags=0; +int gs_to_newroom=-1; + +ScriptDrawingSurface* Room_GetDrawingSurfaceForBackground(int backgroundNumber) +{ + if (displayed_room < 0) + quit("!Room.GetDrawingSurfaceForBackground: no room is currently loaded"); + + if (backgroundNumber == SCR_NO_VALUE) + { + backgroundNumber = play.bg_frame; + } + + if ((backgroundNumber < 0) || ((size_t)backgroundNumber >= 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 (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 croom->numobj; +} + +int Room_GetWidth() { + return thisroom.Width; +} + +int Room_GetHeight() { + return thisroom.Height; +} + +int Room_GetColorDepth() { + return thisroom.BgFrames[0].Graphic->GetColorDepth(); +} + +int Room_GetLeftEdge() { + return thisroom.Edges.Left; +} + +int Room_GetRightEdge() { + return thisroom.Edges.Right; +} + +int Room_GetTopEdge() { + return thisroom.Edges.Top; +} + +int Room_GetBottomEdge() { + return thisroom.Edges.Bottom; +} + +int Room_GetMusicOnLoad() { + return thisroom.Options.StartupMusic; +} + +int Room_GetProperty(const char *property) +{ + return get_int_property(thisroom.Properties, croom->roomProps, property); +} + +const char* Room_GetTextProperty(const char *property) +{ + return get_text_property_dynamic_string(thisroom.Properties, croom->roomProps, property); +} + +bool Room_SetProperty(const char *property, int value) +{ + return set_int_property(croom->roomProps, property, value); +} + +bool Room_SetTextProperty(const char *property, const char *value) +{ + return set_text_property(croom->roomProps, property, value); +} + +const char* Room_GetMessages(int index) { + if ((index < 0) || ((size_t)index >= thisroom.MessageCount)) { + return nullptr; + } + char buffer[STD_BUFFER_SIZE]; + buffer[0]=0; + replace_tokens(get_translation(thisroom.Messages[index]), buffer, STD_BUFFER_SIZE); + return CreateNewScriptString(buffer); +} + + +//============================================================================= + +// 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 (!game.AllowRelativeRes() || !thisroom.IsRelativeRes()) + return; + + int bkg_width = thisroom.Width; + int bkg_height = thisroom.Height; + data_to_game_coords(&bkg_width, &bkg_height); + + for (size_t i = 0; i < thisroom.BgFrameCount; ++i) + thisroom.BgFrames[i].Graphic = FixBitmap(thisroom.BgFrames[i].Graphic, bkg_width, bkg_height); + + // Fix walk-behinds to match room background + // TODO: would not we need to do similar to each mask if they were 1:1 in hires room? + thisroom.WalkBehindMask = FixBitmap(thisroom.WalkBehindMask, bkg_width, bkg_height); +} + + +void save_room_data_segment () { + croom->FreeScriptData(); + + croom->tsdatasize = roominst->globaldatasize; + if (croom->tsdatasize > 0) { + croom->tsdata=(char*)malloc(croom->tsdatasize+10); + memcpy(croom->tsdata,&roominst->globaldata[0],croom->tsdatasize); + } + +} + +void unload_old_room() { + int ff; + + // if switching games on restore, don't do this + if (displayed_room < 0) + return; + + debug_script_log("Unloading room %d", displayed_room); + + current_fade_out_effect(); + + dispose_room_drawdata(); + + for (ff=0;ffnumobj;ff++) + objs[ff].moving = 0; + + if (!play.ambient_sounds_persist) { + for (ff = 1; ff < MAX_SOUND_CHANNELS; ff++) + StopAmbientSound(ff); + } + + cancel_all_scripts(); + numevents = 0; // cancel any pending room events + + if (roomBackgroundBmp != nullptr) + { + gfxDriver->DestroyDDB(roomBackgroundBmp); + roomBackgroundBmp = nullptr; + } + + if (croom==nullptr) ; + else if (roominst!=nullptr) { + save_room_data_segment(); + delete roominstFork; + delete roominst; + roominstFork = nullptr; + roominst=nullptr; + } + else croom->tsdatasize=0; + memset(&play.walkable_areas_on[0],1,MAX_WALK_AREAS+1); + play.bg_frame=0; + play.bg_frame_locked=0; + remove_screen_overlay(-1); + delete raw_saved_screen; + raw_saved_screen = nullptr; + for (ff = 0; ff < MAX_ROOM_BGFRAMES; ff++) + play.raw_modified[ff] = 0; + for (size_t i = 0; i < thisroom.LocalVariables.size() && i < MAX_GLOBAL_VARIABLES; ++i) + croom->interactionVariableValues[i] = thisroom.LocalVariables[i].Value; + + // wipe the character cache when we change rooms + for (ff = 0; ff < game.numcharacters; ff++) { + if (charcache[ff].inUse) { + delete charcache[ff].image; + charcache[ff].image = nullptr; + charcache[ff].inUse = 0; + } + // ensure that any half-moves (eg. with scaled movement) are stopped + charextra[ff].xwas = INVALID_X; + } + + play.swap_portrait_lastchar = -1; + play.swap_portrait_lastlastchar = -1; + + for (ff = 0; ff < croom->numobj; ff++) { + // un-export the object's script object + if (objectScriptObjNames[ff].IsEmpty()) + continue; + + ccRemoveExternalSymbol(objectScriptObjNames[ff]); + } + + for (ff = 0; ff < MAX_ROOM_HOTSPOTS; ff++) { + if (thisroom.Hotspots[ff].ScriptName.IsEmpty()) + continue; + + ccRemoveExternalSymbol(thisroom.Hotspots[ff].ScriptName); + } + + croom_ptr_clear(); + + // clear the object cache + for (ff = 0; ff < MAX_ROOM_OBJECTS; ff++) { + delete objcache[ff].image; + objcache[ff].image = nullptr; + } + // clear the 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 + for (ff = 0; ff < MAX_ROOM_OBJECTS + game.numcharacters; ff++) { + delete actsps[ff]; + actsps[ff] = nullptr; + + if (actspsbmp[ff] != nullptr) + gfxDriver->DestroyDDB(actspsbmp[ff]); + actspsbmp[ff] = nullptr; + + delete actspswb[ff]; + actspswb[ff] = nullptr; + + if (actspswbbmp[ff] != nullptr) + gfxDriver->DestroyDDB(actspswbbmp[ff]); + actspswbbmp[ff] = nullptr; + + actspswbcache[ff].valid = 0; + } + + // if Hide Player Character was ticked, restore it to visible + if (play.temporarily_turned_off_character >= 0) { + game.chars[play.temporarily_turned_off_character].on = 1; + 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 (game.GetDataUpscaleMult() == 1) + return; + + const int mul = game.GetDataUpscaleMult(); + for (size_t i = 0; i < rstruc->ObjectCount; ++i) + { + rstruc->Objects[i].X /= mul; + rstruc->Objects[i].Y /= mul; + if (rstruc->Objects[i].Baseline > 0) + { + rstruc->Objects[i].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; +} + +extern int convert_16bit_bgr; + +void update_letterbox_mode() +{ + const Size real_room_sz = Size(data_to_game_coord(thisroom.Width), data_to_game_coord(thisroom.Height)); + const Rect game_frame = RectWH(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 < game.GetLetterboxSize().Height ? real_room_sz.Height : + (real_room_sz.Height >= game.GetLetterboxSize().Height && real_room_sz.Height < game.GetGameRes().Height) ? game.GetLetterboxSize().Height : + game.GetGameRes().Height; + new_main_view.SetHeight(viewport_height); + + play.SetMainViewport(CenterInRect(game_frame, new_main_view)); + play.SetUIViewport(new_main_view); +} + +// 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(thisroom.Width), data_to_game_coord(thisroom.Height)); + const Rect main_view = play.GetMainViewport(); + Rect new_room_view = RectWH(Size::Clamp(real_room_sz, Size(1, 1), main_view.GetSize())); + + auto view = 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 < play.GetRoomCameraCount(); ++i) + { + auto cam = play.GetRoomCamera(i); + const Rect old_pos = cam->GetRect(); + cam->SetSize(old_pos.GetSize()); + cam->SetAt(old_pos.Left, old_pos.Top); + } +} + +// 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; + int cc; + done_es_error = 0; + 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); + 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 ((loaded_game_file_version < kGameVersion_270 && Common::AssetManager::DoesAssetExist("intro.crm")) || + (loaded_game_file_version >= kGameVersion_270 && !Common::AssetManager::DoesAssetExist(room_filename))) + { + room_filename = "intro.crm"; + } + } + + update_polled_stuff_if_runtime(); + + // load the room from disk + our_eip=200; + thisroom.GameID = NO_GAME_ID_IN_ROOM_FILE; + load_room(room_filename, &thisroom, game.IsLegacyHiRes(), game.SpriteInfos); + + if ((thisroom.GameID != NO_GAME_ID_IN_ROOM_FILE) && + (thisroom.GameID != game.uniqueid)) { + quitprintf("!Unable to load '%s'. This room file is assigned to a different game.", room_filename.GetCStr()); + } + + convert_room_coordinates_to_data_res(&thisroom); + + update_polled_stuff_if_runtime(); + our_eip=201; + /* // apparently, doing this stops volume spiking between tracks + if (thisroom.Options.StartupMusic>0) { + stopmusic(); + delay(100); + }*/ + + play.room_width = thisroom.Width; + play.room_height = thisroom.Height; + play.anim_background_speed = thisroom.BgAnimSpeed; + play.bg_anim_delay = play.anim_background_speed; + + // do the palette + for (cc=0;cc<256;cc++) { + if (game.paluses[cc]==PAL_BACKGROUND) + palette[cc]=thisroom.Palette[cc]; + else { + // copy the gamewide colours into the room palette + for (size_t i = 0; i < thisroom.BgFrameCount; ++i) + thisroom.BgFrames[i].Palette[cc] = palette[cc]; + } + } + + for (size_t i = 0; i < thisroom.BgFrameCount; ++i) { + update_polled_stuff_if_runtime(); + thisroom.BgFrames[i].Graphic = PrepareSpriteForUse(thisroom.BgFrames[i].Graphic, false); + } + + update_polled_stuff_if_runtime(); + + our_eip=202; + // Update game viewports + if (game.IsLegacyLetterbox()) + update_letterbox_mode(); + SetMouseBounds(0, 0, 0, 0); + + our_eip=203; + in_new_room=1; + + // walkable_areas_temp is used by the pathfinder to generate a + // copy of the walkable areas - allocate it here to save time later + delete walkable_areas_temp; + walkable_areas_temp = BitmapHelper::CreateBitmap(thisroom.WalkAreaMask->GetWidth(), thisroom.WalkAreaMask->GetHeight(), 8); + + // Make a backup copy of the walkable areas prior to + // any RemoveWalkableArea commands + delete walkareabackup; + // copy the walls screen + walkareabackup=BitmapHelper::CreateBitmapCopy(thisroom.WalkAreaMask.get()); + + our_eip=204; + update_polled_stuff_if_runtime(); + redo_walkable_areas(); + update_polled_stuff_if_runtime(); + + set_color_depth(game.GetColorDepth()); + convert_room_background_to_game_res(); + recache_walk_behinds(); + update_polled_stuff_if_runtime(); + + our_eip=205; + // setup objects + if (forchar != nullptr) { + // if not restoring a game, always reset this room + troom.beenhere=0; + troom.FreeScriptData(); + troom.FreeProperties(); + memset(&troom.hotspot_enabled[0],1,MAX_ROOM_HOTSPOTS); + memset(&troom.region_enabled[0], 1, MAX_ROOM_REGIONS); + } + if ((newnum>=0) & (newnumbeenhere > 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 (thisroom.EventHandlers == nullptr) + {// legacy interactions + thisroom.Interaction->CopyTimesRun(croom->intrRoom); + for (cc=0;cc < MAX_ROOM_HOTSPOTS;cc++) + thisroom.Hotspots[cc].Interaction->CopyTimesRun(croom->intrHotspot[cc]); + for (cc=0;cc < MAX_ROOM_OBJECTS;cc++) + thisroom.Objects[cc].Interaction->CopyTimesRun(croom->intrObject[cc]); + for (cc=0;cc < MAX_ROOM_REGIONS;cc++) + thisroom.Regions[cc].Interaction->CopyTimesRun(croom->intrRegion[cc]); + } + } + if (croom->beenhere==0) { + croom->numobj=thisroom.ObjectCount; + croom->tsdatasize=0; + for (cc=0;ccnumobj;cc++) { + croom->obj[cc].x=thisroom.Objects[cc].X; + croom->obj[cc].y=thisroom.Objects[cc].Y; + croom->obj[cc].num=thisroom.Objects[cc].Sprite; + croom->obj[cc].on=thisroom.Objects[cc].IsOn; + croom->obj[cc].view=-1; + croom->obj[cc].loop=0; + croom->obj[cc].frame=0; + croom->obj[cc].wait=0; + croom->obj[cc].transparent=0; + croom->obj[cc].moving=-1; + croom->obj[cc].flags = thisroom.Objects[cc].Flags; + croom->obj[cc].baseline=-1; + croom->obj[cc].zoom = 100; + croom->obj[cc].last_width = 0; + croom->obj[cc].last_height = 0; + croom->obj[cc].blocking_width = 0; + croom->obj[cc].blocking_height = 0; + if (thisroom.Objects[cc].Baseline>=0) + // croom->obj[cc].baseoffs=thisroom.Objects.Baseline[cc]-thisroom.Objects[cc].y; + croom->obj[cc].baseline=thisroom.Objects[cc].Baseline; + } + for (size_t i = 0; i < (size_t)MAX_WALK_BEHINDS; ++i) + croom->walkbehind_base[i] = thisroom.WalkBehinds[i].Baseline; + for (cc=0;ccflagstates[cc]=0; + + /* // we copy these structs for the Score column to work + croom->misccond=thisroom.misccond; + for (cc=0;cchscond[cc]=thisroom.hscond[cc]; + for (cc=0;ccobjcond[cc]=thisroom.objcond[cc];*/ + + for (cc=0;cc < MAX_ROOM_HOTSPOTS;cc++) { + croom->hotspot_enabled[cc] = 1; + } + for (cc = 0; cc < MAX_ROOM_REGIONS; cc++) { + croom->region_enabled[cc] = 1; + } + + croom->beenhere=1; + in_new_room=2; + } + else { + // We have been here before + for (size_t i = 0; i < thisroom.LocalVariables.size() && i < (size_t)MAX_GLOBAL_VARIABLES; ++i) + thisroom.LocalVariables[i].Value = croom->interactionVariableValues[i]; + } + + update_polled_stuff_if_runtime(); + + if (thisroom.EventHandlers == nullptr) + {// legacy interactions + // copy interactions from room file into our temporary struct + croom->intrRoom = *thisroom.Interaction; + for (cc=0;ccintrHotspot[cc] = *thisroom.Hotspots[cc].Interaction; + for (cc=0;ccintrObject[cc] = *thisroom.Objects[cc].Interaction; + for (cc=0;ccintrRegion[cc] = *thisroom.Regions[cc].Interaction; + } + + objs=&croom->obj[0]; + + for (cc = 0; cc < MAX_ROOM_OBJECTS; cc++) { + // 64 bit: Using the id instead + // scrObj[cc].obj = &croom->obj[cc]; + objectScriptObjNames[cc].Free(); + } + + for (cc = 0; cc < croom->numobj; cc++) { + // export the object's script object + if (thisroom.Objects[cc].ScriptName.IsEmpty()) + continue; + objectScriptObjNames[cc] = thisroom.Objects[cc].ScriptName; + ccAddExternalDynamicObject(objectScriptObjNames[cc], &scrObj[cc], &ccDynamicObject); + } + + for (cc = 0; cc < MAX_ROOM_HOTSPOTS; cc++) { + if (thisroom.Hotspots[cc].ScriptName.IsEmpty()) + continue; + + ccAddExternalDynamicObject(thisroom.Hotspots[cc].ScriptName, &scrHotspot[cc], &ccDynamicHotspot); + } + + our_eip=206; + /* THIS IS DONE IN THE EDITOR NOW + thisroom.BgFrames.IsPaletteShared[0] = 1; + for (dd = 1; dd < thisroom.BgFrameCount; dd++) { + if (memcmp (&thisroom.BgFrames.Palette[dd][0], &palette[0], sizeof(color) * 256) == 0) + thisroom.BgFrames.IsPaletteShared[dd] = 1; + else + thisroom.BgFrames.IsPaletteShared[dd] = 0; + } + // only make the first frame shared if the last is + if (thisroom.BgFrames.IsPaletteShared[thisroom.BgFrameCount - 1] == 0) + thisroom.BgFrames.IsPaletteShared[0] = 0;*/ + + update_polled_stuff_if_runtime(); + + 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 (palette[ff].r > 63) + palette[ff].r = 63; + if (palette[ff].g > 63) + palette[ff].g = 63; + if (palette[ff].b > 63) + palette[ff].b = 63; + } + create_rgb_table (&rgb_table, palette, nullptr); + rgb_map = &rgb_table; + } + 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 < game.numcharacters; ff++) { + if ((game.chars[ff].following >= 0) && (game.chars[ff].room < 0)) { + if ((game.chars[ff].following == game.playercharacter) && + (forchar->prevroom == newnum)) + // the player went back to the previous room, so make sure + // the following character is still there + game.chars[ff].room = newnum; + else + game.chars[ff].room = game.chars[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 (cc=0;cctsdatasize>0) { + if (croom->tsdatasize != roominst->globaldatasize) + quit("room script data segment size has changed"); + memcpy(&roominst->globaldata[0],croom->tsdata,croom->tsdatasize); + } + } + our_eip=207; + play.entered_edge = -1; + + if ((new_room_x != SCR_NO_VALUE) && (forchar != nullptr)) + { + forchar->x = new_room_x; + forchar->y = new_room_y; + + if (new_room_loop != SCR_NO_VALUE) + forchar->loop = new_room_loop; + } + new_room_x = SCR_NO_VALUE; + new_room_loop = SCR_NO_VALUE; + + if ((new_room_pos>0) & (forchar!=nullptr)) { + if (new_room_pos>=4000) { + play.entered_edge = 3; + forchar->y = thisroom.Edges.Top + get_fixed_pixel_size(1); + forchar->x=new_room_pos%1000; + if (forchar->x==0) forchar->x=thisroom.Width/2; + if (forchar->x <= thisroom.Edges.Left) + forchar->x = thisroom.Edges.Left + 3; + if (forchar->x >= thisroom.Edges.Right) + forchar->x = thisroom.Edges.Right - 3; + forchar->loop=0; + } + else if (new_room_pos>=3000) { + play.entered_edge = 2; + forchar->y = thisroom.Edges.Bottom - get_fixed_pixel_size(1); + forchar->x=new_room_pos%1000; + if (forchar->x==0) forchar->x=thisroom.Width/2; + if (forchar->x <= thisroom.Edges.Left) + forchar->x = thisroom.Edges.Left + 3; + if (forchar->x >= thisroom.Edges.Right) + forchar->x = thisroom.Edges.Right - 3; + forchar->loop=3; + } + else if (new_room_pos>=2000) { + play.entered_edge = 1; + forchar->x = thisroom.Edges.Right - get_fixed_pixel_size(1); + forchar->y=new_room_pos%1000; + if (forchar->y==0) forchar->y=thisroom.Height/2; + if (forchar->y <= thisroom.Edges.Top) + forchar->y = thisroom.Edges.Top + 3; + if (forchar->y >= thisroom.Edges.Bottom) + forchar->y = thisroom.Edges.Bottom - 3; + forchar->loop=1; + } + else if (new_room_pos>=1000) { + play.entered_edge = 0; + forchar->x = thisroom.Edges.Left + get_fixed_pixel_size(1); + forchar->y=new_room_pos%1000; + if (forchar->y==0) forchar->y=thisroom.Height/2; + if (forchar->y <= thisroom.Edges.Top) + forchar->y = thisroom.Edges.Top + 3; + if (forchar->y >= thisroom.Edges.Bottom) + forchar->y = thisroom.Edges.Bottom - 3; + forchar->loop=2; + } + // if starts on un-walkable area + if (get_walkable_area_pixel(forchar->x, forchar->y) == 0) { + if (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>thisroom.Edges.Left) { tryleft--; nowhere++; } + if (tryright=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>thisroom.Edges.Top) { tryleft--; nowhere++; } + if (tryrightx; + play.entered_at_y=forchar->y; + if (forchar->x >= thisroom.Edges.Right) + play.entered_edge = 1; + else if (forchar->x <= thisroom.Edges.Left) + play.entered_edge = 0; + else if (forchar->y >= thisroom.Edges.Bottom) + play.entered_edge = 2; + else if (forchar->y <= thisroom.Edges.Top) + play.entered_edge = 3; + } + if (thisroom.Options.StartupMusic>0) + PlayMusicResetQueue(thisroom.Options.StartupMusic); + + our_eip=208; + if (forchar!=nullptr) { + if (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) + play.temporarily_turned_off_character = game.playercharacter; + } + if (forchar->flags & CHF_FIXVIEW) ; + else if (thisroom.Options.PlayerView==0) forchar->view=forchar->defview; + else forchar->view=thisroom.Options.PlayerView-1; + forchar->frame=0; // make him standing + } + color_map = nullptr; + + our_eip = 209; + update_polled_stuff_if_runtime(); + generate_light_table(); + update_music_volume(); + + // If we are not restoring a save, update cameras to accomodate for this + // new room; otherwise this is done later when cameras are recreated. + if (forchar != nullptr) + { + if (play.IsAutoRoomViewport()) + adjust_viewport_to_room(); + update_all_viewcams_with_newroom(); + play.UpdateRoomCameras(); // update auto tracking + } + init_room_drawdata(); + + our_eip = 212; + invalidate_screen(); + for (cc=0;ccnumobj;cc++) { + if (objs[cc].on == 2) + MergeObject(cc); + } + new_room_flags=0; + play.gscript_timer=-1; // avoid screw-ups with changing screens + 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 (game.color_depth > 1) + setpal(); + + our_eip=220; + update_polled_stuff_if_runtime(); + debug_script_log("Now in room %d", displayed_room); + guis_need_update = 1; + pl_run_plugin_hooks(AGSE_ENTERROOM, displayed_room); + // MoveToWalkableArea(game.playercharacter); + // MSS_CHECK_ALL_BLOCKS; +} + +extern int psp_clear_cache_on_room_change; + +// 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 + 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(displayed_room)); + + pl_run_plugin_hooks(AGSE_LEAVEROOM, displayed_room); + + // update the new room number if it has been altered by OnLeave scripts + newnum = in_leaves_screen; + in_leaves_screen = -1; + + if ((playerchar->following >= 0) && + (game.chars[playerchar->following].room != newnum)) { + // the player character is following another character, + // who is not in the new room. therefore, abort the follow + playerchar->following = -1; + } + update_polled_stuff_if_runtime(); + + // change rooms + unload_old_room(); + + if (psp_clear_cache_on_room_change) + { + // Delete all cached sprites + spriteset.DisposeAll(); + + // Delete all gui background images + for (int i = 0; i < game.numgui; i++) + { + delete guibg[i]; + guibg[i] = nullptr; + + if (guibgbmp[i]) + gfxDriver->DestroyDDB(guibgbmp[i]); + guibgbmp[i] = nullptr; + } + guis_need_update = 1; + } + + update_polled_stuff_if_runtime(); + + load_new_room(newnum,forchar); +} + +int find_highest_room_entered() { + int qq,fndas=-1; + for (qq=0;qqbeenhere != 0)) + fndas = qq; + } + // This is actually legal - they might start in room 400 and save + //if (fndas<0) quit("find_highest_room: been in no rooms?"); + return fndas; +} + +void first_room_initialization() { + starting_room = displayed_room; + set_loop_counter(0); + mouse_z_was = mouse_z; +} + +void check_new_room() { + // if they're in a new room, run Player Enters Screen and on_event(ENTER_ROOM) + if ((in_new_room>0) & (in_new_room!=3)) { + EventHappened evh; + evh.type = EV_RUNEVBLOCK; + evh.data1 = EVB_ROOM; + evh.data2 = 0; + evh.data3 = 5; + evh.player=game.playercharacter; + // make sure that any script calls don't re-call enters screen + int newroom_was = in_new_room; + in_new_room = 0; + play.disabled_user_interface ++; + process_event(&evh); + play.disabled_user_interface --; + in_new_room = newroom_was; + // setevent(EV_RUNEVBLOCK,EVB_ROOM,0,5); + } +} + +void compile_room_script() { + ccError = 0; + + roominst = ccInstance::CreateFromScript(thisroom.CompiledScript); + + if ((ccError!=0) || (roominst==nullptr)) { + quitprintf("Unable to create local script: %s", ccErrorString.GetCStr()); + } + + roominstFork = roominst->Fork(); + if (roominstFork == nullptr) + quitprintf("Unable to create forked room instance: %s", ccErrorString.GetCStr()); + + repExecAlways.roomHasFunction = true; + lateRepExecAlways.roomHasFunction = true; + getDialogOptionsDimensionsFunc.roomHasFunction = true; +} + +int bg_just_changed = 0; + +void on_background_frame_change () { + + invalidate_screen(); + mark_current_background_dirty(); + invalidate_cached_walkbehinds(); + + // get the new frame's palette + memcpy (palette, thisroom.BgFrames[play.bg_frame].Palette, sizeof(color) * 256); + + // hi-colour, update the palette. It won't have an immediate effect + // but will be drawn properly when the screen fades in + if (game.color_depth > 1) + setpal(); + + if (in_enters_screen) + return; + + // Don't update the palette if it hasn't changed + if (thisroom.BgFrames[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 (game.color_depth == 1) + bg_just_changed = 1; +} + +void croom_ptr_clear() +{ + croom = nullptr; + objs = nullptr; +} + + +AGS_INLINE int room_to_mask_coord(int coord) +{ + return coord * game.GetDataUpscaleMult() / thisroom.MaskResolution; +} + +AGS_INLINE int mask_to_room_coord(int coord) +{ + return coord * thisroom.MaskResolution / game.GetDataUpscaleMult(); +} + +void convert_move_path_to_room_resolution(MoveList *ml) +{ + if ((game.options[OPT_WALKSPEEDABSOLUTE] != 0) && 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] / game.GetDataUpscaleMult(); + ml->ypermove[i] = ml->ypermove[i] / game.GetDataUpscaleMult(); + } + } + + if (thisroom.MaskResolution == 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 (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 +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// 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, 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, 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); +} + + +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); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Room::GetDrawingSurfaceForBackground^1", (void*)Room_GetDrawingSurfaceForBackground); + ccAddExternalFunctionForPlugin("Room::GetProperty^1", (void*)Room_GetProperty); + ccAddExternalFunctionForPlugin("Room::GetTextProperty^1", (void*)Room_GetTextProperty); + ccAddExternalFunctionForPlugin("Room::get_BottomEdge", (void*)Room_GetBottomEdge); + ccAddExternalFunctionForPlugin("Room::get_ColorDepth", (void*)Room_GetColorDepth); + ccAddExternalFunctionForPlugin("Room::get_Height", (void*)Room_GetHeight); + ccAddExternalFunctionForPlugin("Room::get_LeftEdge", (void*)Room_GetLeftEdge); + ccAddExternalFunctionForPlugin("Room::geti_Messages", (void*)Room_GetMessages); + ccAddExternalFunctionForPlugin("Room::get_MusicOnLoad", (void*)Room_GetMusicOnLoad); + ccAddExternalFunctionForPlugin("Room::get_ObjectCount", (void*)Room_GetObjectCount); + ccAddExternalFunctionForPlugin("Room::get_RightEdge", (void*)Room_GetRightEdge); + ccAddExternalFunctionForPlugin("Room::get_TopEdge", (void*)Room_GetTopEdge); + ccAddExternalFunctionForPlugin("Room::get_Width", (void*)Room_GetWidth); +} diff --git a/engines/ags/engine/ac/room.h b/engines/ags/engine/ac/room.h new file mode 100644 index 00000000000..c915f8f2cbd --- /dev/null +++ b/engines/ags/engine/ac/room.h @@ -0,0 +1,74 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__ROOM_H +#define __AGS_EE_AC__ROOM_H + +#include "ac/dynobj/scriptdrawingsurface.h" +#include "ac/characterinfo.h" +#include "script/runtimescriptvalue.h" +#include "game/roomstruct.h" + +ScriptDrawingSurface* Room_GetDrawingSurfaceForBackground(int backgroundNumber); +ScriptDrawingSurface* Room_GetDrawingSurfaceForMask(RoomAreaMask mask); +int Room_GetObjectCount(); +int Room_GetWidth(); +int Room_GetHeight(); +int Room_GetColorDepth(); +int Room_GetLeftEdge(); +int Room_GetRightEdge(); +int Room_GetTopEdge(); +int Room_GetBottomEdge(); +int Room_GetMusicOnLoad(); +const char* Room_GetTextProperty(const char *property); +int Room_GetProperty(const char *property); +const char* Room_GetMessages(int index); +RuntimeScriptValue Sc_Room_GetProperty(const RuntimeScriptValue *params, int32_t param_count); + +//============================================================================= + +void save_room_data_segment (); +void unload_old_room(); +void load_new_room(int newnum,CharacterInfo*forchar); +void new_room(int newnum,CharacterInfo*forchar); +int find_highest_room_entered(); +void first_room_initialization(); +void check_new_room(); +void compile_room_script(); +void on_background_frame_change (); +// Clear the current room pointer if room status is no longer valid +void croom_ptr_clear(); + +// These functions convert coordinates between data resolution and region mask. +// In hi-res games region masks are 1:2 (or smaller) of the room size. +// In legacy games with low-res data resolution there's additional conversion +// between data and room coordinates. +// +// gets multiplier for converting from room mask to data coordinate +extern AGS_INLINE int get_roommask_to_data_mul(); +// coordinate conversion data ---> room ---> mask +extern AGS_INLINE int room_to_mask_coord(int coord); +// coordinate conversion mask ---> room ---> data +extern AGS_INLINE int mask_to_room_coord(int coord); + +struct MoveList; +// Convert move path from room's mask resolution to room resolution +void convert_move_path_to_room_resolution(MoveList *ml); + +extern AGS::Common::RoomStruct thisroom; + +#endif // __AGS_EE_AC__ROOM_H diff --git a/engines/ags/engine/ac/roomobject.cpp b/engines/ags/engine/ac/roomobject.cpp new file mode 100644 index 00000000000..ccddc1032c7 --- /dev/null +++ b/engines/ags/engine/ac/roomobject.cpp @@ -0,0 +1,166 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/roomobject.h" +#include "ac/common.h" +#include "ac/common_defines.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/runtime_defines.h" +#include "ac/viewframe.h" +#include "main/update.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +extern ViewStruct*views; +extern GameState play; +extern GameSetupStruct game; + +RoomObject::RoomObject() +{ + x = y = 0; + transparent = 0; + tint_r = tint_g = 0; + tint_b = tint_level = 0; + tint_light = 0; + zoom = 0; + last_width = last_height = 0; + num = 0; + baseline = 0; + view = loop = frame = 0; + wait = moving = 0; + cycling = 0; + overall_speed = 0; + on = 0; + flags = 0; + blocking_width = blocking_height = 0; +} + +int RoomObject::get_width() { + if (last_width == 0) + return game.SpriteInfos[num].Width; + return last_width; +} +int RoomObject::get_height() { + if (last_height == 0) + return game.SpriteInfos[num].Height; + return last_height; +} +int RoomObject::get_baseline() { + if (baseline < 1) + return y; + return baseline; +} + +void RoomObject::UpdateCyclingView() +{ + if (on != 1) return; + if (moving>0) { + do_movelist_move(&moving,&x,&y); + } + if (cycling==0) return; + if (view<0) return; + if (wait>0) { wait--; return; } + + if (cycling >= ANIM_BACKWARDS) { + + update_cycle_view_backwards(); + + } + else { // Animate forwards + + update_cycle_view_forwards(); + + } // end if forwards + + ViewFrame*vfptr=&views[view].loops[loop].frames[frame]; + num = vfptr->pic; + + if (cycling == 0) + return; + + wait=vfptr->speed+overall_speed; + CheckViewFrame (view, loop, frame); +} + + +void RoomObject::update_cycle_view_forwards() +{ + frame++; + if (frame >= views[view].loops[loop].numFrames) { + // go to next loop thing + if (views[view].loops[loop].RunNextLoop()) { + if (loop+1 >= views[view].numLoops) + quit("!Last loop in a view requested to move to next loop"); + loop++; + frame=0; + } + else if (cycling % ANIM_BACKWARDS == ANIM_ONCE) { + // leave it on the last frame + cycling=0; + frame--; + } + else { + if (play.no_multiloop_repeat == 0) { + // multi-loop anims, go back to start of it + while ((loop > 0) && + (views[view].loops[loop - 1].RunNextLoop())) + loop --; + } + if (cycling % ANIM_BACKWARDS == ANIM_ONCERESET) + cycling=0; + frame=0; + } + } +} + +void RoomObject::update_cycle_view_backwards() +{ + // animate backwards + frame--; + if (frame < 0) { + if ((loop > 0) && + (views[view].loops[loop - 1].RunNextLoop())) + { + // If it's a Go-to-next-loop on the previous one, then go back + loop --; + frame = views[view].loops[loop].numFrames - 1; + } + else if (cycling % ANIM_BACKWARDS == ANIM_ONCE) { + // leave it on the first frame + cycling = 0; + frame = 0; + } + else { // repeating animation + frame = views[view].loops[loop].numFrames - 1; + } + } +} + +void RoomObject::ReadFromFile(Stream *in) +{ + in->ReadArrayOfInt32(&x, 3); + in->ReadArrayOfInt16(&tint_r, 15); + in->ReadArrayOfInt8((int8_t*)&cycling, 4); + in->ReadArrayOfInt16(&blocking_width, 2); +} + +void RoomObject::WriteToFile(Stream *out) const +{ + out->WriteArrayOfInt32(&x, 3); + out->WriteArrayOfInt16(&tint_r, 15); + out->WriteArrayOfInt8((int8_t*)&cycling, 4); + out->WriteArrayOfInt16(&blocking_width, 2); +} diff --git a/engines/ags/engine/ac/roomobject.h b/engines/ags/engine/ac/roomobject.h new file mode 100644 index 00000000000..ce5dd95134c --- /dev/null +++ b/engines/ags/engine/ac/roomobject.h @@ -0,0 +1,62 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__ROOMOBJECT_H +#define __AGS_EE_AC__ROOMOBJECT_H + +#include "ac/common_defines.h" + +namespace AGS { namespace Common { class Stream; }} +using namespace AGS; // FIXME later + +// IMPORTANT: this struct is restricted by plugin API! +struct RoomObject { + int x,y; + int transparent; // current transparency setting + short tint_r, tint_g; // specific object tint + short tint_b, tint_level; + short tint_light; + short zoom; // zoom level, either manual or from the current area + short last_width, last_height; // width/height last time drawn + short num; // sprite slot number + short baseline; // <=0 to use Y co-ordinate; >0 for specific baseline + short view,loop,frame; // only used to track animation - 'num' holds the current sprite + short wait,moving; + char cycling; // is it currently animating? + char overall_speed; + char on; + char flags; + short blocking_width, blocking_height; + + RoomObject(); + + int get_width(); + int get_height(); + int get_baseline(); + + inline bool has_explicit_light() const { return (flags & OBJF_HASLIGHT) != 0; } + inline bool has_explicit_tint() const { return (flags & OBJF_HASTINT) != 0; } + + void UpdateCyclingView(); + void update_cycle_view_forwards(); + void update_cycle_view_backwards(); + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out) const; +}; + +#endif // __AGS_EE_AC__ROOMOBJECT_H diff --git a/engines/ags/engine/ac/roomstatus.cpp b/engines/ags/engine/ac/roomstatus.cpp new file mode 100644 index 00000000000..baabe5644f0 --- /dev/null +++ b/engines/ags/engine/ac/roomstatus.cpp @@ -0,0 +1,240 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include // memset +#include // free +#include "ac/common.h" +#include "ac/game_version.h" +#include "ac/roomstatus.h" +#include "game/customproperties.h" +#include "game/savegame_components.h" +#include "util/alignedstream.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +RoomStatus::RoomStatus() +{ + beenhere = 0; + numobj = 0; + memset(&flagstates, 0, sizeof(flagstates)); + tsdatasize = 0; + tsdata = nullptr; + + memset(&hotspot_enabled, 0, sizeof(hotspot_enabled)); + memset(®ion_enabled, 0, sizeof(region_enabled)); + memset(&walkbehind_base, 0, sizeof(walkbehind_base)); + memset(&interactionVariableValues, 0, sizeof(interactionVariableValues)); +} + +RoomStatus::~RoomStatus() +{ + if (tsdata) + delete [] tsdata; +} + +void RoomStatus::FreeScriptData() +{ + if (tsdata) + delete [] tsdata; + tsdata = nullptr; + tsdatasize = 0; +} + +void RoomStatus::FreeProperties() +{ + roomProps.clear(); + for (int i = 0; i < MAX_ROOM_HOTSPOTS; ++i) + { + hsProps[i].clear(); + } + for (int i = 0; i < MAX_ROOM_OBJECTS; ++i) + { + objProps[i].clear(); + } +} + +void RoomStatus::ReadFromFile_v321(Stream *in) +{ + beenhere = in->ReadInt32(); + numobj = in->ReadInt32(); + ReadRoomObjects_Aligned(in); + in->ReadArrayOfInt16(flagstates, MAX_FLAGS); + tsdatasize = in->ReadInt32(); + in->ReadInt32(); // tsdata + for (int i = 0; i < MAX_ROOM_HOTSPOTS; ++i) + { + intrHotspot[i].ReadFromSavedgame_v321(in); + } + for (int i = 0; i < MAX_ROOM_OBJECTS; ++i) + { + intrObject[i].ReadFromSavedgame_v321(in); + } + for (int i = 0; i < MAX_ROOM_REGIONS; ++i) + { + intrRegion[i].ReadFromSavedgame_v321(in); + } + intrRoom.ReadFromSavedgame_v321(in); + in->ReadArrayOfInt8((int8_t*)hotspot_enabled, MAX_ROOM_HOTSPOTS); + in->ReadArrayOfInt8((int8_t*)region_enabled, MAX_ROOM_REGIONS); + in->ReadArrayOfInt16(walkbehind_base, MAX_WALK_BEHINDS); + in->ReadArrayOfInt32(interactionVariableValues, MAX_GLOBAL_VARIABLES); + + if (loaded_game_file_version >= kGameVersion_340_4) + { + Properties::ReadValues(roomProps, in); + for (int i = 0; i < MAX_ROOM_HOTSPOTS; ++i) + { + Properties::ReadValues(hsProps[i], in); + } + for (int i = 0; i < MAX_ROOM_OBJECTS; ++i) + { + Properties::ReadValues(objProps[i], in); + } + } +} + +void RoomStatus::ReadRoomObjects_Aligned(Common::Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int i = 0; i < MAX_ROOM_OBJECTS; ++i) + { + obj[i].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +void RoomStatus::ReadFromSavegame(Stream *in) +{ + FreeScriptData(); + FreeProperties(); + + beenhere = in->ReadInt8(); + numobj = in->ReadInt32(); + for (int i = 0; i < numobj; ++i) + { + obj[i].ReadFromFile(in); + Properties::ReadValues(objProps[i], in); + if (loaded_game_file_version <= kGameVersion_272) + SavegameComponents::ReadInteraction272(intrObject[i], in); + } + for (int i = 0; i < MAX_ROOM_HOTSPOTS; ++i) + { + hotspot_enabled[i] = in->ReadInt8(); + Properties::ReadValues(hsProps[i], in); + if (loaded_game_file_version <= kGameVersion_272) + SavegameComponents::ReadInteraction272(intrHotspot[i], in); + } + for (int i = 0; i < MAX_ROOM_REGIONS; ++i) + { + region_enabled[i] = in->ReadInt8(); + if (loaded_game_file_version <= kGameVersion_272) + SavegameComponents::ReadInteraction272(intrRegion[i], in); + } + for (int i = 0; i < MAX_WALK_BEHINDS; ++i) + { + walkbehind_base[i] = in->ReadInt32(); + } + + Properties::ReadValues(roomProps, in); + if (loaded_game_file_version <= kGameVersion_272) + { + SavegameComponents::ReadInteraction272(intrRoom, in); + in->ReadArrayOfInt32(interactionVariableValues, MAX_GLOBAL_VARIABLES); + } + + tsdatasize = in->ReadInt32(); + if (tsdatasize) + { + tsdata = new char[tsdatasize]; + in->Read(tsdata, tsdatasize); + } +} + +void RoomStatus::WriteToSavegame(Stream *out) const +{ + out->WriteInt8(beenhere); + out->WriteInt32(numobj); + for (int i = 0; i < numobj; ++i) + { + obj[i].WriteToFile(out); + Properties::WriteValues(objProps[i], out); + if (loaded_game_file_version <= kGameVersion_272) + SavegameComponents::WriteInteraction272(intrObject[i], out); + } + for (int i = 0; i < MAX_ROOM_HOTSPOTS; ++i) + { + out->WriteInt8(hotspot_enabled[i]); + Properties::WriteValues(hsProps[i], out); + if (loaded_game_file_version <= kGameVersion_272) + SavegameComponents::WriteInteraction272(intrHotspot[i], out); + } + for (int i = 0; i < MAX_ROOM_REGIONS; ++i) + { + out->WriteInt8(region_enabled[i]); + if (loaded_game_file_version <= kGameVersion_272) + SavegameComponents::WriteInteraction272(intrRegion[i], out); + } + for (int i = 0; i < MAX_WALK_BEHINDS; ++i) + { + out->WriteInt32(walkbehind_base[i]); + } + + Properties::WriteValues(roomProps, out); + if (loaded_game_file_version <= kGameVersion_272) + { + SavegameComponents::WriteInteraction272(intrRoom, out); + out->WriteArrayOfInt32(interactionVariableValues, MAX_GLOBAL_VARIABLES); + } + + out->WriteInt32(tsdatasize); + if (tsdatasize) + out->Write(tsdata, tsdatasize); +} + +// JJS: Replacement for the global roomstats array in the original engine. + +RoomStatus* room_statuses[MAX_ROOMS]; + +// Replaces all accesses to the roomstats array +RoomStatus* getRoomStatus(int room) +{ + if (room_statuses[room] == nullptr) + { + // First access, allocate and initialise the status + room_statuses[room] = new RoomStatus(); + } + return room_statuses[room]; +} + +// Used in places where it is only important to know whether the player +// had previously entered the room. In this case it is not necessary +// to initialise the status because a player can only have been in +// a room if the status is already initialised. +bool isRoomStatusValid(int room) +{ + return (room_statuses[room] != nullptr); +} + +void resetRoomStatuses() +{ + for (int i = 0; i < MAX_ROOMS; i++) + { + if (room_statuses[i] != nullptr) + { + delete room_statuses[i]; + room_statuses[i] = nullptr; + } + } +} diff --git a/engines/ags/engine/ac/roomstatus.h b/engines/ags/engine/ac/roomstatus.h new file mode 100644 index 00000000000..9e6eb2dde93 --- /dev/null +++ b/engines/ags/engine/ac/roomstatus.h @@ -0,0 +1,80 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__ROOMSTATUS_H +#define __AGS_EE_AC__ROOMSTATUS_H + +#include "ac/roomobject.h" +#include "game/roomstruct.h" +#include "game/interactions.h" +#include "util/string_types.h" + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using AGS::Common::Stream; +using AGS::Common::Interaction; + +// This struct is saved in the save games - it contains everything about +// a room that could change +struct RoomStatus { + int beenhere; + int numobj; + RoomObject obj[MAX_ROOM_OBJECTS]; + short flagstates[MAX_FLAGS]; + int tsdatasize; + char* tsdata; + Interaction intrHotspot[MAX_ROOM_HOTSPOTS]; + Interaction intrObject [MAX_ROOM_OBJECTS]; + Interaction intrRegion [MAX_ROOM_REGIONS]; + Interaction intrRoom; + + Common::StringIMap roomProps; + Common::StringIMap hsProps[MAX_ROOM_HOTSPOTS]; + Common::StringIMap objProps[MAX_ROOM_OBJECTS]; + // [IKM] 2012-06-22: not used anywhere +#ifdef UNUSED_CODE + EventBlock hscond[MAX_ROOM_HOTSPOTS]; + EventBlock objcond[MAX_ROOM_OBJECTS]; + EventBlock misccond; +#endif + char hotspot_enabled[MAX_ROOM_HOTSPOTS]; + char region_enabled[MAX_ROOM_REGIONS]; + short walkbehind_base[MAX_WALK_BEHINDS]; + int interactionVariableValues[MAX_GLOBAL_VARIABLES]; + + RoomStatus(); + ~RoomStatus(); + + void FreeScriptData(); + void FreeProperties(); + + void ReadFromFile_v321(Common::Stream *in); + void ReadRoomObjects_Aligned(Common::Stream *in); + void ReadFromSavegame(Common::Stream *in); + void WriteToSavegame(Common::Stream *out) const; +}; + +// Replaces all accesses to the roomstats array +RoomStatus* getRoomStatus(int room); +// Used in places where it is only important to know whether the player +// had previously entered the room. In this case it is not necessary +// to initialise the status because a player can only have been in +// a room if the status is already initialised. +bool isRoomStatusValid(int room); +void resetRoomStatuses(); + +#endif // __AGS_EE_AC__ROOMSTATUS_H diff --git a/engines/ags/engine/ac/route_finder.cpp b/engines/ags/engine/ac/route_finder.cpp new file mode 100644 index 00000000000..24663a94e47 --- /dev/null +++ b/engines/ags/engine/ac/route_finder.cpp @@ -0,0 +1,162 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/route_finder.h" + +#include "ac/route_finder_impl.h" +#include "ac/route_finder_impl_legacy.h" + +#include "debug/out.h" + +using AGS::Common::Bitmap; + +class IRouteFinder +{ + public: + virtual void init_pathfinder() = 0; + virtual void shutdown_pathfinder() = 0; + virtual void set_wallscreen(Bitmap *wallscreen) = 0; + virtual int can_see_from(int x1, int y1, int x2, int y2) = 0; + virtual void get_lastcpos(int &lastcx, int &lastcy) = 0; + virtual void set_route_move_speed(int speed_x, int speed_y) = 0; + virtual int find_route(short srcx, short srcy, short xx, short yy, Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0) = 0; + virtual void calculate_move_stage(MoveList * mlsp, int aaa) = 0; +}; + +class AGSRouteFinder : public IRouteFinder +{ + public: + void init_pathfinder() override + { + AGS::Engine::RouteFinder::init_pathfinder(); + } + void shutdown_pathfinder() override + { + AGS::Engine::RouteFinder::shutdown_pathfinder(); + } + void set_wallscreen(Bitmap *wallscreen) override + { + AGS::Engine::RouteFinder::set_wallscreen(wallscreen); + } + int can_see_from(int x1, int y1, int x2, int y2) override + { + return AGS::Engine::RouteFinder::can_see_from(x1, y1, x2, y2); + } + void get_lastcpos(int &lastcx, int &lastcy) override + { + AGS::Engine::RouteFinder::get_lastcpos(lastcx, lastcy); + } + void set_route_move_speed(int speed_x, int speed_y) override + { + AGS::Engine::RouteFinder::set_route_move_speed(speed_x, speed_y); + } + int find_route(short srcx, short srcy, short xx, short yy, Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0) override + { + return AGS::Engine::RouteFinder::find_route(srcx, srcy, xx, yy, onscreen, movlst, nocross, ignore_walls); + } + void calculate_move_stage(MoveList * mlsp, int aaa) override + { + AGS::Engine::RouteFinder::calculate_move_stage(mlsp, aaa); + } +}; + +class AGSLegacyRouteFinder : public IRouteFinder +{ + public: + void init_pathfinder() override + { + AGS::Engine::RouteFinderLegacy::init_pathfinder(); + } + void shutdown_pathfinder() override + { + AGS::Engine::RouteFinderLegacy::shutdown_pathfinder(); + } + void set_wallscreen(Bitmap *wallscreen) override + { + AGS::Engine::RouteFinderLegacy::set_wallscreen(wallscreen); + } + int can_see_from(int x1, int y1, int x2, int y2) override + { + return AGS::Engine::RouteFinderLegacy::can_see_from(x1, y1, x2, y2); + } + void get_lastcpos(int &lastcx, int &lastcy) override + { + AGS::Engine::RouteFinderLegacy::get_lastcpos(lastcx, lastcy); + } + void set_route_move_speed(int speed_x, int speed_y) override + { + AGS::Engine::RouteFinderLegacy::set_route_move_speed(speed_x, speed_y); + } + int find_route(short srcx, short srcy, short xx, short yy, Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0) override + { + return AGS::Engine::RouteFinderLegacy::find_route(srcx, srcy, xx, yy, onscreen, movlst, nocross, ignore_walls); + } + void calculate_move_stage(MoveList * mlsp, int aaa) override + { + AGS::Engine::RouteFinderLegacy::calculate_move_stage(mlsp, aaa); + } +}; + +static IRouteFinder *route_finder_impl = nullptr; + +void init_pathfinder(GameDataVersion game_file_version) +{ + if (game_file_version >= kGameVersion_350) + { + AGS::Common::Debug::Printf(AGS::Common::MessageType::kDbgMsg_Info, "Initialize path finder library"); + route_finder_impl = new AGSRouteFinder(); + } + else + { + AGS::Common::Debug::Printf(AGS::Common::MessageType::kDbgMsg_Info, "Initialize legacy path finder library"); + route_finder_impl = new AGSLegacyRouteFinder(); + } + + route_finder_impl->init_pathfinder(); +} + +void shutdown_pathfinder() +{ + route_finder_impl->shutdown_pathfinder(); +} + +void set_wallscreen(Bitmap *wallscreen) +{ + route_finder_impl->set_wallscreen(wallscreen); +} + +int can_see_from(int x1, int y1, int x2, int y2) +{ + return route_finder_impl->can_see_from(x1, y1, x2, y2); +} + +void get_lastcpos(int &lastcx, int &lastcy) +{ + route_finder_impl->get_lastcpos(lastcx, lastcy); +} + +void set_route_move_speed(int speed_x, int speed_y) +{ + route_finder_impl->set_route_move_speed(speed_x, speed_y); +} + +int find_route(short srcx, short srcy, short xx, short yy, Bitmap *onscreen, int movlst, int nocross, int ignore_walls) +{ + return route_finder_impl->find_route(srcx, srcy, xx, yy, onscreen, movlst, nocross, ignore_walls); +} + +void calculate_move_stage(MoveList * mlsp, int aaa) +{ + route_finder_impl->calculate_move_stage(mlsp, aaa); +} diff --git a/engines/ags/engine/ac/route_finder.h b/engines/ags/engine/ac/route_finder.h new file mode 100644 index 00000000000..fafdd43530c --- /dev/null +++ b/engines/ags/engine/ac/route_finder.h @@ -0,0 +1,37 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_ROUTEFND_H +#define __AC_ROUTEFND_H + +#include "ac/game_version.h" + +// Forward declaration +namespace AGS { namespace Common { class Bitmap; }} +struct MoveList; + +void init_pathfinder(GameDataVersion game_file_version); +void shutdown_pathfinder(); + +void set_wallscreen(AGS::Common::Bitmap *wallscreen); + +int can_see_from(int x1, int y1, int x2, int y2); +void get_lastcpos(int &lastcx, int &lastcy); +// NOTE: pathfinder implementation mostly needs to know proportion between x and y speed +void set_route_move_speed(int speed_x, int speed_y); + +int find_route(short srcx, short srcy, short xx, short yy, AGS::Common::Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0); +void calculate_move_stage(MoveList * mlsp, int aaa); + +#endif // __AC_ROUTEFND_H \ No newline at end of file diff --git a/engines/ags/engine/ac/route_finder_impl.cpp b/engines/ags/engine/ac/route_finder_impl.cpp new file mode 100644 index 00000000000..2e3d71bbee5 --- /dev/null +++ b/engines/ags/engine/ac/route_finder_impl.cpp @@ -0,0 +1,273 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// New jump point search (JPS) A* pathfinder by Martin Sedlak. +// +//============================================================================= + +#include "ac/route_finder_impl.h" + +#include +#include + +#include "ac/common.h" // quit() +#include "ac/movelist.h" // MoveList +#include "ac/common_defines.h" +#include "gfx/bitmap.h" +#include "debug/out.h" + +#include "route_finder_jps.inl" + +extern MoveList *mls; + +using AGS::Common::Bitmap; + +// #define DEBUG_PATHFINDER + +namespace AGS { +namespace Engine { +namespace RouteFinder { + +#define MAKE_INTCOORD(x,y) (((unsigned short)x << 16) | ((unsigned short)y)) + +static const int MAXNAVPOINTS = MAXNEEDSTAGES; +static int navpoints[MAXNAVPOINTS]; +static int num_navpoints; +static fixed move_speed_x, move_speed_y; +static Navigation nav; +static Bitmap *wallscreen; +static int lastcx, lastcy; + +void init_pathfinder() +{ +} + +void shutdown_pathfinder() +{ +} + +void set_wallscreen(Bitmap *wallscreen_) +{ + wallscreen = wallscreen_; +} + +static void sync_nav_wallscreen() +{ + // FIXME: this is dumb, but... + nav.Resize(wallscreen->GetWidth(), wallscreen->GetHeight()); + + for (int y=0; yGetHeight(); y++) + nav.SetMapRow(y, wallscreen->GetScanLine(y)); +} + +int can_see_from(int x1, int y1, int x2, int y2) +{ + lastcx = x1; + lastcy = y1; + + if ((x1 == x2) && (y1 == y2)) + return 1; + + sync_nav_wallscreen(); + + return !nav.TraceLine(x1, y1, x2, y2, lastcx, lastcy); +} + +void get_lastcpos(int &lastcx_, int &lastcy_) +{ + lastcx_ = lastcx; + lastcy_ = lastcy; +} + +// new routing using JPS +static int find_route_jps(int fromx, int fromy, int destx, int desty) +{ + sync_nav_wallscreen(); + + static std::vector path, cpath; + path.clear(); + cpath.clear(); + + if (nav.NavigateRefined(fromx, fromy, destx, desty, path, cpath) == Navigation::NAV_UNREACHABLE) + return 0; + + num_navpoints = 0; + + // new behavior: cut path if too complex rather than abort with error message + int count = std::min((int)cpath.size(), MAXNAVPOINTS); + + for (int i = 0; ipos[aaa] == mlsp->pos[aaa + 1]) { + mlsp->xpermove[aaa] = 0; + mlsp->ypermove[aaa] = 0; + return; + } + + short ourx = (mlsp->pos[aaa] >> 16) & 0x000ffff; + short oury = (mlsp->pos[aaa] & 0x000ffff); + short destx = ((mlsp->pos[aaa + 1] >> 16) & 0x000ffff); + short desty = (mlsp->pos[aaa + 1] & 0x000ffff); + + // Special case for vertical and horizontal movements + if (ourx == destx) { + mlsp->xpermove[aaa] = 0; + mlsp->ypermove[aaa] = move_speed_y; + if (desty < oury) + mlsp->ypermove[aaa] = -mlsp->ypermove[aaa]; + + return; + } + + if (oury == desty) { + mlsp->xpermove[aaa] = move_speed_x; + mlsp->ypermove[aaa] = 0; + if (destx < ourx) + mlsp->xpermove[aaa] = -mlsp->xpermove[aaa]; + + return; + } + + fixed xdist = itofix(abs(ourx - destx)); + fixed ydist = itofix(abs(oury - desty)); + + fixed useMoveSpeed; + + if (move_speed_x == move_speed_y) { + useMoveSpeed = move_speed_x; + } + else { + // different X and Y move speeds + // the X proportion of the movement is (x / (x + y)) + fixed xproportion = fixdiv(xdist, (xdist + ydist)); + + if (move_speed_x > move_speed_y) { + // speed = y + ((1 - xproportion) * (x - y)) + useMoveSpeed = move_speed_y + fixmul(xproportion, move_speed_x - move_speed_y); + } + else { + // speed = x + (xproportion * (y - x)) + useMoveSpeed = move_speed_x + fixmul(itofix(1) - xproportion, move_speed_y - move_speed_x); + } + } + + fixed angl = fixatan(fixdiv(ydist, xdist)); + + // now, since new opp=hyp*sin, work out the Y step size + //fixed newymove = useMoveSpeed * fsin(angl); + fixed newymove = fixmul(useMoveSpeed, fixsin(angl)); + + // since adj=hyp*cos, work out X step size + //fixed newxmove = useMoveSpeed * fcos(angl); + fixed newxmove = fixmul(useMoveSpeed, fixcos(angl)); + + if (destx < ourx) + newxmove = -newxmove; + if (desty < oury) + newymove = -newymove; + + mlsp->xpermove[aaa] = newxmove; + mlsp->ypermove[aaa] = newymove; +} + + +int find_route(short srcx, short srcy, short xx, short yy, Bitmap *onscreen, int movlst, int nocross, int ignore_walls) +{ + int i; + + wallscreen = onscreen; + + num_navpoints = 0; + + if (ignore_walls || can_see_from(srcx, srcy, xx, yy)) + { + num_navpoints = 2; + navpoints[0] = MAKE_INTCOORD(srcx, srcy); + navpoints[1] = MAKE_INTCOORD(xx, yy); + } else { + if ((nocross == 0) && (wallscreen->GetPixel(xx, yy) == 0)) + return 0; // clicked on a wall + + find_route_jps(srcx, srcy, xx, yy); + } + + if (!num_navpoints) + return 0; + + // FIXME: really necessary? + if (num_navpoints == 1) + navpoints[num_navpoints++] = navpoints[0]; + + assert(num_navpoints <= MAXNAVPOINTS); + +#ifdef DEBUG_PATHFINDER + AGS::Common::Debug::Printf("Route from %d,%d to %d,%d - %d stages", srcx,srcy,xx,yy,num_navpoints); +#endif + + int mlist = movlst; + mls[mlist].numstage = num_navpoints; + memcpy(&mls[mlist].pos[0], &navpoints[0], sizeof(int) * num_navpoints); +#ifdef DEBUG_PATHFINDER + AGS::Common::Debug::Printf("stages: %d\n",num_navpoints); +#endif + + for (i=0; i +#include + +#include "ac/common.h" // quit() +#include "ac/common_defines.h" +#include "game/roomstruct.h" +#include "ac/movelist.h" // MoveList +#include "gfx/bitmap.h" +#include "debug/out.h" + +extern void update_polled_stuff_if_runtime(); + +extern MoveList *mls; + +using AGS::Common::Bitmap; +namespace BitmapHelper = AGS::Common::BitmapHelper; + +// #define DEBUG_PATHFINDER + +#ifdef DEBUG_PATHFINDER +// extern Bitmap *mousecurs[10]; +#endif + +namespace AGS { +namespace Engine { +namespace RouteFinderLegacy { + +#define MANOBJNUM 99 + +#define MAXPATHBACK 1000 +static int *pathbackx = nullptr; +static int *pathbacky = nullptr; +static int waspossible = 1; +static int suggestx; +static int suggesty; +static fixed move_speed_x; +static fixed move_speed_y; + +void init_pathfinder() +{ + pathbackx = (int *)malloc(sizeof(int) * MAXPATHBACK); + pathbacky = (int *)malloc(sizeof(int) * MAXPATHBACK); +} + +static Bitmap *wallscreen; + +void set_wallscreen(Bitmap *wallscreen_) +{ + wallscreen = wallscreen_; +} + +static int line_failed = 0; +static int lastcx, lastcy; + +// TODO: find a way to reimpl this with Bitmap +static void line_callback(BITMAP *bmpp, int x, int y, int d) +{ +/* if ((x>=320) | (y>=200) | (x<0) | (y<0)) line_failed=1; + else */ if (getpixel(bmpp, x, y) < 1) + line_failed = 1; + else if (line_failed == 0) { + lastcx = x; + lastcy = y; + } +} + + + +int can_see_from(int x1, int y1, int x2, int y2) +{ + assert(wallscreen != nullptr); + + line_failed = 0; + lastcx = x1; + lastcy = y1; + + if ((x1 == x2) && (y1 == y2)) + return 1; + + // TODO: need some way to use Bitmap with callback + do_line((BITMAP*)wallscreen->GetAllegroBitmap(), x1, y1, x2, y2, 0, line_callback); + if (line_failed == 0) + return 1; + + return 0; +} + +void get_lastcpos(int &lastcx_, int &lastcy_) { + lastcx_ = lastcx; + lastcy_ = lastcy; +} + + +int find_nearest_walkable_area(Bitmap *tempw, int fromX, int fromY, int toX, int toY, int destX, int destY, int granularity) +{ + assert(tempw != nullptr); + + int ex, ey, nearest = 99999, thisis, nearx, neary; + if (fromX < 0) fromX = 0; + if (fromY < 0) fromY = 0; + if (toX >= tempw->GetWidth()) toX = tempw->GetWidth() - 1; + if (toY >= tempw->GetHeight()) toY = tempw->GetHeight() - 1; + + for (ex = fromX; ex < toX; ex += granularity) + { + for (ey = fromY; ey < toY; ey += granularity) + { + if (tempw->GetScanLine(ey)[ex] != 232) + continue; + + thisis = (int)::sqrt((double)((ex - destX) * (ex - destX) + (ey - destY) * (ey - destY))); + if (thisis < nearest) + { + nearest = thisis; + nearx = ex; + neary = ey; + } + } + } + + if (nearest < 90000) { + suggestx = nearx; + suggesty = neary; + return 1; + } + + return 0; +} + +#define MAX_GRANULARITY 3 +static int walk_area_granularity[MAX_WALK_AREAS + 1]; +static int is_route_possible(int fromx, int fromy, int tox, int toy, Bitmap *wss) +{ + wallscreen = wss; + suggestx = -1; + + // ensure it's a memory bitmap, so we can use direct access to line[] array + if ((wss == nullptr) || (!wss->IsMemoryBitmap()) || (wss->GetColorDepth() != 8)) + quit("is_route_possible: invalid walkable areas bitmap supplied"); + + if (wallscreen->GetPixel(fromx, fromy) < 1) + return 0; + + Bitmap *tempw = BitmapHelper::CreateBitmapCopy(wallscreen, 8); + + if (tempw == nullptr) + quit("no memory for route calculation"); + if (!tempw->IsMemoryBitmap()) + quit("tempw is not memory bitmap"); + + int dd, ff; + // initialize array for finding widths of walkable areas + int thisar, inarow = 0, lastarea = 0; + int walk_area_times[MAX_WALK_AREAS + 1]; + for (dd = 0; dd <= MAX_WALK_AREAS; dd++) { + walk_area_times[dd] = 0; + walk_area_granularity[dd] = 0; + } + + for (ff = 0; ff < tempw->GetHeight(); ff++) { + const uint8_t *tempw_scanline = tempw->GetScanLine(ff); + for (dd = 0; dd < tempw->GetWidth(); dd++) { + thisar = tempw_scanline[dd]; + // count how high the area is at this point + if ((thisar == lastarea) && (thisar > 0)) + inarow++; + else if (lastarea > MAX_WALK_AREAS) + quit("!Calculate_Route: invalid colours in walkable area mask"); + else if (lastarea != 0) { + walk_area_granularity[lastarea] += inarow; + walk_area_times[lastarea]++; + inarow = 0; + } + lastarea = thisar; + } + } + + for (dd = 0; dd < tempw->GetWidth(); dd++) { + for (ff = 0; ff < tempw->GetHeight(); ff++) { + uint8_t *tempw_scanline = tempw->GetScanLineForWriting(ff); + thisar = tempw_scanline[dd]; + if (thisar > 0) + tempw_scanline[dd] = 1; + // count how high the area is at this point + if ((thisar == lastarea) && (thisar > 0)) + inarow++; + else if (lastarea != 0) { + walk_area_granularity[lastarea] += inarow; + walk_area_times[lastarea]++; + inarow = 0; + } + lastarea = thisar; + } + } + + // find the average "width" of a path in this walkable area + for (dd = 1; dd <= MAX_WALK_AREAS; dd++) { + if (walk_area_times[dd] == 0) { + walk_area_granularity[dd] = MAX_GRANULARITY; + continue; + } + + walk_area_granularity[dd] /= walk_area_times[dd]; + if (walk_area_granularity[dd] <= 4) + walk_area_granularity[dd] = 2; + else if (walk_area_granularity[dd] <= 15) + walk_area_granularity[dd] = 3; + else + walk_area_granularity[dd] = MAX_GRANULARITY; + +#ifdef DEBUG_PATHFINDER + AGS::Common::Debug::Printf("area %d: Gran %d", dd, walk_area_granularity[dd]); +#endif + } + walk_area_granularity[0] = MAX_GRANULARITY; + + tempw->FloodFill(fromx, fromy, 232); + if (tempw->GetPixel(tox, toy) != 232) + { + // Destination pixel is not walkable + // Try the 100x100 square around the target first at 3-pixel granularity + int tryFirstX = tox - 50, tryToX = tox + 50; + int tryFirstY = toy - 50, tryToY = toy + 50; + + if (!find_nearest_walkable_area(tempw, tryFirstX, tryFirstY, tryToX, tryToY, tox, toy, 3)) + { + // Nothing found, sweep the whole room at 5 pixel granularity + find_nearest_walkable_area(tempw, 0, 0, tempw->GetWidth(), tempw->GetHeight(), tox, toy, 5); + } + + delete tempw; + return 0; + } + delete tempw; + + return 1; +} + +static int leftorright = 0; +static int nesting = 0; +static int pathbackstage = 0; +static int finalpartx = 0; +static int finalparty = 0; +static short **beenhere = nullptr; //[200][320]; +static int beenhere_array_size = 0; +static const int BEENHERE_SIZE = 2; + +#define DIR_LEFT 0 +#define DIR_RIGHT 2 +#define DIR_UP 1 +#define DIR_DOWN 3 + +static int try_this_square(int srcx, int srcy, int tox, int toy) +{ + assert(pathbackx != nullptr); + assert(pathbacky != nullptr); + assert(beenhere != nullptr); + + if (beenhere[srcy][srcx] & 0x80) + return 0; + + // nesting of 8040 leads to stack overflow + if (nesting > 7000) + return 0; + + nesting++; + if (can_see_from(srcx, srcy, tox, toy)) { + finalpartx = srcx; + finalparty = srcy; + nesting--; + pathbackstage = 0; + return 2; + } + +#ifdef DEBUG_PATHFINDER + // wputblock(lastcx, lastcy, mousecurs[C_CROSS], 1); +#endif + + int trydir = DIR_UP; + int xdiff = abs(srcx - tox), ydiff = abs(srcy - toy); + if (ydiff > xdiff) { + if (srcy > toy) + trydir = DIR_UP; + else + trydir = DIR_DOWN; + } else if (srcx > tox) + trydir = DIR_LEFT; + else if (srcx < tox) + trydir = DIR_RIGHT; + + int iterations = 0; + +try_again: + int nextx = srcx, nexty = srcy; + if (trydir == DIR_LEFT) + nextx--; + else if (trydir == DIR_RIGHT) + nextx++; + else if (trydir == DIR_DOWN) + nexty++; + else if (trydir == DIR_UP) + nexty--; + + iterations++; + if (iterations > 5) { +#ifdef DEBUG_PATHFINDER + AGS::Common::Debug::Printf("not found: %d,%d beenhere 0x%X\n",srcx,srcy,beenhere[srcy][srcx]); +#endif + nesting--; + return 0; + } + + if (((nextx < 0) | (nextx >= wallscreen->GetWidth()) | (nexty < 0) | (nexty >= wallscreen->GetHeight())) || + (wallscreen->GetPixel(nextx, nexty) == 0) || ((beenhere[srcy][srcx] & (1 << trydir)) != 0)) { + + if (leftorright == 0) { + trydir++; + if (trydir > 3) + trydir = 0; + } else { + trydir--; + if (trydir < 0) + trydir = 3; + } + goto try_again; + } + beenhere[srcy][srcx] |= (1 << trydir); +// srcx=nextx; srcy=nexty; + beenhere[srcy][srcx] |= 0x80; // being processed + + int retcod = try_this_square(nextx, nexty, tox, toy); + if (retcod == 0) + goto try_again; + + nesting--; + beenhere[srcy][srcx] &= 0x7f; + if (retcod == 2) { + pathbackx[pathbackstage] = srcx; + pathbacky[pathbackstage] = srcy; + pathbackstage++; + if (pathbackstage >= MAXPATHBACK - 1) + return 0; + + return 2; + } + return 1; +} + + +#define CHECK_MIN(cellx, celly) { \ + if (beenhere[celly][cellx] == -1) {\ + adjcount = 0; \ + if ((wallscreen->GetScanLine(celly)[cellx] != 0) && (beenhere[j][i]+modifier <= min)) {\ + if (beenhere[j][i]+modifier < min) { \ + min = beenhere[j][i]+modifier; \ + numfound = 0; } \ + if (numfound < 40) { \ + newcell[numfound] = (celly) * wallscreen->GetWidth() + (cellx);\ + cheapest[numfound] = j * wallscreen->GetWidth() + i;\ + numfound++; \ + }\ + } \ + }} + +#define MAX_TRAIL_LENGTH 5000 + +// Round down the supplied co-ordinates to the area granularity, +// and move a bit if this causes them to become non-walkable +static void round_down_coords(int &tmpx, int &tmpy) +{ + assert(wallscreen != nullptr); + + int startgran = walk_area_granularity[wallscreen->GetPixel(tmpx, tmpy)]; + tmpy = tmpy - tmpy % startgran; + + if (tmpy < 0) + tmpy = 0; + + tmpx = tmpx - tmpx % startgran; + if (tmpx < 0) + tmpx = 0; + + if (wallscreen->GetPixel(tmpx, tmpy) == 0) { + tmpx += startgran; + if ((wallscreen->GetPixel(tmpx, tmpy) == 0) && (tmpy < wallscreen->GetHeight() - startgran)) { + tmpy += startgran; + + if (wallscreen->GetPixel(tmpx, tmpy) == 0) + tmpx -= startgran; + } + } +} + +static int find_route_dijkstra(int fromx, int fromy, int destx, int desty) +{ + int i, j; + + assert(wallscreen != nullptr); + assert(pathbackx != nullptr); + assert(pathbacky != nullptr); + assert(beenhere != nullptr); + + // This algorithm doesn't behave differently the second time, so ignore + if (leftorright == 1) + return 0; + + for (i = 0; i < wallscreen->GetHeight(); i++) + memset(&beenhere[i][0], 0xff, wallscreen->GetWidth() * BEENHERE_SIZE); + + round_down_coords(fromx, fromy); + beenhere[fromy][fromx] = 0; + + int temprd = destx, tempry = desty; + round_down_coords(temprd, tempry); + if ((temprd == fromx) && (tempry == fromy)) { + // already at destination + pathbackstage = 0; + return 1; + } + + int allocsize = int (wallscreen->GetWidth()) * int (wallscreen->GetHeight()) * sizeof(int); + int *parent = (int *)malloc(allocsize); + int min = 999999, cheapest[40], newcell[40], replace[40]; + int *visited = (int *)malloc(MAX_TRAIL_LENGTH * sizeof(int)); + int iteration = 1; + visited[0] = fromy * wallscreen->GetWidth() + fromx; + parent[visited[0]] = -1; + + int granularity = 3, newx = -1, newy, foundAnswer = -1, numreplace; + int changeiter, numfound, adjcount; + int destxlow = destx - MAX_GRANULARITY; + int destylow = desty - MAX_GRANULARITY; + int destxhi = destxlow + MAX_GRANULARITY * 2; + int destyhi = destylow + MAX_GRANULARITY * 2; + int modifier = 0; + int totalfound = 0; + int DIRECTION_BONUS = 0; + + update_polled_stuff_if_runtime(); + + while (foundAnswer < 0) { + min = 29999; + changeiter = iteration; + numfound = 0; + numreplace = 0; + + for (int n = 0; n < iteration; n++) { + if (visited[n] == -1) + continue; + + i = visited[n] % wallscreen->GetWidth(); + j = visited[n] / wallscreen->GetWidth(); + granularity = walk_area_granularity[wallscreen->GetScanLine(j)[i]]; + adjcount = 1; + + if (i >= granularity) { + modifier = (destx < i) ? DIRECTION_BONUS : 0; + CHECK_MIN(i - granularity, j) + } + + if (j >= granularity) { + modifier = (desty < j) ? DIRECTION_BONUS : 0; + CHECK_MIN(i, j - granularity) + } + + if (i < wallscreen->GetWidth() - granularity) { + modifier = (destx > i) ? DIRECTION_BONUS : 0; + CHECK_MIN(i + granularity, j) + } + + if (j < wallscreen->GetHeight() - granularity) { + modifier = (desty > j) ? DIRECTION_BONUS : 0; + CHECK_MIN(i, j + granularity) + } + + // If all the adjacent cells have been done, stop checking this one + if (adjcount) { + if (numreplace < 40) { + visited[numreplace] = -1; + replace[numreplace] = n; + numreplace++; + } + } + } + + if (numfound == 0) { + free(visited); + free(parent); + return 0; + } + + totalfound += numfound; + for (int p = 0; p < numfound; p++) { + newx = newcell[p] % wallscreen->GetWidth(); + newy = newcell[p] / wallscreen->GetWidth(); + beenhere[newy][newx] = beenhere[cheapest[p] / wallscreen->GetWidth()][cheapest[p] % wallscreen->GetWidth()] + 1; +// int wal = walk_area_granularity[->GetPixel(wallscreen, newx, newy)]; +// beenhere[newy - newy%wal][newx - newx%wal] = beenhere[newy][newx]; + parent[newcell[p]] = cheapest[p]; + + // edges of screen pose a problem, so if current and dest are within + // certain distance of the edge, say we've got it + if ((newx >= wallscreen->GetWidth() - MAX_GRANULARITY) && (destx >= wallscreen->GetWidth() - MAX_GRANULARITY)) + newx = destx; + + if ((newy >= wallscreen->GetHeight() - MAX_GRANULARITY) && (desty >= wallscreen->GetHeight() - MAX_GRANULARITY)) + newy = desty; + + // Found the desination, abort loop + if ((newx >= destxlow) && (newx <= destxhi) && (newy >= destylow) + && (newy <= destyhi)) { + foundAnswer = newcell[p]; + break; + } + + if (totalfound >= 1000) { + //Doesn't work cos it can see the destination from the point that's + //not nearest + // every so often, check if we can see the destination + if (can_see_from(newx, newy, destx, desty)) { + DIRECTION_BONUS -= 50; + totalfound = 0; + } + + } + + if (numreplace > 0) { + numreplace--; + changeiter = replace[numreplace]; + } else + changeiter = iteration; + + visited[changeiter] = newcell[p]; + if (changeiter == iteration) + iteration++; + + changeiter = iteration; + if (iteration >= MAX_TRAIL_LENGTH) { + free(visited); + free(parent); + return 0; + } + } + if (totalfound >= 1000) { + update_polled_stuff_if_runtime(); + totalfound = 0; + } + } + free(visited); + + int on; + pathbackstage = 0; + pathbackx[pathbackstage] = destx; + pathbacky[pathbackstage] = desty; + pathbackstage++; + + for (on = parent[foundAnswer];; on = parent[on]) { + if (on == -1) + break; + + newx = on % wallscreen->GetWidth(); + newy = on / wallscreen->GetWidth(); + if ((newx >= destxlow) && (newx <= destxhi) && (newy >= destylow) + && (newy <= destyhi)) + break; + + pathbackx[pathbackstage] = on % wallscreen->GetWidth(); + pathbacky[pathbackstage] = on / wallscreen->GetWidth(); + pathbackstage++; + if (pathbackstage >= MAXPATHBACK) { + free(parent); + return 0; + } + } + free(parent); + return 1; +} + +static int __find_route(int srcx, int srcy, short *tox, short *toy, int noredx) +{ + assert(wallscreen != nullptr); + assert(beenhere != nullptr); + assert(tox != nullptr); + assert(toy != nullptr); + + if ((noredx == 0) && (wallscreen->GetPixel(tox[0], toy[0]) == 0)) + return 0; // clicked on a wall + + pathbackstage = 0; + + if (leftorright == 0) { + waspossible = 1; + +findroutebk: + if ((srcx == tox[0]) && (srcy == toy[0])) { + pathbackstage = 0; + return 1; + } + + if ((waspossible = is_route_possible(srcx, srcy, tox[0], toy[0], wallscreen)) == 0) { + if (suggestx >= 0) { + tox[0] = suggestx; + toy[0] = suggesty; + goto findroutebk; + } + return 0; + } + } + + if (leftorright == 1) { + if (waspossible == 0) + return 0; + } + + // Try the new pathfinding algorithm + if (find_route_dijkstra(srcx, srcy, tox[0], toy[0])) { + return 1; + } + + // if the new pathfinder failed, try the old one + pathbackstage = 0; + memset(&beenhere[0][0], 0, wallscreen->GetWidth() * wallscreen->GetHeight() * BEENHERE_SIZE); + if (try_this_square(srcx, srcy, tox[0], toy[0]) == 0) + return 0; + + return 1; +} + +void set_route_move_speed(int speed_x, int speed_y) +{ + // negative move speeds like -2 get converted to 1/2 + if (speed_x < 0) { + move_speed_x = itofix(1) / (-speed_x); + } + else { + move_speed_x = itofix(speed_x); + } + + if (speed_y < 0) { + move_speed_y = itofix(1) / (-speed_y); + } + else { + move_speed_y = itofix(speed_y); + } +} + +// Calculates the X and Y per game loop, for this stage of the +// movelist +void calculate_move_stage(MoveList * mlsp, int aaa) +{ + assert(mlsp != nullptr); + + // work out the x & y per move. First, opp/adj=tan, so work out the angle + if (mlsp->pos[aaa] == mlsp->pos[aaa + 1]) { + mlsp->xpermove[aaa] = 0; + mlsp->ypermove[aaa] = 0; + return; + } + + short ourx = (mlsp->pos[aaa] >> 16) & 0x000ffff; + short oury = (mlsp->pos[aaa] & 0x000ffff); + short destx = ((mlsp->pos[aaa + 1] >> 16) & 0x000ffff); + short desty = (mlsp->pos[aaa + 1] & 0x000ffff); + + // Special case for vertical and horizontal movements + if (ourx == destx) { + mlsp->xpermove[aaa] = 0; + mlsp->ypermove[aaa] = move_speed_y; + if (desty < oury) + mlsp->ypermove[aaa] = -mlsp->ypermove[aaa]; + + return; + } + + if (oury == desty) { + mlsp->xpermove[aaa] = move_speed_x; + mlsp->ypermove[aaa] = 0; + if (destx < ourx) + mlsp->xpermove[aaa] = -mlsp->xpermove[aaa]; + + return; + } + + fixed xdist = itofix(abs(ourx - destx)); + fixed ydist = itofix(abs(oury - desty)); + + fixed useMoveSpeed; + + if (move_speed_x == move_speed_y) { + useMoveSpeed = move_speed_x; + } + else { + // different X and Y move speeds + // the X proportion of the movement is (x / (x + y)) + fixed xproportion = fixdiv(xdist, (xdist + ydist)); + + if (move_speed_x > move_speed_y) { + // speed = y + ((1 - xproportion) * (x - y)) + useMoveSpeed = move_speed_y + fixmul(xproportion, move_speed_x - move_speed_y); + } + else { + // speed = x + (xproportion * (y - x)) + useMoveSpeed = move_speed_x + fixmul(itofix(1) - xproportion, move_speed_y - move_speed_x); + } + } + + fixed angl = fixatan(fixdiv(ydist, xdist)); + + // now, since new opp=hyp*sin, work out the Y step size + //fixed newymove = useMoveSpeed * fsin(angl); + fixed newymove = fixmul(useMoveSpeed, fixsin(angl)); + + // since adj=hyp*cos, work out X step size + //fixed newxmove = useMoveSpeed * fcos(angl); + fixed newxmove = fixmul(useMoveSpeed, fixcos(angl)); + + if (destx < ourx) + newxmove = -newxmove; + if (desty < oury) + newymove = -newymove; + + mlsp->xpermove[aaa] = newxmove; + mlsp->ypermove[aaa] = newymove; + +#ifdef DEBUG_PATHFINDER + AGS::Common::Debug::Printf("stage %d from %d,%d to %d,%d Xpermove:%X Ypm:%X", aaa, ourx, oury, destx, desty, newxmove, newymove); + // wtextcolor(14); + // wgtprintf((reallyneed[aaa] >> 16) & 0x000ffff, reallyneed[aaa] & 0x000ffff, cbuttfont, "%d", aaa); +#endif +} + + +#define MAKE_INTCOORD(x,y) (((unsigned short)x << 16) | ((unsigned short)y)) + +int find_route(short srcx, short srcy, short xx, short yy, Bitmap *onscreen, int movlst, int nocross, int ignore_walls) +{ + assert(onscreen != nullptr); + assert(mls != nullptr); + assert(pathbackx != nullptr); + assert(pathbacky != nullptr); + +#ifdef DEBUG_PATHFINDER + // __wnormscreen(); +#endif + wallscreen = onscreen; + leftorright = 0; + int aaa; + + if (wallscreen->GetHeight() > beenhere_array_size) + { + beenhere = (short**)realloc(beenhere, sizeof(short*) * wallscreen->GetHeight()); + beenhere_array_size = wallscreen->GetHeight(); + + if (beenhere == nullptr) + quit("insufficient memory to allocate pathfinder beenhere buffer"); + + for (aaa = 0; aaa < wallscreen->GetHeight(); aaa++) + { + beenhere[aaa] = nullptr; + } + } + + int orisrcx = srcx, orisrcy = srcy; + finalpartx = -1; + + if (ignore_walls) { + pathbackstage = 0; + } + else if (can_see_from(srcx, srcy, xx, yy)) { + pathbackstage = 0; + } + else { + beenhere[0] = (short *)malloc((wallscreen->GetWidth()) * (wallscreen->GetHeight()) * BEENHERE_SIZE); + + for (aaa = 1; aaa < wallscreen->GetHeight(); aaa++) + beenhere[aaa] = beenhere[0] + aaa * (wallscreen->GetWidth()); + + if (__find_route(srcx, srcy, &xx, &yy, nocross) == 0) { + leftorright = 1; + if (__find_route(srcx, srcy, &xx, &yy, nocross) == 0) + pathbackstage = -1; + } + free(beenhere[0]); + + for (aaa = 0; aaa < wallscreen->GetHeight(); aaa++) + { + beenhere[aaa] = nullptr; + } + } + + if (pathbackstage >= 0) { + int nearestpos = 0, nearestindx; + int reallyneed[MAXNEEDSTAGES], numstages = 0; + reallyneed[numstages] = MAKE_INTCOORD(srcx,srcy); + numstages++; + nearestindx = -1; + + int lastpbs = pathbackstage; + +stage_again: + nearestpos = 0; + aaa = 1; + // find the furthest point that can be seen from this stage + for (aaa = pathbackstage - 1; aaa >= 0; aaa--) { +#ifdef DEBUG_PATHFINDER + AGS::Common::Debug::Printf("stage %2d: %2d,%2d\n",aaa,pathbackx[aaa],pathbacky[aaa]); +#endif + if (can_see_from(srcx, srcy, pathbackx[aaa], pathbacky[aaa])) { + nearestpos = MAKE_INTCOORD(pathbackx[aaa], pathbacky[aaa]); + nearestindx = aaa; + } + } + + if ((nearestpos == 0) && (can_see_from(srcx, srcy, xx, yy) == 0) && + (srcx >= 0) && (srcy >= 0) && (srcx < wallscreen->GetWidth()) && (srcy < wallscreen->GetHeight()) && (pathbackstage > 0)) { + // If we couldn't see anything, we're stuck in a corner so advance + // to the next square anyway (but only if they're on the screen) + nearestindx = pathbackstage - 1; + nearestpos = MAKE_INTCOORD(pathbackx[nearestindx], pathbacky[nearestindx]); + } + + if (nearestpos > 0) { + reallyneed[numstages] = nearestpos; + numstages++; + if (numstages >= MAXNEEDSTAGES - 1) + quit("too many stages for auto-walk"); + srcx = (nearestpos >> 16) & 0x000ffff; + srcy = nearestpos & 0x000ffff; +#ifdef DEBUG_PATHFINDER + AGS::Common::Debug::Printf("Added: %d, %d pbs:%d",srcx,srcy,pathbackstage); +#endif + lastpbs = pathbackstage; + pathbackstage = nearestindx; + goto stage_again; + } + + if (finalpartx >= 0) { + reallyneed[numstages] = MAKE_INTCOORD(finalpartx, finalparty); + numstages++; + } + + // Make sure the end co-ord is in there + if (reallyneed[numstages - 1] != MAKE_INTCOORD(xx, yy)) { + reallyneed[numstages] = MAKE_INTCOORD(xx, yy); + numstages++; + } + + if ((numstages == 1) && (xx == orisrcx) && (yy == orisrcy)) { + return 0; + } +#ifdef DEBUG_PATHFINDER + AGS::Common::Debug::Printf("Route from %d,%d to %d,%d - %d stage, %d stages", orisrcx,orisrcy,xx,yy,pathbackstage,numstages); +#endif + int mlist = movlst; + mls[mlist].numstage = numstages; + memcpy(&mls[mlist].pos[0], &reallyneed[0], sizeof(int) * numstages); +#ifdef DEBUG_PATHFINDER + AGS::Common::Debug::Printf("stages: %d\n",numstages); +#endif + + for (aaa = 0; aaa < numstages - 1; aaa++) { + calculate_move_stage(&mls[mlist], aaa); + } + + mls[mlist].fromx = orisrcx; + mls[mlist].fromy = orisrcy; + mls[mlist].onstage = 0; + mls[mlist].onpart = 0; + mls[mlist].doneflag = 0; + mls[mlist].lastx = -1; + mls[mlist].lasty = -1; +#ifdef DEBUG_PATHFINDER + // getch(); +#endif + return mlist; + } else { + return 0; + } + +#ifdef DEBUG_PATHFINDER + // __unnormscreen(); +#endif +} + +void shutdown_pathfinder() +{ + if (pathbackx != nullptr) + { + free(pathbackx); + } + if (pathbacky != nullptr) + { + free(pathbacky); + } + if (beenhere != nullptr) + { + if (beenhere[0] != nullptr) + { + free(beenhere[0]); + } + free(beenhere); + } + + pathbackx = nullptr; + pathbacky = nullptr; + beenhere = nullptr; + beenhere_array_size = 0; +} + + + +} // namespace RouteFinderLegacy +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/ac/route_finder_impl_legacy.h b/engines/ags/engine/ac/route_finder_impl_legacy.h new file mode 100644 index 00000000000..490182c2b13 --- /dev/null +++ b/engines/ags/engine/ac/route_finder_impl_legacy.h @@ -0,0 +1,43 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_ROUTE_FINDER_IMPL_LEGACY +#define __AC_ROUTE_FINDER_IMPL_LEGACY + +// Forward declaration +namespace AGS { namespace Common { class Bitmap; }} +struct MoveList; + +namespace AGS { +namespace Engine { +namespace RouteFinderLegacy { + +void init_pathfinder(); +void shutdown_pathfinder(); + +void set_wallscreen(AGS::Common::Bitmap *wallscreen); + +int can_see_from(int x1, int y1, int x2, int y2); +void get_lastcpos(int &lastcx, int &lastcy); + +void set_route_move_speed(int speed_x, int speed_y); + +int find_route(short srcx, short srcy, short xx, short yy, AGS::Common::Bitmap *onscreen, int movlst, int nocross = 0, int ignore_walls = 0); +void calculate_move_stage(MoveList * mlsp, int aaa); + +} // namespace RouteFinderLegacy +} // namespace Engine +} // namespace AGS + +#endif // __AC_ROUTE_FINDER_IMPL_LEGACY \ No newline at end of file diff --git a/engines/ags/engine/ac/route_finder_jps.inl b/engines/ags/engine/ac/route_finder_jps.inl new file mode 100644 index 00000000000..e78db1b5762 --- /dev/null +++ b/engines/ags/engine/ac/route_finder_jps.inl @@ -0,0 +1,921 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// jump point search grid navigation with navpoint refinement +// (c) 2018 Martin Sedlak +// +//============================================================================= + +#include +#include +#include +#include +#include +#include +#include + +// TODO: this could be cleaned up/simplified ... + +// further optimizations possible: +// - forward refinement should use binary search + +class Navigation +{ +public: + Navigation(); + + void Resize(int width, int height); + + enum NavResult + { + // unreachable + NAV_UNREACHABLE, + // straight line exists + NAV_STRAIGHT, + // path used + NAV_PATH + }; + + // ncpath = navpoint-compressed path + // opath = path composed of individual grid elements + NavResult NavigateRefined(int sx, int sy, int ex, int ey, std::vector &opath, + std::vector &ncpath); + + NavResult Navigate(int sx, int sy, int ex, int ey, std::vector &opath); + + bool TraceLine(int srcx, int srcy, int targx, int targy, int &lastValidX, int &lastValidY) const; + bool TraceLine(int srcx, int srcy, int targx, int targy, std::vector *rpath = nullptr) const; + + inline void SetMapRow(int y, const unsigned char *row) {map[y] = row;} + + inline static int PackSquare(int x, int y); + inline static void UnpackSquare(int sq, int &x, int &y); + +private: + // priority queue entry + struct Entry + { + float cost; + int index; + + inline Entry() = default; + + inline Entry(float ncost, int nindex) + : cost(ncost) + , index(nindex) + { + } + + inline bool operator <(const Entry &b) const + { + return cost < b.cost; + } + + inline bool operator >(const Entry &b) const + { + return cost > b.cost; + } + }; + + int mapWidth; + int mapHeight; + std::vector map; + + typedef unsigned short tFrameId; + typedef int tPrev; + + struct NodeInfo + { + // quantized min distance from origin + unsigned short dist; + // frame id (counter to detect new search) + tFrameId frameId; + // previous node index (packed, relative to current node) + tPrev prev; + + inline NodeInfo() + : dist(0) + , frameId(0) + , prev(-1) + { + } + }; + + static const float DIST_SCALE_PACK; + static const float DIST_SCALE_UNPACK; + + std::vector mapNodes; + tFrameId frameId; + + std::priority_queue, std::greater > pq; + + // temporary buffers: + mutable std::vector fpath; + std::vector ncpathIndex; + std::vector rayPath, orayPath; + + // temps for routing towards unreachable areas + int cnode; + int closest; + + // orthogonal only (this should correspond to what AGS is doing) + bool nodiag; + + bool navLock; + + void IncFrameId(); + + // outside map test + inline bool Outside(int x, int y) const; + // stronger inside test + bool Passable(int x, int y) const; + // plain access, unchecked + inline bool Walkable(int x, int y) const; + + void AddPruned(int *buf, int &bcount, int x, int y) const; + bool HasForcedNeighbor(int x, int y, int dx, int dy) const; + int FindJump(int x, int y, int dx, int dy, int ex, int ey); + int FindOrthoJump(int x, int y, int dx, int dy, int ex, int ey); + + // neighbor reachable (nodiag only) + bool Reachable(int x0, int y0, int x1, int y1) const; + + static inline int sign(int n) + { + return n < 0 ? -1 : (n > 0 ? 1 : 0); + } + + static inline int iabs(int n) + { + return n < 0 ? -n : n; + } + + static inline int iclamp(int v, int min, int max) + { + return v < min ? min : (v > max ? max : v); + } + + static inline int ClosestDist(int dx, int dy) + { + return dx*dx + dy*dy; + // Manhattan? + //return iabs(dx) + iabs(dy); + } +}; + +// Navigation + +// scale pack of 2 means we can route up to 32767 units (euclidean distance) from starting point +// this means that the maximum routing bitmap size we can handle is 23169x23169; should be more than enough! +const float Navigation::DIST_SCALE_PACK = 2.0f; +const float Navigation::DIST_SCALE_UNPACK = 1.0f / Navigation::DIST_SCALE_PACK; + +Navigation::Navigation() + : mapWidth(0) + , mapHeight(0) + , frameId(1) + , cnode(0) + , closest(0) + // no diagonal route - this should correspond to what AGS does + , nodiag(true) + , navLock(false) +{ +} + +void Navigation::Resize(int width, int height) +{ + mapWidth = width; + mapHeight = height; + + int size = mapWidth*mapHeight; + + map.resize(mapHeight); + mapNodes.resize(size); +} + +void Navigation::IncFrameId() +{ + if (++frameId == 0) + { + for (int i=0; i<(int)mapNodes.size(); i++) + mapNodes[i].frameId = 0; + + frameId = 1; + } +} + +inline int Navigation::PackSquare(int x, int y) +{ + return (y << 16) + x; +} + +inline void Navigation::UnpackSquare(int sq, int &x, int &y) +{ + y = sq >> 16; + x = sq & ((1 << 16)-1); +} + +inline bool Navigation::Outside(int x, int y) const +{ + return + (unsigned)x >= (unsigned)mapWidth || + (unsigned)y >= (unsigned)mapHeight; +} + +inline bool Navigation::Walkable(int x, int y) const +{ + // invert condition because of AGS + return map[y][x] != 0; +} + +bool Navigation::Passable(int x, int y) const +{ + return !Outside(x, y) && Walkable(x, y); +} + +bool Navigation::Reachable(int x0, int y0, int x1, int y1) const +{ + assert(nodiag); + + return Passable(x1, y1) && + (Passable(x1, y0) || Passable(x0, y1)); +} + +// A* using jump point search (JPS) +// reference: http://users.cecs.anu.edu.au/~dharabor/data/papers/harabor-grastien-aaai11.pdf +void Navigation::AddPruned(int *buf, int &bcount, int x, int y) const +{ + assert(buf && bcount < 8); + + if (Passable(x, y)) + buf[bcount++] = PackSquare(x, y); +} + +bool Navigation::HasForcedNeighbor(int x, int y, int dx, int dy) const +{ + if (!dy) + { + return (!Passable(x, y-1) && Passable(x+dx, y-1)) || + (!Passable(x, y+1) && Passable(x+dx, y+1)); + } + + if (!dx) + { + return (!Passable(x-1, y) && Passable(x-1, y+dy)) || + (!Passable(x+1, y) && Passable(x+1, y+dy)); + } + + return + (!Passable(x - dx, y) && Passable(x - dx, y + dy)) || + (!Passable(x, y - dy) && Passable(x + dx, y - dy)); +} + +int Navigation::FindOrthoJump(int x, int y, int dx, int dy, int ex, int ey) +{ + assert((!dx || !dy) && (dx || dy)); + + for (;;) + { + x += dx; + y += dy; + + if (!Passable(x, y)) + break; + + int edx = x - ex; + int edy = y - ey; + int edist = ClosestDist(edx, edy); + + if (edist < closest) + { + closest = edist; + cnode = PackSquare(x, y); + } + + if ((x == ex && y == ey) || HasForcedNeighbor(x, y, dx, dy)) + return PackSquare(x, y); + } + + return -1; +} + +int Navigation::FindJump(int x, int y, int dx, int dy, int ex, int ey) +{ + if (!(dx && dy)) + return FindOrthoJump(x, y, dx, dy, ex, ey); + + if (nodiag && !Reachable(x, y, x+dx, y+dy)) + return -1; + + x += dx; + y += dy; + + if (!Passable(x, y)) + return -1; + + int edx = x - ex; + int edy = y - ey; + int edist = ClosestDist(edx, edy); + + if (edist < closest) + { + closest = edist; + cnode = PackSquare(x, y); + } + + if ((x == ex && y == ey) || HasForcedNeighbor(x, y, dx, dy)) + return PackSquare(x, y); + + if (dx && dy) + { + if (FindOrthoJump(x, y, dx, 0, ex, ey) || + FindOrthoJump(x, y, 0, dy, ex, ey)) + return PackSquare(x, y); + } + + return nodiag ? -1 : FindJump(x, y, dx, dy, ex, ey); +} + +Navigation::NavResult Navigation::Navigate(int sx, int sy, int ex, int ey, std::vector &opath) +{ + IncFrameId(); + + if (!Passable(sx, sy)) + { + opath.clear(); + return NAV_UNREACHABLE; + } + + // try ray first, if reachable, no need for A* at all + if (!TraceLine(sx, sy, ex, ey, &opath)) + return NAV_STRAIGHT; + + NodeInfo &ni = mapNodes[sy*mapWidth+sx]; + ni.dist = 0; + ni.frameId = frameId; + ni.prev = -1; + + closest = 0x7fffffff; + cnode = PackSquare(sx, sy); + + // no clear for priority queue, like, really?! + while (!pq.empty()) + pq.pop(); + + pq.push(Entry(0.0, cnode)); + + while (!pq.empty()) + { + Entry e = pq.top(); + pq.pop(); + + int x, y; + UnpackSquare(e.index, x, y); + + int dx = x - ex; + int dy = y - ey; + int edist = ClosestDist(dx, dy); + + if (edist < closest) + { + closest = edist; + cnode = e.index; + } + + if (x == ex && y == ey) + { + // done + break; + } + + const NodeInfo &node = mapNodes[y*mapWidth+x]; + + float dist = node.dist * DIST_SCALE_UNPACK; + + int pneig[8]; + int ncount = 0; + + int prev = node.prev; + + if (prev < 0) + { + for (int ny = y-1; ny <= y+1; ny++) + { + if ((unsigned)ny >= (unsigned)mapHeight) + continue; + + for (int nx = x-1; nx <= x+1; nx++) + { + if (nx == x && ny == y) + continue; + + if ((unsigned)nx >= (unsigned)mapWidth) + continue; + + if (!Walkable(nx, ny)) + continue; + + if (nodiag && !Reachable(x, y, nx, ny)) + continue; + + pneig[ncount++] = PackSquare(nx, ny); + } + } + } + else + { + // filter + int px, py; + UnpackSquare(prev, px, py); + int dx = sign(x - px); + int dy = sign(y - py); + assert(dx || dy); + + if (!dy) + { + AddPruned(pneig, ncount, x+dx, y); + + // add corners + if (!nodiag || Passable(x+dx, y)) + { + if (!Passable(x, y+1)) + AddPruned(pneig, ncount, x+dx, y+1); + + if (!Passable(x, y-1)) + AddPruned(pneig, ncount, x+dx, y-1); + } + } + else if (!dx) + { + // same as above but transposed + AddPruned(pneig, ncount, x, y+dy); + + // add corners + if (!nodiag || Passable(x, y+dy)) + { + if (!Passable(x+1, y)) + AddPruned(pneig, ncount, x+1, y+dy); + + if (!Passable(x-1, y)) + AddPruned(pneig, ncount, x-1, y+dy); + } + } + else + { + // diagonal case + AddPruned(pneig, ncount, x, y+dy); + AddPruned(pneig, ncount, x+dx, y); + + if (!nodiag || Reachable(x, y, x+dx, y+dy)) + AddPruned(pneig, ncount, x+dx, y+dy); + + if (!Passable(x - dx, y) && + (nodiag || Reachable(x, y, x-dx, y+dy))) + AddPruned(pneig, ncount, x-dx, y+dy); + + if (!Passable(x, y-dy) && + (nodiag || Reachable(x, y, x+dx, y-dy))) + AddPruned(pneig, ncount, x+dx, y-dy); + } + } + + // sort by heuristics + Entry sort[8]; + + for (int ni = 0; ni < ncount; ni++) + { + int nx, ny; + UnpackSquare(pneig[ni], nx, ny); + float edx = (float)(nx - ex); + float edy = (float)(ny - ey); + sort[ni].cost = sqrt(edx*edx + edy*edy); + sort[ni].index = pneig[ni]; + } + + std::sort(sort, sort+ncount); + + int succ[8]; + int nsucc = 0; + + for (int ni=0; ni 65535.0f) + continue; + + node.dist = (unsigned short)(ecost + 0.5f); + node.frameId = frameId; + node.prev = PackSquare(x, y); + pq.push(Entry(ecost + heur, PackSquare(nx, ny))); + } + } + } + + opath.clear(); + + // now since we allow approx routing even if dst + // isn't directly reachable + // note: not sure if this provides optimal results even if we update + // cnode during jump search + int nex, ney; + UnpackSquare(cnode, nex, ney); + + if ((nex != sx || ney != sy) && (nex != ex || ney != ey)) + { + // target not directly reachable => move closer to target + TraceLine(nex, ney, ex, ey, &opath); + UnpackSquare(opath.back(), nex, ney); + + NavResult res = NAV_PATH; + + // note: navLock => better safe than sorry + // infinite recursion should never happen but... better safe than sorry + assert(!navLock); + + if (!navLock) + { + // and re-route + opath.clear(); + + navLock = true; + res = Navigate(sx, sy, nex, ney, opath); + navLock = false; + } + + // refine this a bit further; find path point closest + // to original target and truncate + + int best = 0x7fffffff; + int bestSize = (int)opath.size(); + + for (int i=0; i<(int)opath.size(); i++) + { + int x, y; + UnpackSquare(opath[i], x, y); + int dx = x-ex, dy = y-ey; + int cost = ClosestDist(dx, dy); + + if (cost < best) + { + best = cost; + bestSize = i+1; + } + } + + opath.resize(bestSize); + + return res; + } + + if (ex < 0 || ex >= mapWidth || ey < 0 || ey >= mapHeight || + mapNodes[ey*mapWidth+ex].frameId != frameId) + { + // path not found + return NAV_UNREACHABLE; + } + + int tx = ex; + int ty = ey; + // add end + opath.push_back(PackSquare(tx, ty)); + + for (;;) + { + int prev = mapNodes[ty*mapWidth+tx].prev; + + if (prev < 0) + break; + + // unpack because we use JPS + int px, py; + UnpackSquare(prev, px, py); + int dx = sign(px - tx); + int dy = sign(py - ty); + + while (tx != px || ty != py) + { + tx += dx; + ty += dy; + opath.push_back(PackSquare(tx, ty)); + } + } + + std::reverse(opath.begin(), opath.end()); + return NAV_PATH; +} + +Navigation::NavResult Navigation::NavigateRefined(int sx, int sy, int ex, int ey, + std::vector &opath, std::vector &ncpath) +{ + ncpath.clear(); + + NavResult res = Navigate(sx, sy, ex, ey, opath); + + if (res != NAV_PATH) + { + if (res == NAV_STRAIGHT) + { + ncpath.push_back(opath[0]); + ncpath.push_back(opath.back()); + } + + return res; + } + + int fx = sx; + int fy = sy; + + fpath.clear(); + ncpathIndex.clear(); + + fpath.reserve(opath.size()); + fpath.push_back(opath[0]); + ncpath.push_back(opath[0]); + ncpathIndex.push_back(0); + + rayPath.clear(); + orayPath.clear(); + + rayPath.reserve(opath.size()); + orayPath.reserve(opath.size()); + + for (int i=1; i<(int)opath.size(); i++) + { + // trying to optimize path + int tx, ty; + UnpackSquare(opath[i], tx, ty); + + bool last = i == (int)opath.size()-1; + + if (!TraceLine(fx, fy, tx, ty, &rayPath)) + { + assert(rayPath.back() == opath[i]); + std::swap(rayPath, orayPath); + + if (!last) + continue; + } + + // copy orayPath + for (int j=1; j<(int)orayPath.size(); j++) + fpath.push_back(orayPath[j]); + + if (!orayPath.empty()) + { + assert(ncpath.back() == orayPath[0]); + ncpath.push_back(orayPath.back()); + ncpathIndex.push_back((int)fpath.size()-1); + + if (!last) + { + UnpackSquare(orayPath.back(), fx, fy); + orayPath.clear(); + i--; + continue; + } + } + + if (fpath.back() != opath[i]) + fpath.push_back(opath[i]); + + if (ncpath.back() != opath[i]) + { + ncpath.push_back(opath[i]); + ncpathIndex.push_back((int)fpath.size()-1); + } + + fx = tx; + fy = ty; + } + + std::swap(opath, fpath); + + // validate cpath + for (int i=0; i<(int)ncpath.size()-1; i++) + { + int tx, ty; + UnpackSquare(ncpath[i], fx, fy); + UnpackSquare(ncpath[i+1], tx, ty); + assert(!TraceLine(fx, fy, tx, ty, &rayPath)); + } + + assert(ncpath.size() == ncpathIndex.size()); + + // so now we have opath, ncpath and ncpathIndex + // we want to gradually move ncpath node towards previous to see + // if we can raycast from prev ncpath node to moved and from moved + // to the end + + bool adjusted = false; + + for (int i=(int)ncpath.size()-2; i>0; i--) + { + int px, py; + int nx, ny; + + int pidx = ncpathIndex[i-1]; + int idx = ncpathIndex[i]; + + UnpackSquare(ncpath[i-1], px, py); + UnpackSquare(ncpath[i+1], nx, ny); + + for (int j=idx-1; j >= pidx; j--) + { + int x, y; + UnpackSquare(opath[j], x, y); + + // if we can raycast px,py => x,y and x,y => nx,ny, + // we can move ncPath node! + if (TraceLine(px, py, x, y)) + continue; + + if (TraceLine(x, y, nx, ny)) + continue; + + ncpath[i] = opath[j]; + ncpathIndex[i] = j; + adjusted = true; + } + + if (ncpath[i] == ncpath[i-1]) + { + // if we get here, we need to remove ncpath[i] + // because we reached the previous node + ncpath.erase(ncpath.begin()+i); + ncpathIndex.erase(ncpathIndex.begin()+i); + adjusted = true; + } + } + + if (!adjusted) + return NAV_PATH; + + // final step (if necessary) is to reconstruct path from compressed path + + opath.clear(); + opath.push_back(ncpath[0]); + + for (int i=1; i<(int)ncpath.size(); i++) + { + int fx, fy; + int tx, ty; + + UnpackSquare(ncpath[i-1], fx, fy); + UnpackSquare(ncpath[i], tx, ty); + + TraceLine(fx, fy, tx, ty, &rayPath); + + for (int j=1; j<(int)rayPath.size(); j++) + opath.push_back(rayPath[j]); + } + + return NAV_PATH; +} + +bool Navigation::TraceLine(int srcx, int srcy, int targx, int targy, int &lastValidX, int &lastValidY) const +{ + lastValidX = srcx; + lastValidY = srcy; + + bool res = TraceLine(srcx, srcy, targx, targy, &fpath); + + if (!fpath.empty()) + UnpackSquare(fpath.back(), lastValidX, lastValidY); + + return res; +} + +bool Navigation::TraceLine(int srcx, int srcy, int targx, int targy, std::vector *rpath) const +{ + if (rpath) + rpath->clear(); + + // DDA + int x0 = (srcx << 16) + 0x8000; + int y0 = (srcy << 16) + 0x8000; + int x1 = (targx << 16) + 0x8000; + int y1 = (targy << 16) + 0x8000; + + int dx = x1 - x0; + int dy = y1 - y0; + + if (!dx && !dy) + { + if (!Passable(srcx, srcy)) + return true; + + if (rpath) + rpath->push_back(PackSquare(srcx, srcy)); + + return false; + } + + int xinc, yinc; + + if (iabs(dx) >= iabs(dy)) + { + // step along x + xinc = sign(dx) * 65536; + yinc = (int)((double)dy * 65536 / iabs(dx)); + } + else + { + // step along y + yinc = sign(dy) * 65536; + xinc = (int)((double)dx * 65536 / iabs(dy)); + } + + int fx = x0; + int fy = y0; + int x = x0 >> 16; + int y = y0 >> 16; + int ex = x1 >> 16; + int ey = y1 >> 16; + + while (x != ex || y != ey) + { + if (!Passable(x, y)) + return true; + + if (rpath) + rpath->push_back(PackSquare(x, y)); + + fx += xinc; + fy += yinc; + int ox = x; + int oy = y; + x = fx >> 16; + y = fy >> 16; + + if (nodiag && !Reachable(ox, oy, x, y)) + return true; + } + + assert(iabs(x - ex) <= 1 && iabs(y - ey) <= 1); + + if (nodiag && !Reachable(x, y, ex, ey)) + return false; + + if (!Passable(ex, ey)) + return true; + + int sq = PackSquare(ex, ey); + + if (rpath && (rpath->empty() || rpath->back() != sq)) + rpath->push_back(sq); + + return false; +} diff --git a/engines/ags/engine/ac/runtime_defines.h b/engines/ags/engine/ac/runtime_defines.h new file mode 100644 index 00000000000..d868a0f3a9c --- /dev/null +++ b/engines/ags/engine/ac/runtime_defines.h @@ -0,0 +1,153 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_RUNTIMEDEFINES_H +#define __AC_RUNTIMEDEFINES_H + +// xalleg.h pulls in an Allegro-internal definition of MAX_TIMERS which +// conflicts with the definition in runtime_defines.h. Forget it. +#ifdef MAX_TIMERS +#undef MAX_TIMERS +#endif + +// Max script string length +#define MAX_MAXSTRLEN 200 +#define MAXGLOBALVARS 50 + +#define INVALID_X 30000 +#define MAXGSVALUES 500 +#define MAXGLOBALSTRINGS 51 +#define MAX_INVORDER 500 +#define DIALOG_NONE 0 +#define DIALOG_RUNNING 1 +#define DIALOG_STOP 2 +#define DIALOG_NEWROOM 100 +#define DIALOG_NEWTOPIC 12000 +#define MAX_TIMERS 21 +#define MAX_PARSED_WORDS 15 +#define MAXSAVEGAMES 50 +#define MAX_QUEUED_MUSIC 10 +#define GLED_INTERACTION 1 +#define GLED_EFFECTS 2 +#define QUEUED_MUSIC_REPEAT 10000 +#define PLAYMP3FILE_MAX_FILENAME_LEN 50 +#define MAX_AUDIO_TYPES 30 + +// Legacy (pre 3.5.0) alignment types used in the script API +enum LegacyScriptAlignment +{ + kLegacyScAlignLeft = 1, + kLegacyScAlignCentre = 2, + kLegacyScAlignRight = 3 +}; + +const int LegacyMusicMasterVolumeAdjustment = 60; +const int LegacyRoomVolumeFactor = 30; + +// These numbers were chosen arbitrarily -- the idea is +// to make sure that the user gets the parameters the right way round +#define ANYWHERE 304 +#define WALKABLE_AREAS 305 +#define BLOCKING 919 +#define IN_BACKGROUND 920 +#define FORWARDS 1062 +#define BACKWARDS 1063 +#define STOP_MOVING 1 +#define KEEP_MOVING 0 + +#define SCR_NO_VALUE 31998 +#define SCR_COLOR_TRANSPARENT -1 + + + +#define NUM_DIGI_VOICES 16 +#define NUM_MOD_DIGI_VOICES 12 + +#define DEBUG_CONSOLE_NUMLINES 6 +#define TXT_SCOREBAR 29 +#define MAXSCORE play.totalscore +#define CHANIM_REPEAT 2 +#define CHANIM_BACKWARDS 4 +#define ANIM_BACKWARDS 10 +#define ANIM_ONCE 1 +#define ANIM_REPEAT 2 +#define ANIM_ONCERESET 3 +#define FONT_STATUSBAR 0 +#define FONT_NORMAL play.normal_font +//#define FONT_SPEECHBACK 1 +#define FONT_SPEECH play.speech_font +#define MODE_WALK 0 +#define MODE_LOOK 1 +#define MODE_HAND 2 +#define MODE_TALK 3 +#define MODE_USE 4 +#define MODE_PICKUP 5 +#define CURS_ARROW 6 +#define CURS_WAIT 7 +#define MODE_CUSTOM1 8 +#define MODE_CUSTOM2 9 + +#define OVER_TEXTMSG 1 +#define OVER_COMPLETE 2 +#define OVER_PICTURE 3 +#define OVER_CUSTOM 100 +#define OVR_AUTOPLACE 30000 +#define FOR_ANIMATION 1 +#define FOR_SCRIPT 2 +#define FOR_EXITLOOP 3 +#define CHMLSOFFS (MAX_ROOM_OBJECTS+1) // reserve this many movelists for objects & stuff +#define abort_all_conditions restrict_until +#define MAX_SCRIPT_AT_ONCE 10 +#define EVENT_NONE 0 +#define EVENT_INPROGRESS 1 +#define EVENT_CLAIMED 2 + +// Internal skip style flags, for speech/display, wait +#define SKIP_NONE 0 +#define SKIP_AUTOTIMER 1 +#define SKIP_KEYPRESS 2 +#define SKIP_MOUSECLICK 4 + +#define MANOBJNUM 99 + +#define STD_BUFFER_SIZE 3000 + +#define TURNING_AROUND 1000 +#define TURNING_BACKWARDS 10000 + +#define MAX_PLUGIN_OBJECT_READERS 50 + +#ifndef MAX_PATH +#define MAX_PATH 260 +#endif + +#define TRANS_ALPHA_CHANNEL 20000 +#define TRANS_OPAQUE 20001 +#define TRANS_RUN_PLUGIN 20002 + + +#define LOCTYPE_HOTSPOT 1 +#define LOCTYPE_CHAR 2 +#define LOCTYPE_OBJ 3 + +#define MAX_DYNAMIC_SURFACES 20 + +#define MAX_ANIMATING_BUTTONS 15 +#define RESTART_POINT_SAVE_GAME_NUMBER 999 + +#define MAX_OPEN_SCRIPT_FILES 10 + +#include "ac/common_defines.h" + +#endif // __AC_RUNTIMEDEFINES_H diff --git a/engines/ags/engine/ac/screen.cpp b/engines/ags/engine/ac/screen.cpp new file mode 100644 index 00000000000..c28ea7f9a5b --- /dev/null +++ b/engines/ags/engine/ac/screen.cpp @@ -0,0 +1,229 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_game.h" +#include "ac/global_screen.h" +#include "ac/screen.h" +#include "ac/dynobj/scriptviewport.h" +#include "ac/dynobj/scriptuserobject.h" +#include "script/script_runtime.h" +#include "platform/base/agsplatformdriver.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "gfx/bitmap.h" +#include "gfx/graphicsdriver.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern GameState play; +extern IGraphicsDriver *gfxDriver; +extern AGSPlatformDriver *platform; + +void my_fade_in(PALETTE p, int speed) { + if (game.color_depth > 1) { + set_palette (p); + + play.screen_is_faded_out = 0; + + if (play.no_hicolor_fadein) { + return; + } + } + + gfxDriver->FadeIn(speed, p, play.fade_to_red, play.fade_to_green, play.fade_to_blue); +} + +Bitmap *saved_viewport_bitmap = nullptr; +color old_palette[256]; +void current_fade_out_effect () { + if (pl_run_plugin_hooks(AGSE_TRANSITIONOUT, 0)) + return; + + // get the screen transition type + int theTransition = play.fade_effect; + // was a temporary transition selected? if so, use it + if (play.next_screen_transition >= 0) + theTransition = play.next_screen_transition; + const bool ignore_transition = play.screen_tint > 0; + + if ((theTransition == FADE_INSTANT) || ignore_transition) { + if (!play.keep_screen_during_instant_transition) + set_palette_range(black_palette, 0, 255, 0); + } + else if (theTransition == FADE_NORMAL) + { + my_fade_out(5); + } + else if (theTransition == FADE_BOXOUT) + { + gfxDriver->BoxOutEffect(true, get_fixed_pixel_size(16), 1000 / GetGameSpeed()); + play.screen_is_faded_out = 1; + } + else + { + get_palette(old_palette); + const Rect &viewport = play.GetMainViewport(); + saved_viewport_bitmap = CopyScreenIntoBitmap(viewport.GetWidth(), viewport.GetHeight()); + } +} + +IDriverDependantBitmap* prepare_screen_for_transition_in() +{ + if (saved_viewport_bitmap == nullptr) + quit("Crossfade: buffer is null attempting transition"); + + saved_viewport_bitmap = ReplaceBitmapWithSupportedFormat(saved_viewport_bitmap); + const Rect &viewport = play.GetMainViewport(); + if (saved_viewport_bitmap->GetHeight() < viewport.GetHeight()) + { + Bitmap *enlargedBuffer = BitmapHelper::CreateBitmap(saved_viewport_bitmap->GetWidth(), viewport.GetHeight(), saved_viewport_bitmap->GetColorDepth()); + enlargedBuffer->Blit(saved_viewport_bitmap, 0, 0, 0, (viewport.GetHeight() - saved_viewport_bitmap->GetHeight()) / 2, saved_viewport_bitmap->GetWidth(), saved_viewport_bitmap->GetHeight()); + delete saved_viewport_bitmap; + saved_viewport_bitmap = enlargedBuffer; + } + else if (saved_viewport_bitmap->GetHeight() > viewport.GetHeight()) + { + Bitmap *clippedBuffer = BitmapHelper::CreateBitmap(saved_viewport_bitmap->GetWidth(), viewport.GetHeight(), saved_viewport_bitmap->GetColorDepth()); + clippedBuffer->Blit(saved_viewport_bitmap, 0, (saved_viewport_bitmap->GetHeight() - viewport.GetHeight()) / 2, 0, 0, saved_viewport_bitmap->GetWidth(), saved_viewport_bitmap->GetHeight()); + delete saved_viewport_bitmap; + saved_viewport_bitmap = clippedBuffer; + } + IDriverDependantBitmap *ddb = gfxDriver->CreateDDBFromBitmap(saved_viewport_bitmap, false); + return ddb; +} + +//============================================================================= +// +// Screen script API. +// +//============================================================================= + +int Screen_GetScreenWidth() +{ + return game.GetGameRes().Width; +} + +int Screen_GetScreenHeight() +{ + return game.GetGameRes().Height; +} + +bool Screen_GetAutoSizeViewport() +{ + return play.IsAutoRoomViewport(); +} + +void Screen_SetAutoSizeViewport(bool on) +{ + play.SetAutoRoomViewport(on); +} + +ScriptViewport* Screen_GetViewport() +{ + return play.GetScriptViewport(0); +} + +int Screen_GetViewportCount() +{ + return play.GetRoomViewportCount(); +} + +ScriptViewport* Screen_GetAnyViewport(int index) +{ + return play.GetScriptViewport(index); +} + +ScriptUserObject* Screen_ScreenToRoomPoint(int scrx, int scry) +{ + data_to_game_coords(&scrx, &scry); + + VpPoint vpt = play.ScreenToRoom(scrx, scry); + if (vpt.second < 0) + return nullptr; + + game_to_data_coords(vpt.first.X, vpt.first.Y); + return ScriptStructHelpers::CreatePoint(vpt.first.X, vpt.first.Y); +} + +ScriptUserObject *Screen_RoomToScreenPoint(int roomx, int roomy) +{ + data_to_game_coords(&roomx, &roomy); + Point pt = play.RoomToScreen(roomx, roomy); + game_to_data_coords(pt.X, pt.Y); + return ScriptStructHelpers::CreatePoint(pt.X, pt.Y); +} + +RuntimeScriptValue Sc_Screen_GetScreenHeight(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(Screen_GetScreenHeight); +} + +RuntimeScriptValue Sc_Screen_GetScreenWidth(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(Screen_GetScreenWidth); +} + +RuntimeScriptValue Sc_Screen_GetAutoSizeViewport(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_BOOL(Screen_GetAutoSizeViewport); +} + +RuntimeScriptValue Sc_Screen_SetAutoSizeViewport(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PBOOL(Screen_SetAutoSizeViewport); +} + +RuntimeScriptValue Sc_Screen_GetViewport(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO(ScriptViewport, Screen_GetViewport); +} + +RuntimeScriptValue Sc_Screen_GetViewportCount(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(Screen_GetViewportCount); +} + +RuntimeScriptValue Sc_Screen_GetAnyViewport(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT(ScriptViewport, Screen_GetAnyViewport); +} + +RuntimeScriptValue Sc_Screen_ScreenToRoomPoint(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT2(ScriptUserObject, Screen_ScreenToRoomPoint); +} + +RuntimeScriptValue Sc_Screen_RoomToScreenPoint(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT2(ScriptUserObject, Screen_RoomToScreenPoint); +} + +void RegisterScreenAPI() +{ + ccAddExternalStaticFunction("Screen::get_Height", Sc_Screen_GetScreenHeight); + ccAddExternalStaticFunction("Screen::get_Width", Sc_Screen_GetScreenWidth); + ccAddExternalStaticFunction("Screen::get_AutoSizeViewportOnRoomLoad", Sc_Screen_GetAutoSizeViewport); + ccAddExternalStaticFunction("Screen::set_AutoSizeViewportOnRoomLoad", Sc_Screen_SetAutoSizeViewport); + ccAddExternalStaticFunction("Screen::get_Viewport", Sc_Screen_GetViewport); + ccAddExternalStaticFunction("Screen::get_ViewportCount", Sc_Screen_GetViewportCount); + ccAddExternalStaticFunction("Screen::geti_Viewports", Sc_Screen_GetAnyViewport); + ccAddExternalStaticFunction("Screen::ScreenToRoomPoint", Sc_Screen_ScreenToRoomPoint); + ccAddExternalStaticFunction("Screen::RoomToScreenPoint", Sc_Screen_RoomToScreenPoint); +} diff --git a/engines/ags/engine/ac/screen.h b/engines/ags/engine/ac/screen.h new file mode 100644 index 00000000000..4ad3e7de022 --- /dev/null +++ b/engines/ags/engine/ac/screen.h @@ -0,0 +1,31 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__SCREEN_H +#define __AGS_EE_AC__SCREEN_H + +namespace AGS { namespace Common { class Bitmap; } } +namespace AGS { namespace Engine { class IDriverDependantBitmap; } } + +void my_fade_in(PALETTE p, int speed); +void current_fade_out_effect (); +AGS::Engine::IDriverDependantBitmap* prepare_screen_for_transition_in(); + +// Screenshot made in the last room, used during some of the transition effects +extern AGS::Common::Bitmap *saved_viewport_bitmap; + +#endif // __AGS_EE_AC__SCREEN_H diff --git a/engines/ags/engine/ac/screenoverlay.cpp b/engines/ags/engine/ac/screenoverlay.cpp new file mode 100644 index 00000000000..205e3b0867f --- /dev/null +++ b/engines/ags/engine/ac/screenoverlay.cpp @@ -0,0 +1,59 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "screenoverlay.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +void ScreenOverlay::ReadFromFile(Stream *in, int32_t cmp_ver) +{ + // Skipping bmp and pic pointer values + // TODO: find out if it's safe to just drop these pointers!! replace with unique_ptr? + bmp = nullptr; + pic = nullptr; + in->ReadInt32(); // bmp + hasSerializedBitmap = in->ReadInt32() != 0; + type = in->ReadInt32(); + x = in->ReadInt32(); + y = in->ReadInt32(); + timeout = in->ReadInt32(); + bgSpeechForChar = in->ReadInt32(); + associatedOverlayHandle = in->ReadInt32(); + hasAlphaChannel = in->ReadBool(); + positionRelativeToScreen = in->ReadBool(); + if (cmp_ver >= 1) + { + _offsetX = in->ReadInt32(); + _offsetY = in->ReadInt32(); + } +} + +void ScreenOverlay::WriteToFile(Stream *out) const +{ + // Writing bitmap "pointers" to correspond to full structure writing + out->WriteInt32(0); // bmp + out->WriteInt32(pic ? 1 : 0); // pic + out->WriteInt32(type); + out->WriteInt32(x); + out->WriteInt32(y); + out->WriteInt32(timeout); + out->WriteInt32(bgSpeechForChar); + out->WriteInt32(associatedOverlayHandle); + out->WriteBool(hasAlphaChannel); + out->WriteBool(positionRelativeToScreen); + // since cmp_ver = 1 + out->WriteInt32(_offsetX); + out->WriteInt32(_offsetY); +} diff --git a/engines/ags/engine/ac/screenoverlay.h b/engines/ags/engine/ac/screenoverlay.h new file mode 100644 index 00000000000..6ede440e6c2 --- /dev/null +++ b/engines/ags/engine/ac/screenoverlay.h @@ -0,0 +1,44 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__SCREENOVERLAY_H +#define __AGS_EE_AC__SCREENOVERLAY_H + +#include + +// Forward declaration +namespace AGS { namespace Common { class Bitmap; class Stream; } } +namespace AGS { namespace Engine { class IDriverDependantBitmap; }} +using namespace AGS; // FIXME later + + +struct ScreenOverlay { + Engine::IDriverDependantBitmap *bmp = nullptr; + Common::Bitmap *pic = nullptr; + int type = 0, x = 0, y = 0, timeout = 0; + int bgSpeechForChar = 0; + int associatedOverlayHandle = 0; + bool hasAlphaChannel = false; + bool positionRelativeToScreen = false; + bool hasSerializedBitmap = false; + int _offsetX = 0, _offsetY = 0; + + void ReadFromFile(Common::Stream *in, int32_t cmp_ver); + void WriteToFile(Common::Stream *out) const; +}; + +#endif // __AGS_EE_AC__SCREENOVERLAY_H diff --git a/engines/ags/engine/ac/scriptcontainers.cpp b/engines/ags/engine/ac/scriptcontainers.cpp new file mode 100644 index 00000000000..b02d53bcc46 --- /dev/null +++ b/engines/ags/engine/ac/scriptcontainers.cpp @@ -0,0 +1,354 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Containers script API. +// +//============================================================================= +#include "ac/common.h" // quit +#include "ac/string.h" +#include "ac/dynobj/cc_dynamicarray.h" +#include "ac/dynobj/cc_dynamicobject.h" +#include "ac/dynobj/scriptdict.h" +#include "ac/dynobj/scriptset.h" +#include "ac/dynobj/scriptstring.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "util/bbop.h" + +extern ScriptString myScriptStringImpl; + +//============================================================================= +// +// Dictionary of strings script API. +// +//============================================================================= + +ScriptDictBase *Dict_CreateImpl(bool sorted, bool case_sensitive) +{ + ScriptDictBase *dic; + if (sorted) + { + if (case_sensitive) + dic = new ScriptDict(); + else + dic = new ScriptDictCI(); + } + else + { + if (case_sensitive) + dic = new ScriptHashDict(); + else + dic = new ScriptHashDictCI(); + } + return dic; +} + +ScriptDictBase *Dict_Create(bool sorted, bool case_sensitive) +{ + ScriptDictBase *dic = Dict_CreateImpl(sorted, case_sensitive); + ccRegisterManagedObject(dic, dic); + return dic; +} + +// TODO: we need memory streams +ScriptDictBase *Dict_Unserialize(int index, const char *serializedData, int dataSize) +{ + if (dataSize < sizeof(int32_t) * 2) + quit("Dict_Unserialize: not enough data."); + const char *ptr = serializedData; + const int sorted = BBOp::Int32FromLE(*((int*)ptr)); ptr += sizeof(int32_t); + const int cs = BBOp::Int32FromLE(*((int*)ptr)); ptr += sizeof(int32_t); + ScriptDictBase *dic = Dict_CreateImpl(sorted != 0, cs != 0); + dic->Unserialize(index, ptr, dataSize -= sizeof(int32_t) * 2); + return dic; +} + +void Dict_Clear(ScriptDictBase *dic) +{ + dic->Clear(); +} + +bool Dict_Contains(ScriptDictBase *dic, const char *key) +{ + return dic->Contains(key); +} + +const char *Dict_Get(ScriptDictBase *dic, const char *key) +{ + auto *str = dic->Get(key); + return str ? CreateNewScriptString(str) : nullptr; +} + +bool Dict_Remove(ScriptDictBase *dic, const char *key) +{ + return dic->Remove(key); +} + +bool Dict_Set(ScriptDictBase *dic, const char *key, const char *value) +{ + return dic->Set(key, value); +} + +int Dict_GetCompareStyle(ScriptDictBase *dic) +{ + return dic->IsCaseSensitive() ? 1 : 0; +} + +int Dict_GetSortStyle(ScriptDictBase *dic) +{ + return dic->IsSorted() ? 1 : 0; +} + +int Dict_GetItemCount(ScriptDictBase *dic) +{ + return dic->GetItemCount(); +} + +void *Dict_GetKeysAsArray(ScriptDictBase *dic) +{ + std::vector items; + dic->GetKeys(items); + if (items.size() == 0) + return nullptr; + DynObjectRef arr = DynamicArrayHelpers::CreateStringArray(items); + return arr.second; +} + +void *Dict_GetValuesAsArray(ScriptDictBase *dic) +{ + std::vector items; + dic->GetValues(items); + if (items.size() == 0) + return nullptr; + DynObjectRef arr = DynamicArrayHelpers::CreateStringArray(items); + return arr.second; +} + +RuntimeScriptValue Sc_Dict_Create(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PBOOL2(ScriptDictBase, Dict_Create); +} + +RuntimeScriptValue Sc_Dict_Clear(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptDictBase, Dict_Clear); +} + +RuntimeScriptValue Sc_Dict_Contains(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ(ScriptDictBase, Dict_Contains, const char); +} + +RuntimeScriptValue Sc_Dict_Get(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_POBJ(ScriptDictBase, const char, myScriptStringImpl, Dict_Get, const char); +} + +RuntimeScriptValue Sc_Dict_Remove(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ(ScriptDictBase, Dict_Remove, const char); +} + +RuntimeScriptValue Sc_Dict_Set(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ2(ScriptDictBase, Dict_Set, const char, const char); +} + +RuntimeScriptValue Sc_Dict_GetCompareStyle(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDictBase, Dict_GetCompareStyle); +} + +RuntimeScriptValue Sc_Dict_GetSortStyle(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDictBase, Dict_GetSortStyle); +} + +RuntimeScriptValue Sc_Dict_GetItemCount(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptDictBase, Dict_GetItemCount); +} + +RuntimeScriptValue Sc_Dict_GetKeysAsArray(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptDictBase, void, globalDynamicArray, Dict_GetKeysAsArray); +} + +RuntimeScriptValue Sc_Dict_GetValuesAsArray(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptDictBase, void, globalDynamicArray, Dict_GetValuesAsArray); +} + +//============================================================================= +// +// Set of strings script API. +// +//============================================================================= + +ScriptSetBase *Set_CreateImpl(bool sorted, bool case_sensitive) +{ + ScriptSetBase *set; + if (sorted) + { + if (case_sensitive) + set = new ScriptSet(); + else + set = new ScriptSetCI(); + } + else + { + if (case_sensitive) + set = new ScriptHashSet(); + else + set = new ScriptHashSetCI(); + } + return set; +} + +ScriptSetBase *Set_Create(bool sorted, bool case_sensitive) +{ + ScriptSetBase *set = Set_CreateImpl(sorted, case_sensitive); + ccRegisterManagedObject(set, set); + return set; +} + +// TODO: we need memory streams +ScriptSetBase *Set_Unserialize(int index, const char *serializedData, int dataSize) +{ + if (dataSize < sizeof(int32_t) * 2) + quit("Set_Unserialize: not enough data."); + const char *ptr = serializedData; + const int sorted = BBOp::Int32FromLE(*((int*)ptr)); ptr += sizeof(int32_t); + const int cs = BBOp::Int32FromLE(*((int*)ptr)); ptr += sizeof(int32_t); + ScriptSetBase *set = Set_CreateImpl(sorted != 0, cs != 0); + set->Unserialize(index, ptr, dataSize -= sizeof(int32_t) * 2); + return set; +} + +bool Set_Add(ScriptSetBase *set, const char *item) +{ + return set->Add(item); +} + +void Set_Clear(ScriptSetBase *set) +{ + set->Clear(); +} + +bool Set_Contains(ScriptSetBase *set, const char *item) +{ + return set->Contains(item); +} + +bool Set_Remove(ScriptSetBase *set, const char *item) +{ + return set->Remove(item); +} + +int Set_GetCompareStyle(ScriptSetBase *set) +{ + return set->IsCaseSensitive() ? 1 : 0; +} + +int Set_GetSortStyle(ScriptSetBase *set) +{ + return set->IsSorted() ? 1 : 0; +} + +int Set_GetItemCount(ScriptSetBase *set) +{ + return set->GetItemCount(); +} + +void *Set_GetItemsAsArray(ScriptSetBase *set) +{ + std::vector items; + set->GetItems(items); + if (items.size() == 0) + return nullptr; + DynObjectRef arr = DynamicArrayHelpers::CreateStringArray(items); + return arr.second; +} + +RuntimeScriptValue Sc_Set_Create(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PBOOL2(ScriptSetBase, Set_Create); +} + +RuntimeScriptValue Sc_Set_Add(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ(ScriptSetBase, Set_Add, const char); +} + +RuntimeScriptValue Sc_Set_Clear(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptSetBase, Set_Clear); +} + +RuntimeScriptValue Sc_Set_Contains(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ(ScriptSetBase, Set_Contains, const char); +} + +RuntimeScriptValue Sc_Set_Remove(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL_POBJ(ScriptSetBase, Set_Remove, const char); +} + +RuntimeScriptValue Sc_Set_GetCompareStyle(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptSetBase, Set_GetCompareStyle); +} + +RuntimeScriptValue Sc_Set_GetSortStyle(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptSetBase, Set_GetSortStyle); +} + +RuntimeScriptValue Sc_Set_GetItemCount(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptSetBase, Set_GetItemCount); +} + +RuntimeScriptValue Sc_Set_GetItemAsArray(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptSetBase, void, globalDynamicArray, Set_GetItemsAsArray); +} + + + +void RegisterContainerAPI() +{ + ccAddExternalStaticFunction("Dictionary::Create", Sc_Dict_Create); + ccAddExternalObjectFunction("Dictionary::Clear", Sc_Dict_Clear); + ccAddExternalObjectFunction("Dictionary::Contains", Sc_Dict_Contains); + ccAddExternalObjectFunction("Dictionary::Get", Sc_Dict_Get); + ccAddExternalObjectFunction("Dictionary::Remove", Sc_Dict_Remove); + ccAddExternalObjectFunction("Dictionary::Set", Sc_Dict_Set); + ccAddExternalObjectFunction("Dictionary::get_CompareStyle", Sc_Dict_GetCompareStyle); + ccAddExternalObjectFunction("Dictionary::get_SortStyle", Sc_Dict_GetSortStyle); + ccAddExternalObjectFunction("Dictionary::get_ItemCount", Sc_Dict_GetItemCount); + ccAddExternalObjectFunction("Dictionary::GetKeysAsArray", Sc_Dict_GetKeysAsArray); + ccAddExternalObjectFunction("Dictionary::GetValuesAsArray", Sc_Dict_GetValuesAsArray); + + ccAddExternalStaticFunction("Set::Create", Sc_Set_Create); + ccAddExternalObjectFunction("Set::Add", Sc_Set_Add); + ccAddExternalObjectFunction("Set::Clear", Sc_Set_Clear); + ccAddExternalObjectFunction("Set::Contains", Sc_Set_Contains); + ccAddExternalObjectFunction("Set::Remove", Sc_Set_Remove); + ccAddExternalObjectFunction("Set::get_CompareStyle", Sc_Set_GetCompareStyle); + ccAddExternalObjectFunction("Set::get_SortStyle", Sc_Set_GetSortStyle); + ccAddExternalObjectFunction("Set::get_ItemCount", Sc_Set_GetItemCount); + ccAddExternalObjectFunction("Set::GetItemsAsArray", Sc_Set_GetItemAsArray); +} diff --git a/engines/ags/engine/ac/slider.cpp b/engines/ags/engine/ac/slider.cpp new file mode 100644 index 00000000000..1f489340fd8 --- /dev/null +++ b/engines/ags/engine/ac/slider.cpp @@ -0,0 +1,223 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/slider.h" +#include "ac/common.h" + +// *** SLIDER FUNCTIONS + +void Slider_SetMax(GUISlider *guisl, int valn) { + + if (valn != guisl->MaxValue) { + guisl->MaxValue = valn; + + if (guisl->Value > guisl->MaxValue) + guisl->Value = guisl->MaxValue; + if (guisl->MinValue > guisl->MaxValue) + quit("!Slider.Max: minimum cannot be greater than maximum"); + + guis_need_update = 1; + } + +} + +int Slider_GetMax(GUISlider *guisl) { + return guisl->MaxValue; +} + +void Slider_SetMin(GUISlider *guisl, int valn) { + + if (valn != guisl->MinValue) { + guisl->MinValue = valn; + + if (guisl->Value < guisl->MinValue) + guisl->Value = guisl->MinValue; + if (guisl->MinValue > guisl->MaxValue) + quit("!Slider.Min: minimum cannot be greater than maximum"); + + guis_need_update = 1; + } + +} + +int Slider_GetMin(GUISlider *guisl) { + return guisl->MinValue; +} + +void Slider_SetValue(GUISlider *guisl, int valn) { + if (valn > guisl->MaxValue) valn = guisl->MaxValue; + if (valn < guisl->MinValue) valn = guisl->MinValue; + + if (valn != guisl->Value) { + guisl->Value = valn; + guis_need_update = 1; + } +} + +int Slider_GetValue(GUISlider *guisl) { + return guisl->Value; +} + +int Slider_GetBackgroundGraphic(GUISlider *guisl) { + return (guisl->BgImage > 0) ? guisl->BgImage : 0; +} + +void Slider_SetBackgroundGraphic(GUISlider *guisl, int newImage) +{ + if (newImage != guisl->BgImage) + { + guisl->BgImage = newImage; + guis_need_update = 1; + } +} + +int Slider_GetHandleGraphic(GUISlider *guisl) { + return (guisl->HandleImage > 0) ? guisl->HandleImage : 0; +} + +void Slider_SetHandleGraphic(GUISlider *guisl, int newImage) +{ + if (newImage != guisl->HandleImage) + { + guisl->HandleImage = newImage; + guis_need_update = 1; + } +} + +int Slider_GetHandleOffset(GUISlider *guisl) { + return guisl->HandleOffset; +} + +void Slider_SetHandleOffset(GUISlider *guisl, int newOffset) +{ + if (newOffset != guisl->HandleOffset) + { + guisl->HandleOffset = newOffset; + guis_need_update = 1; + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// int (GUISlider *guisl) +RuntimeScriptValue Sc_Slider_GetBackgroundGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUISlider, Slider_GetBackgroundGraphic); +} + +// void (GUISlider *guisl, int newImage) +RuntimeScriptValue Sc_Slider_SetBackgroundGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUISlider, Slider_SetBackgroundGraphic); +} + +// int (GUISlider *guisl) +RuntimeScriptValue Sc_Slider_GetHandleGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUISlider, Slider_GetHandleGraphic); +} + +// void (GUISlider *guisl, int newImage) +RuntimeScriptValue Sc_Slider_SetHandleGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUISlider, Slider_SetHandleGraphic); +} + +// int (GUISlider *guisl) +RuntimeScriptValue Sc_Slider_GetHandleOffset(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUISlider, Slider_GetHandleOffset); +} + +// void (GUISlider *guisl, int newOffset) +RuntimeScriptValue Sc_Slider_SetHandleOffset(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUISlider, Slider_SetHandleOffset); +} + +// int (GUISlider *guisl) +RuntimeScriptValue Sc_Slider_GetMax(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUISlider, Slider_GetMax); +} + +// void (GUISlider *guisl, int valn) +RuntimeScriptValue Sc_Slider_SetMax(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUISlider, Slider_SetMax); +} + +// int (GUISlider *guisl) +RuntimeScriptValue Sc_Slider_GetMin(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUISlider, Slider_GetMin); +} + +// void (GUISlider *guisl, int valn) +RuntimeScriptValue Sc_Slider_SetMin(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUISlider, Slider_SetMin); +} + +// int (GUISlider *guisl) +RuntimeScriptValue Sc_Slider_GetValue(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUISlider, Slider_GetValue); +} + +// void Slider_SetValue(GUISlider *guisl, int valn) +RuntimeScriptValue Sc_Slider_SetValue(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUISlider, Slider_SetValue); +} + + +void RegisterSliderAPI() +{ + ccAddExternalObjectFunction("Slider::get_BackgroundGraphic", Sc_Slider_GetBackgroundGraphic); + ccAddExternalObjectFunction("Slider::set_BackgroundGraphic", Sc_Slider_SetBackgroundGraphic); + ccAddExternalObjectFunction("Slider::get_HandleGraphic", Sc_Slider_GetHandleGraphic); + ccAddExternalObjectFunction("Slider::set_HandleGraphic", Sc_Slider_SetHandleGraphic); + ccAddExternalObjectFunction("Slider::get_HandleOffset", Sc_Slider_GetHandleOffset); + ccAddExternalObjectFunction("Slider::set_HandleOffset", Sc_Slider_SetHandleOffset); + ccAddExternalObjectFunction("Slider::get_Max", Sc_Slider_GetMax); + ccAddExternalObjectFunction("Slider::set_Max", Sc_Slider_SetMax); + ccAddExternalObjectFunction("Slider::get_Min", Sc_Slider_GetMin); + ccAddExternalObjectFunction("Slider::set_Min", Sc_Slider_SetMin); + ccAddExternalObjectFunction("Slider::get_Value", Sc_Slider_GetValue); + ccAddExternalObjectFunction("Slider::set_Value", Sc_Slider_SetValue); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("Slider::get_BackgroundGraphic", (void*)Slider_GetBackgroundGraphic); + ccAddExternalFunctionForPlugin("Slider::set_BackgroundGraphic", (void*)Slider_SetBackgroundGraphic); + ccAddExternalFunctionForPlugin("Slider::get_HandleGraphic", (void*)Slider_GetHandleGraphic); + ccAddExternalFunctionForPlugin("Slider::set_HandleGraphic", (void*)Slider_SetHandleGraphic); + ccAddExternalFunctionForPlugin("Slider::get_HandleOffset", (void*)Slider_GetHandleOffset); + ccAddExternalFunctionForPlugin("Slider::set_HandleOffset", (void*)Slider_SetHandleOffset); + ccAddExternalFunctionForPlugin("Slider::get_Max", (void*)Slider_GetMax); + ccAddExternalFunctionForPlugin("Slider::set_Max", (void*)Slider_SetMax); + ccAddExternalFunctionForPlugin("Slider::get_Min", (void*)Slider_GetMin); + ccAddExternalFunctionForPlugin("Slider::set_Min", (void*)Slider_SetMin); + ccAddExternalFunctionForPlugin("Slider::get_Value", (void*)Slider_GetValue); + ccAddExternalFunctionForPlugin("Slider::set_Value", (void*)Slider_SetValue); +} diff --git a/engines/ags/engine/ac/slider.h b/engines/ags/engine/ac/slider.h new file mode 100644 index 00000000000..402b7365664 --- /dev/null +++ b/engines/ags/engine/ac/slider.h @@ -0,0 +1,38 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__SLIDER_H +#define __AGS_EE_AC__SLIDER_H + +#include "gui/guislider.h" + +using AGS::Common::GUISlider; + +void Slider_SetMax(GUISlider *guisl, int valn); +int Slider_GetMax(GUISlider *guisl); +void Slider_SetMin(GUISlider *guisl, int valn); +int Slider_GetMin(GUISlider *guisl); +void Slider_SetValue(GUISlider *guisl, int valn); +int Slider_GetValue(GUISlider *guisl); +int Slider_GetBackgroundGraphic(GUISlider *guisl); +void Slider_SetBackgroundGraphic(GUISlider *guisl, int newImage); +int Slider_GetHandleGraphic(GUISlider *guisl); +void Slider_SetHandleGraphic(GUISlider *guisl, int newImage); +int Slider_GetHandleOffset(GUISlider *guisl); +void Slider_SetHandleOffset(GUISlider *guisl, int newOffset); + +#endif // __AGS_EE_AC__SLIDER_H diff --git a/engines/ags/engine/ac/speech.cpp b/engines/ags/engine/ac/speech.cpp new file mode 100644 index 00000000000..dbe24beb3b1 --- /dev/null +++ b/engines/ags/engine/ac/speech.cpp @@ -0,0 +1,254 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/runtime_defines.h" +#include "ac/speech.h" +#include "debug/debug_log.h" + +int user_to_internal_skip_speech(SkipSpeechStyle userval) +{ + switch (userval) + { + case kSkipSpeechKeyMouseTime: + return SKIP_AUTOTIMER | SKIP_KEYPRESS | SKIP_MOUSECLICK; + case kSkipSpeechKeyTime: + return SKIP_AUTOTIMER | SKIP_KEYPRESS; + case kSkipSpeechTime: + return SKIP_AUTOTIMER; + case kSkipSpeechKeyMouse: + return SKIP_KEYPRESS | SKIP_MOUSECLICK; + case kSkipSpeechMouseTime: + return SKIP_AUTOTIMER | SKIP_MOUSECLICK; + case kSkipSpeechKey: + return SKIP_KEYPRESS; + case kSkipSpeechMouse: + return SKIP_MOUSECLICK; + default: + quit("user_to_internal_skip_speech: unknown userval"); + return 0; + } +} + +SkipSpeechStyle internal_skip_speech_to_user(int internal_val) +{ + if (internal_val & SKIP_AUTOTIMER) + { + internal_val &= ~SKIP_AUTOTIMER; + if (internal_val == (SKIP_KEYPRESS | SKIP_MOUSECLICK)) + { + return kSkipSpeechKeyMouseTime; + } + else if (internal_val == SKIP_KEYPRESS) + { + return kSkipSpeechKeyTime; + } + else if (internal_val == SKIP_MOUSECLICK) + { + return kSkipSpeechMouseTime; + } + return kSkipSpeechTime; + } + else + { + if (internal_val == (SKIP_KEYPRESS | SKIP_MOUSECLICK)) + { + return kSkipSpeechKeyMouse; + } + else if (internal_val == SKIP_KEYPRESS) + { + return kSkipSpeechKey; + } + else if (internal_val == SKIP_MOUSECLICK) + { + return kSkipSpeechMouse; + } + } + return kSkipSpeechUndefined; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_audio.h" +#include "ac/global_display.h" +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +extern GameSetupStruct game; +extern GameState play; + +RuntimeScriptValue Sc_Speech_GetAnimationStopTimeMargin(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(play.close_mouth_speech_time); +} + +RuntimeScriptValue Sc_Speech_SetAnimationStopTimeMargin(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARSET_PINT(play.close_mouth_speech_time); +} + +RuntimeScriptValue Sc_Speech_GetCustomPortraitPlacement(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(play.speech_portrait_placement); +} + +RuntimeScriptValue Sc_Speech_SetCustomPortraitPlacement(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARSET_PINT(play.speech_portrait_placement); +} + +RuntimeScriptValue Sc_Speech_GetDisplayPostTimeMs(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(play.speech_display_post_time_ms); +} + +RuntimeScriptValue Sc_Speech_SetDisplayPostTimeMs(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARSET_PINT(play.speech_display_post_time_ms); +} + +RuntimeScriptValue Sc_Speech_GetGlobalSpeechAnimationDelay(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(play.talkanim_speed); +} + +RuntimeScriptValue Sc_Speech_SetGlobalSpeechAnimationDelay(const RuntimeScriptValue *params, int32_t param_count) +{ + if (game.options[OPT_GLOBALTALKANIMSPD] == 0) + { + debug_script_warn("Speech.GlobalSpeechAnimationDelay cannot be set when global speech animation speed is not enabled; set Speech.UseGlobalSpeechAnimationDelay first!"); + return RuntimeScriptValue(); + } + API_VARSET_PINT(play.talkanim_speed); +} + +RuntimeScriptValue Sc_Speech_GetPortraitXOffset(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(play.speech_portrait_x); +} + +RuntimeScriptValue Sc_Speech_SetPortraitXOffset(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARSET_PINT(play.speech_portrait_x); +} + +RuntimeScriptValue Sc_Speech_GetPortraitY(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(play.speech_portrait_y); +} + +RuntimeScriptValue Sc_Speech_SetPortraitY(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARSET_PINT(play.speech_portrait_y); +} + +RuntimeScriptValue Sc_Speech_GetStyle(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(game.options[OPT_SPEECHTYPE]); +} + +extern RuntimeScriptValue Sc_SetSpeechStyle(const RuntimeScriptValue *params, int32_t param_count); + +RuntimeScriptValue Sc_Speech_GetSkipKey(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(play.skip_speech_specific_key); +} + +RuntimeScriptValue Sc_Speech_SetSkipKey(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARSET_PINT(play.skip_speech_specific_key); +} + +RuntimeScriptValue Sc_Speech_GetSkipStyle(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetSkipSpeech); +} + +extern RuntimeScriptValue Sc_SetSkipSpeech(const RuntimeScriptValue *params, int32_t param_count); + +RuntimeScriptValue Sc_Speech_GetTextAlignment(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(play.speech_text_align); +} + +RuntimeScriptValue Sc_Speech_SetTextAlignment_Old(const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_VARIABLE_VALUE(play.speech_text_align); + play.speech_text_align = ReadScriptAlignment(params[0].IValue); + return RuntimeScriptValue(); +} + +RuntimeScriptValue Sc_Speech_SetTextAlignment(const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_VARIABLE_VALUE(play.speech_text_align); + play.speech_text_align = (HorAlignment)params[0].IValue; + return RuntimeScriptValue(); +} + +RuntimeScriptValue Sc_Speech_GetUseGlobalSpeechAnimationDelay(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARGET_INT(game.options[OPT_GLOBALTALKANIMSPD]); +} + +RuntimeScriptValue Sc_Speech_SetUseGlobalSpeechAnimationDelay(const RuntimeScriptValue *params, int32_t param_count) +{ + API_VARSET_PINT(game.options[OPT_GLOBALTALKANIMSPD]); +} + +RuntimeScriptValue Sc_Speech_GetVoiceMode(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(GetVoiceMode); +} + +extern RuntimeScriptValue Sc_SetVoiceMode(const RuntimeScriptValue *params, int32_t param_count); + +void RegisterSpeechAPI(ScriptAPIVersion base_api, ScriptAPIVersion compat_api) +{ + ccAddExternalStaticFunction("Speech::get_AnimationStopTimeMargin", Sc_Speech_GetAnimationStopTimeMargin); + ccAddExternalStaticFunction("Speech::set_AnimationStopTimeMargin", Sc_Speech_SetAnimationStopTimeMargin); + ccAddExternalStaticFunction("Speech::get_CustomPortraitPlacement", Sc_Speech_GetCustomPortraitPlacement); + ccAddExternalStaticFunction("Speech::set_CustomPortraitPlacement", Sc_Speech_SetCustomPortraitPlacement); + ccAddExternalStaticFunction("Speech::get_DisplayPostTimeMs", Sc_Speech_GetDisplayPostTimeMs); + ccAddExternalStaticFunction("Speech::set_DisplayPostTimeMs", Sc_Speech_SetDisplayPostTimeMs); + ccAddExternalStaticFunction("Speech::get_GlobalSpeechAnimationDelay", Sc_Speech_GetGlobalSpeechAnimationDelay); + ccAddExternalStaticFunction("Speech::set_GlobalSpeechAnimationDelay", Sc_Speech_SetGlobalSpeechAnimationDelay); + ccAddExternalStaticFunction("Speech::get_PortraitXOffset", Sc_Speech_GetPortraitXOffset); + ccAddExternalStaticFunction("Speech::set_PortraitXOffset", Sc_Speech_SetPortraitXOffset); + ccAddExternalStaticFunction("Speech::get_PortraitY", Sc_Speech_GetPortraitY); + ccAddExternalStaticFunction("Speech::set_PortraitY", Sc_Speech_SetPortraitY); + ccAddExternalStaticFunction("Speech::get_SkipKey", Sc_Speech_GetSkipKey); + ccAddExternalStaticFunction("Speech::set_SkipKey", Sc_Speech_SetSkipKey); + ccAddExternalStaticFunction("Speech::get_SkipStyle", Sc_Speech_GetSkipStyle); + ccAddExternalStaticFunction("Speech::set_SkipStyle", Sc_SetSkipSpeech); + ccAddExternalStaticFunction("Speech::get_Style", Sc_Speech_GetStyle); + ccAddExternalStaticFunction("Speech::set_Style", Sc_SetSpeechStyle); + ccAddExternalStaticFunction("Speech::get_TextAlignment", Sc_Speech_GetTextAlignment); + if (base_api < kScriptAPI_v350) + ccAddExternalStaticFunction("Speech::set_TextAlignment", Sc_Speech_SetTextAlignment_Old); + else + ccAddExternalStaticFunction("Speech::set_TextAlignment", Sc_Speech_SetTextAlignment); + ccAddExternalStaticFunction("Speech::get_UseGlobalSpeechAnimationDelay", Sc_Speech_GetUseGlobalSpeechAnimationDelay); + ccAddExternalStaticFunction("Speech::set_UseGlobalSpeechAnimationDelay", Sc_Speech_SetUseGlobalSpeechAnimationDelay); + ccAddExternalStaticFunction("Speech::get_VoiceMode", Sc_Speech_GetVoiceMode); + ccAddExternalStaticFunction("Speech::set_VoiceMode", Sc_SetVoiceMode); + + /* -- Don't register more unsafe plugin symbols until new plugin interface is designed --*/ +} diff --git a/engines/ags/engine/ac/speech.h b/engines/ags/engine/ac/speech.h new file mode 100644 index 00000000000..8e0d973af75 --- /dev/null +++ b/engines/ags/engine/ac/speech.h @@ -0,0 +1,39 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__SPEECH_H +#define __AGS_EE_AC__SPEECH_H + +enum SkipSpeechStyle +{ + kSkipSpeechUndefined = -1, + kSkipSpeechKeyMouseTime = 0, + kSkipSpeechKeyTime = 1, + kSkipSpeechTime = 2, + kSkipSpeechKeyMouse = 3, + kSkipSpeechMouseTime = 4, + kSkipSpeechKey = 5, + kSkipSpeechMouse = 6, + + kSkipSpeechFirst = kSkipSpeechKeyMouseTime, + kSkipSpeechLast = kSkipSpeechMouse +}; + +int user_to_internal_skip_speech(SkipSpeechStyle userval); +SkipSpeechStyle internal_skip_speech_to_user(int internal_val); + +#endif // __AGS_EE_AC__SPEECH_H diff --git a/engines/ags/engine/ac/sprite.cpp b/engines/ags/engine/ac/sprite.cpp new file mode 100644 index 00000000000..a14bfc577be --- /dev/null +++ b/engines/ags/engine/ac/sprite.cpp @@ -0,0 +1,186 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/gamesetupstruct.h" +#include "ac/sprite.h" +#include "ac/system.h" +#include "platform/base/agsplatformdriver.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "ac/spritecache.h" +#include "gfx/bitmap.h" +#include "gfx/graphicsdriver.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern SpriteCache spriteset; +extern int our_eip, eip_guinum, eip_guiobj; +extern color palette[256]; +extern IGraphicsDriver *gfxDriver; +extern AGSPlatformDriver *platform; + +void get_new_size_for_sprite (int ee, int ww, int hh, int &newwid, int &newhit) +{ + newwid = ww; + newhit = hh; + const SpriteInfo &spinfo = game.SpriteInfos[ee]; + if (!game.AllowRelativeRes() || !spinfo.IsRelativeRes()) + return; + ctx_data_to_game_size(newwid, newhit, spinfo.IsLegacyHiRes()); +} + +// set any alpha-transparent pixels in the image to the appropriate +// RGB mask value so that the blit calls work correctly +void set_rgb_mask_using_alpha_channel(Bitmap *image) +{ + int x, y; + + for (y=0; y < image->GetHeight(); y++) + { + unsigned int*psrc = (unsigned int *)image->GetScanLine(y); + + for (x=0; x < image->GetWidth(); x++) + { + if ((psrc[x] & 0xff000000) == 0x00000000) + psrc[x] = MASK_COLOR_32; + } + } +} + +// from is a 32-bit RGBA image, to is a 15/16/24-bit destination image +Bitmap *remove_alpha_channel(Bitmap *from) +{ + const int game_cd = game.GetColorDepth(); + Bitmap *to = BitmapHelper::CreateBitmap(from->GetWidth(), from->GetHeight(), game_cd); + const int maskcol = to->GetMaskColor(); + int y,x; + unsigned int c,b,g,r; + + if (game_cd == 24) // 32-to-24 + { + for (y=0; y < from->GetHeight(); y++) { + unsigned int*psrc = (unsigned int *)from->GetScanLine(y); + unsigned char*pdest = (unsigned char*)to->GetScanLine(y); + + for (x=0; x < from->GetWidth(); x++) { + c = psrc[x]; + // less than 50% opaque, remove the pixel + if (((c >> 24) & 0x00ff) < 128) + c = maskcol; + + // copy the RGB values across + memcpy(&pdest[x * 3], &c, 3); + } + } + } + else if (game_cd > 8) // 32 to 15 or 16 + { + for (y=0; y < from->GetHeight(); y++) { + unsigned int*psrc = (unsigned int *)from->GetScanLine(y); + unsigned short*pdest = (unsigned short *)to->GetScanLine(y); + + for (x=0; x < from->GetWidth(); x++) { + c = psrc[x]; + // less than 50% opaque, remove the pixel + if (((c >> 24) & 0x00ff) < 128) + pdest[x] = maskcol; + else { + // otherwise, copy it across + r = (c >> 16) & 0x00ff; + g = (c >> 8) & 0x00ff; + b = c & 0x00ff; + pdest[x] = makecol_depth(game_cd, r, g, b); + } + } + } + } + else // 32 to 8-bit game + { // TODO: consider similar to above approach if this becomes a wanted feature + to->Blit(from); + } + return to; +} + +void pre_save_sprite(int ee) { + // not used, we don't save +} + +// these vars are global to help with debugging +Bitmap *tmpdbl, *curspr; +int newwid, newhit; +void initialize_sprite (int ee) { + + if ((ee < 0) || (ee > spriteset.GetSpriteSlotCount())) + quit("initialize_sprite: invalid sprite number"); + + if ((spriteset[ee] == nullptr) && (ee > 0)) { + // replace empty sprites with blue cups, to avoid crashes + spriteset.RemapSpriteToSprite0(ee); + } + else if (spriteset[ee]==nullptr) { + game.SpriteInfos[ee].Width=0; + game.SpriteInfos[ee].Height=0; + } + else { + // stretch sprites to correct resolution + int oldeip = our_eip; + our_eip = 4300; + + if (game.SpriteInfos[ee].Flags & SPF_HADALPHACHANNEL) { + // we stripped the alpha channel out last time, put + // it back so that we can remove it properly again + game.SpriteInfos[ee].Flags |= SPF_ALPHACHANNEL; + } + + curspr = spriteset[ee]; + get_new_size_for_sprite (ee, curspr->GetWidth(), curspr->GetHeight(), newwid, newhit); + + eip_guinum = ee; + eip_guiobj = newwid; + + if ((newwid != curspr->GetWidth()) || (newhit != curspr->GetHeight())) { + tmpdbl = BitmapHelper::CreateTransparentBitmap(newwid,newhit,curspr->GetColorDepth()); + if (tmpdbl == nullptr) + quit("Not enough memory to load sprite graphics"); + tmpdbl->Acquire (); + curspr->Acquire (); + tmpdbl->StretchBlt(curspr,RectWH(0,0,tmpdbl->GetWidth(),tmpdbl->GetHeight()), Common::kBitmap_Transparency); + curspr->Release (); + tmpdbl->Release (); + delete curspr; + spriteset.SubstituteBitmap(ee, tmpdbl); + } + + game.SpriteInfos[ee].Width=spriteset[ee]->GetWidth(); + game.SpriteInfos[ee].Height=spriteset[ee]->GetHeight(); + + spriteset.SubstituteBitmap(ee, PrepareSpriteForUse(spriteset[ee], (game.SpriteInfos[ee].Flags & SPF_ALPHACHANNEL) != 0)); + + if (game.GetColorDepth() < 32) { + game.SpriteInfos[ee].Flags &= ~SPF_ALPHACHANNEL; + // save the fact that it had one for the next time this + // is re-loaded from disk + game.SpriteInfos[ee].Flags |= SPF_HADALPHACHANNEL; + } + + pl_run_plugin_hooks(AGSE_SPRITELOAD, ee); + update_polled_stuff_if_runtime(); + + our_eip = oldeip; + } +} diff --git a/engines/ags/engine/ac/sprite.h b/engines/ags/engine/ac/sprite.h new file mode 100644 index 00000000000..761e5af7bf3 --- /dev/null +++ b/engines/ags/engine/ac/sprite.h @@ -0,0 +1,30 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__SPRITE_H +#define __AGS_EE_AC__SPRITE_H + +void get_new_size_for_sprite (int ee, int ww, int hh, int &newwid, int &newhit); +// set any alpha-transparent pixels in the image to the appropriate +// RGB mask value so that the ->Blit calls work correctly +void set_rgb_mask_using_alpha_channel(Common::Bitmap *image); +// from is a 32-bit RGBA image, to is a 15/16/24-bit destination image +Common::Bitmap *remove_alpha_channel(Common::Bitmap *from); +void pre_save_sprite(int ee); +void initialize_sprite (int ee); + +#endif // __AGS_EE_AC__SPRITE_H diff --git a/engines/ags/engine/ac/spritecache_engine.cpp b/engines/ags/engine/ac/spritecache_engine.cpp new file mode 100644 index 00000000000..adb3b03f8d3 --- /dev/null +++ b/engines/ags/engine/ac/spritecache_engine.cpp @@ -0,0 +1,43 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Implementation from sprcache.cpp specific to Engine runtime +// +//============================================================================= + +// Headers, as they are in sprcache.cpp +#ifdef _MANAGED +// ensure this doesn't get compiled to .NET IL +#pragma unmanaged +#pragma warning (disable: 4996 4312) // disable deprecation warnings +#endif + +#include "ac/gamestructdefines.h" +#include "ac/spritecache.h" +#include "util/compress.h" + +//============================================================================= +// Engine-specific implementation split out of sprcache.cpp +//============================================================================= + +void SpriteCache::InitNullSpriteParams(sprkey_t index) +{ + // make it a blue cup, to avoid crashes + _sprInfos[index].Width = _sprInfos[0].Width; + _sprInfos[index].Height = _sprInfos[0].Height; + _spriteData[index].Image = nullptr; + _spriteData[index].Offset = _spriteData[0].Offset; + _spriteData[index].Size = _spriteData[0].Size; + _spriteData[index].Flags = SPRCACHEFLAG_REMAPPED; +} diff --git a/engines/ags/engine/ac/spritelistentry.h b/engines/ags/engine/ac/spritelistentry.h new file mode 100644 index 00000000000..f9b191f2e62 --- /dev/null +++ b/engines/ags/engine/ac/spritelistentry.h @@ -0,0 +1,36 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__SPRITELISTENTRY_H +#define __AGS_EE_AC__SPRITELISTENTRY_H + +#include "gfx/ddb.h" + +struct SpriteListEntry +{ + AGS::Engine::IDriverDependantBitmap *bmp; + AGS::Common::Bitmap *pic; + int baseline; + int x,y; + int transparent; + bool takesPriorityIfEqual; + bool hasAlphaChannel; + + SpriteListEntry(); +}; + +#endif // __AGS_EE_AC__SPRITELISTENTRY_H diff --git a/engines/ags/engine/ac/statobj/agsstaticobject.cpp b/engines/ags/engine/ac/statobj/agsstaticobject.cpp new file mode 100644 index 00000000000..a06b3f6f1c7 --- /dev/null +++ b/engines/ags/engine/ac/statobj/agsstaticobject.cpp @@ -0,0 +1,80 @@ + +#include +#include "ac/statobj/agsstaticobject.h" +#include "ac/game.h" +#include "ac/gamestate.h" + +AGSStaticObject GlobalStaticManager; +StaticGame GameStaticManager; + +const char* AGSStaticObject::GetFieldPtr(const char *address, intptr_t offset) +{ + return address + offset; +} + +void AGSStaticObject::Read(const char *address, intptr_t offset, void *dest, int size) +{ + memcpy(dest, address + offset, size); +} + +uint8_t AGSStaticObject::ReadInt8(const char *address, intptr_t offset) +{ + return *(uint8_t*)(address + offset); +} + +int16_t AGSStaticObject::ReadInt16(const char *address, intptr_t offset) +{ + return *(int16_t*)(address + offset); +} + +int32_t AGSStaticObject::ReadInt32(const char *address, intptr_t offset) +{ + return *(int32_t*)(address + offset); +} + +float AGSStaticObject::ReadFloat(const char *address, intptr_t offset) +{ + return *(float*)(address + offset); +} + +void AGSStaticObject::Write(const char *address, intptr_t offset, void *src, int size) +{ + memcpy((void*)(address + offset), src, size); +} + +void AGSStaticObject::WriteInt8(const char *address, intptr_t offset, uint8_t val) +{ + *(uint8_t*)(address + offset) = val; +} + +void AGSStaticObject::WriteInt16(const char *address, intptr_t offset, int16_t val) +{ + *(int16_t*)(address + offset) = val; +} + +void AGSStaticObject::WriteInt32(const char *address, intptr_t offset, int32_t val) +{ + *(int32_t*)(address + offset) = val; +} + +void AGSStaticObject::WriteFloat(const char *address, intptr_t offset, float val) +{ + *(float*)(address + offset) = val; +} + + +void StaticGame::WriteInt32(const char *address, intptr_t offset, int32_t val) +{ + if (offset == 4 * sizeof(int32_t)) + { // game.debug_mode + set_debug_mode(val != 0); + } + else if (offset == 99 * sizeof(int32_t) || offset == 112 * sizeof(int32_t)) + { // game.text_align, game.speech_text_align + *(int32_t*)(address + offset) = ReadScriptAlignment(val); + } + else + { + *(int32_t*)(address + offset) = val; + } +} diff --git a/engines/ags/engine/ac/statobj/agsstaticobject.h b/engines/ags/engine/ac/statobj/agsstaticobject.h new file mode 100644 index 00000000000..3a1bf4ae234 --- /dev/null +++ b/engines/ags/engine/ac/statobj/agsstaticobject.h @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_STATOBJ__AGSSTATICOBJECT_H +#define __AGS_EE_STATOBJ__AGSSTATICOBJECT_H + +#include "ac/statobj/staticobject.h" + +struct AGSStaticObject : public ICCStaticObject { + ~AGSStaticObject() override = default; + + // Legacy support for reading and writing object values by their relative offset + const char* GetFieldPtr(const char *address, intptr_t offset) override; + void Read(const char *address, intptr_t offset, void *dest, int size) override; + uint8_t ReadInt8(const char *address, intptr_t offset) override; + int16_t ReadInt16(const char *address, intptr_t offset) override; + int32_t ReadInt32(const char *address, intptr_t offset) override; + float ReadFloat(const char *address, intptr_t offset) override; + void Write(const char *address, intptr_t offset, void *src, int size) override; + void WriteInt8(const char *address, intptr_t offset, uint8_t val) override; + void WriteInt16(const char *address, intptr_t offset, int16_t val) override; + void WriteInt32(const char *address, intptr_t offset, int32_t val) override; + void WriteFloat(const char *address, intptr_t offset, float val) override; +}; + +// Wrapper around script's "Game" struct, managing access to its variables +struct StaticGame : public AGSStaticObject { + void WriteInt32(const char *address, intptr_t offset, int32_t val) override; +}; + +extern AGSStaticObject GlobalStaticManager; +extern StaticGame GameStaticManager; + +#endif // __AGS_EE_STATOBJ__AGSSTATICOBJECT_H diff --git a/engines/ags/engine/ac/statobj/staticarray.cpp b/engines/ags/engine/ac/statobj/staticarray.cpp new file mode 100644 index 00000000000..004ff29f914 --- /dev/null +++ b/engines/ags/engine/ac/statobj/staticarray.cpp @@ -0,0 +1,196 @@ + +#include +#include "ac/statobj/staticarray.h" +#include "ac/dynobj/cc_dynamicobject.h" + +void StaticArray::Create(int elem_legacy_size, int elem_real_size, int elem_count) +{ + _staticMgr = nullptr; + _dynamicMgr = nullptr; + _elemLegacySize = elem_legacy_size; + _elemRealSize = elem_real_size; + _elemCount = elem_count; +} + +void StaticArray::Create(ICCStaticObject *stcmgr, int elem_legacy_size, int elem_real_size, int elem_count) +{ + _staticMgr = stcmgr; + _dynamicMgr = nullptr; + _elemLegacySize = elem_legacy_size; + _elemRealSize = elem_real_size; + _elemCount = elem_count; +} + +void StaticArray::Create(ICCDynamicObject *dynmgr, int elem_legacy_size, int elem_real_size, int elem_count) +{ + _staticMgr = nullptr; + _dynamicMgr = dynmgr; + _elemLegacySize = elem_legacy_size; + _elemRealSize = elem_real_size; + _elemCount = elem_count; +} + +const char *StaticArray::GetElementPtr(const char *address, intptr_t legacy_offset) +{ + return address + (legacy_offset / _elemLegacySize) * _elemRealSize; +} + +const char* StaticArray::GetFieldPtr(const char *address, intptr_t offset) +{ + return GetElementPtr(address, offset); +} + +void StaticArray::Read(const char *address, intptr_t offset, void *dest, int size) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->Read(el_ptr, offset % _elemLegacySize, dest, size); + } + else if (_dynamicMgr) + { + return _dynamicMgr->Read(el_ptr, offset % _elemLegacySize, dest, size); + } + memcpy(dest, el_ptr + offset % _elemLegacySize, size); +} + +uint8_t StaticArray::ReadInt8(const char *address, intptr_t offset) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->ReadInt8(el_ptr, offset % _elemLegacySize); + } + else if (_dynamicMgr) + { + return _dynamicMgr->ReadInt8(el_ptr, offset % _elemLegacySize); + } + return *(uint8_t*)(el_ptr + offset % _elemLegacySize); +} + +int16_t StaticArray::ReadInt16(const char *address, intptr_t offset) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->ReadInt16(el_ptr, offset % _elemLegacySize); + } + else if (_dynamicMgr) + { + return _dynamicMgr->ReadInt16(el_ptr, offset % _elemLegacySize); + } + return *(uint16_t*)(el_ptr + offset % _elemLegacySize); +} + +int32_t StaticArray::ReadInt32(const char *address, intptr_t offset) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->ReadInt32(el_ptr, offset % _elemLegacySize); + } + else if (_dynamicMgr) + { + return _dynamicMgr->ReadInt32(el_ptr, offset % _elemLegacySize); + } + return *(uint32_t*)(el_ptr + offset % _elemLegacySize); +} + +float StaticArray::ReadFloat(const char *address, intptr_t offset) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->ReadFloat(el_ptr, offset % _elemLegacySize); + } + else if (_dynamicMgr) + { + return _dynamicMgr->ReadFloat(el_ptr, offset % _elemLegacySize); + } + return *(float*)(el_ptr + offset % _elemLegacySize); +} + +void StaticArray::Write(const char *address, intptr_t offset, void *src, int size) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->Write(el_ptr, offset % _elemLegacySize, src, size); + } + else if (_dynamicMgr) + { + return _dynamicMgr->Write(el_ptr, offset % _elemLegacySize, src, size); + } + else + { + memcpy((void*)(el_ptr + offset % _elemLegacySize), src, size); + } +} + +void StaticArray::WriteInt8(const char *address, intptr_t offset, uint8_t val) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->WriteInt8(el_ptr, offset % _elemLegacySize, val); + } + else if (_dynamicMgr) + { + return _dynamicMgr->WriteInt8(el_ptr, offset % _elemLegacySize, val); + } + else + { + *(uint8_t*)(el_ptr + offset % _elemLegacySize) = val; + } +} + +void StaticArray::WriteInt16(const char *address, intptr_t offset, int16_t val) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->WriteInt16(el_ptr, offset % _elemLegacySize, val); + } + else if (_dynamicMgr) + { + return _dynamicMgr->WriteInt16(el_ptr, offset % _elemLegacySize, val); + } + else + { + *(uint16_t*)(el_ptr + offset % _elemLegacySize) = val; + } +} + +void StaticArray::WriteInt32(const char *address, intptr_t offset, int32_t val) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->WriteInt32(el_ptr, offset % _elemLegacySize, val); + } + else if (_dynamicMgr) + { + return _dynamicMgr->WriteInt32(el_ptr, offset % _elemLegacySize, val); + } + else + { + *(uint32_t*)(el_ptr + offset % _elemLegacySize) = val; + } +} + +void StaticArray::WriteFloat(const char *address, intptr_t offset, float val) +{ + const char *el_ptr = GetElementPtr(address, offset); + if (_staticMgr) + { + return _staticMgr->WriteFloat(el_ptr, offset % _elemLegacySize, val); + } + else if (_dynamicMgr) + { + return _dynamicMgr->WriteFloat(el_ptr, offset % _elemLegacySize, val); + } + else + { + *(float*)(el_ptr + offset % _elemLegacySize) = val; + } +} diff --git a/engines/ags/engine/ac/statobj/staticarray.h b/engines/ags/engine/ac/statobj/staticarray.h new file mode 100644 index 00000000000..4c25d3657dd --- /dev/null +++ b/engines/ags/engine/ac/statobj/staticarray.h @@ -0,0 +1,64 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_STATOBJ__STATICARRAY_H +#define __AGS_EE_STATOBJ__STATICARRAY_H + +#include "ac/statobj/staticobject.h" + +struct ICCDynamicObject; + +struct StaticArray : public ICCStaticObject { +public: + ~StaticArray() override = default; + + void Create(int elem_legacy_size, int elem_real_size, int elem_count = -1 /*unknown*/); + void Create(ICCStaticObject *stcmgr, int elem_legacy_size, int elem_real_size, int elem_count = -1 /*unknown*/); + void Create(ICCDynamicObject *dynmgr, int elem_legacy_size, int elem_real_size, int elem_count = -1 /*unknown*/); + + inline ICCStaticObject *GetStaticManager() const + { + return _staticMgr; + } + inline ICCDynamicObject *GetDynamicManager() const + { + return _dynamicMgr; + } + // Legacy support for reading and writing object values by their relative offset + virtual const char *GetElementPtr(const char *address, intptr_t legacy_offset); + + const char* GetFieldPtr(const char *address, intptr_t offset) override; + void Read(const char *address, intptr_t offset, void *dest, int size) override; + uint8_t ReadInt8(const char *address, intptr_t offset) override; + int16_t ReadInt16(const char *address, intptr_t offset) override; + int32_t ReadInt32(const char *address, intptr_t offset) override; + float ReadFloat(const char *address, intptr_t offset) override; + void Write(const char *address, intptr_t offset, void *src, int size) override; + void WriteInt8(const char *address, intptr_t offset, uint8_t val) override; + void WriteInt16(const char *address, intptr_t offset, int16_t val) override; + void WriteInt32(const char *address, intptr_t offset, int32_t val) override; + void WriteFloat(const char *address, intptr_t offset, float val) override; + +private: + ICCStaticObject *_staticMgr; + ICCDynamicObject *_dynamicMgr; + int _elemLegacySize; + int _elemRealSize; + int _elemCount; +}; + +#endif // __AGS_EE_STATOBJ__STATICOBJECT_H diff --git a/engines/ags/engine/ac/statobj/staticobject.h b/engines/ags/engine/ac/statobj/staticobject.h new file mode 100644 index 00000000000..6c45a417262 --- /dev/null +++ b/engines/ags/engine/ac/statobj/staticobject.h @@ -0,0 +1,42 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// A stub class for "managing" static global objects exported to script. +// This may be temporary solution (oh no, not again :P) that could be +// replaced by the use of dynamic objects in the future. +// +//============================================================================= +#ifndef __AGS_EE_STATOBJ__STATICOBJECT_H +#define __AGS_EE_STATOBJ__STATICOBJECT_H + +#include "core/types.h" + +struct ICCStaticObject { + virtual ~ICCStaticObject() = default; + + // Legacy support for reading and writing object values by their relative offset + virtual const char* GetFieldPtr(const char *address, intptr_t offset) = 0; + virtual void Read(const char *address, intptr_t offset, void *dest, int size)= 0; + virtual uint8_t ReadInt8(const char *address, intptr_t offset) = 0; + virtual int16_t ReadInt16(const char *address, intptr_t offset) = 0; + virtual int32_t ReadInt32(const char *address, intptr_t offset) = 0; + virtual float ReadFloat(const char *address, intptr_t offset) = 0; + virtual void Write(const char *address, intptr_t offset, void *src, int size)= 0; + virtual void WriteInt8(const char *address, intptr_t offset, uint8_t val) = 0; + virtual void WriteInt16(const char *address, intptr_t offset, int16_t val) = 0; + virtual void WriteInt32(const char *address, intptr_t offset, int32_t val) = 0; + virtual void WriteFloat(const char *address, intptr_t offset, float val) = 0; +}; + +#endif // __AGS_EE_STATOBJ__STATICOBJECT_H diff --git a/engines/ags/engine/ac/string.cpp b/engines/ags/engine/ac/string.cpp new file mode 100644 index 00000000000..0c1068ffea2 --- /dev/null +++ b/engines/ags/engine/ac/string.cpp @@ -0,0 +1,491 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/string.h" +#include "ac/common.h" +#include "ac/display.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_translation.h" +#include "ac/runtime_defines.h" +#include "ac/dynobj/scriptstring.h" +#include "font/fonts.h" +#include "debug/debug_log.h" +#include "script/runtimescriptvalue.h" +#include "util/string_compat.h" + +extern GameSetupStruct game; +extern GameState play; +extern int longestline; +extern ScriptString myScriptStringImpl; + +int String_IsNullOrEmpty(const char *thisString) +{ + if ((thisString == nullptr) || (thisString[0] == 0)) + return 1; + + return 0; +} + +const char* String_Copy(const char *srcString) { + return CreateNewScriptString(srcString); +} + +const char* String_Append(const char *thisString, const char *extrabit) { + char *buffer = (char*)malloc(strlen(thisString) + strlen(extrabit) + 1); + strcpy(buffer, thisString); + strcat(buffer, extrabit); + return CreateNewScriptString(buffer, false); +} + +const char* String_AppendChar(const char *thisString, char extraOne) { + char *buffer = (char*)malloc(strlen(thisString) + 2); + sprintf(buffer, "%s%c", thisString, extraOne); + return CreateNewScriptString(buffer, false); +} + +const char* String_ReplaceCharAt(const char *thisString, int index, char newChar) { + if ((index < 0) || (index >= (int)strlen(thisString))) + quit("!String.ReplaceCharAt: index outside range of string"); + + char *buffer = (char*)malloc(strlen(thisString) + 1); + strcpy(buffer, thisString); + buffer[index] = newChar; + return CreateNewScriptString(buffer, false); +} + +const char* String_Truncate(const char *thisString, int length) { + if (length < 0) + quit("!String.Truncate: invalid length"); + + if (length >= (int)strlen(thisString)) + { + return thisString; + } + + char *buffer = (char*)malloc(length + 1); + strncpy(buffer, thisString, length); + buffer[length] = 0; + return CreateNewScriptString(buffer, false); +} + +const char* String_Substring(const char *thisString, int index, int length) { + if (length < 0) + quit("!String.Substring: invalid length"); + if ((index < 0) || (index > (int)strlen(thisString))) + quit("!String.Substring: invalid index"); + + char *buffer = (char*)malloc(length + 1); + strncpy(buffer, &thisString[index], length); + buffer[length] = 0; + return CreateNewScriptString(buffer, false); +} + +int String_CompareTo(const char *thisString, const char *otherString, bool caseSensitive) { + + if (caseSensitive) { + return strcmp(thisString, otherString); + } + else { + return ags_stricmp(thisString, otherString); + } +} + +int String_StartsWith(const char *thisString, const char *checkForString, bool caseSensitive) { + + if (caseSensitive) { + return (strncmp(thisString, checkForString, strlen(checkForString)) == 0) ? 1 : 0; + } + else { + return (ags_strnicmp(thisString, checkForString, strlen(checkForString)) == 0) ? 1 : 0; + } +} + +int String_EndsWith(const char *thisString, const char *checkForString, bool caseSensitive) { + + int checkAtOffset = strlen(thisString) - strlen(checkForString); + + if (checkAtOffset < 0) + { + return 0; + } + + if (caseSensitive) + { + return (strcmp(&thisString[checkAtOffset], checkForString) == 0) ? 1 : 0; + } + else + { + return (ags_stricmp(&thisString[checkAtOffset], checkForString) == 0) ? 1 : 0; + } +} + +const char* String_Replace(const char *thisString, const char *lookForText, const char *replaceWithText, bool caseSensitive) +{ + char resultBuffer[STD_BUFFER_SIZE] = ""; + int thisStringLen = (int)strlen(thisString); + int outputSize = 0; + for (int i = 0; i < thisStringLen; i++) + { + bool matchHere = false; + if (caseSensitive) + { + matchHere = (strncmp(&thisString[i], lookForText, strlen(lookForText)) == 0); + } + else + { + matchHere = (ags_strnicmp(&thisString[i], lookForText, strlen(lookForText)) == 0); + } + + if (matchHere) + { + strcpy(&resultBuffer[outputSize], replaceWithText); + outputSize += strlen(replaceWithText); + i += strlen(lookForText) - 1; + } + else + { + resultBuffer[outputSize] = thisString[i]; + outputSize++; + } + } + + resultBuffer[outputSize] = 0; + + return CreateNewScriptString(resultBuffer, true); +} + +const char* String_LowerCase(const char *thisString) { + char *buffer = (char*)malloc(strlen(thisString) + 1); + strcpy(buffer, thisString); + ags_strlwr(buffer); + return CreateNewScriptString(buffer, false); +} + +const char* String_UpperCase(const char *thisString) { + char *buffer = (char*)malloc(strlen(thisString) + 1); + strcpy(buffer, thisString); + ags_strupr(buffer); + return CreateNewScriptString(buffer, false); +} + +int String_GetChars(const char *texx, int index) { + if ((index < 0) || (index >= (int)strlen(texx))) + return 0; + return texx[index]; +} + +int StringToInt(const char*stino) { + return atoi(stino); +} + +int StrContains (const char *s1, const char *s2) { + VALIDATE_STRING (s1); + VALIDATE_STRING (s2); + char *tempbuf1 = (char*)malloc(strlen(s1) + 1); + char *tempbuf2 = (char*)malloc(strlen(s2) + 1); + strcpy(tempbuf1, s1); + strcpy(tempbuf2, s2); + ags_strlwr(tempbuf1); + ags_strlwr(tempbuf2); + + char *offs = strstr (tempbuf1, tempbuf2); + free(tempbuf1); + free(tempbuf2); + + if (offs == nullptr) + return -1; + + return (offs - tempbuf1); +} + +//============================================================================= + +const char *CreateNewScriptString(const char *fromText, bool reAllocate) { + return (const char*)CreateNewScriptStringObj(fromText, reAllocate).second; +} + +DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate) +{ + ScriptString *str; + if (reAllocate) { + str = new ScriptString(fromText); + } + else { + str = new ScriptString(); + str->text = (char*)fromText; + } + void *obj_ptr = str->text; + int32_t handle = ccRegisterManagedObject(obj_ptr, str); + if (handle == 0) + { + delete str; + return DynObjectRef(0, nullptr); + } + return DynObjectRef(handle, obj_ptr); +} + +size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines) { + if (fonnt == -1) + fonnt = play.normal_font; + + // char sofar[100]; + if (todis[0]=='&') { + while ((todis[0]!=' ') & (todis[0]!=0)) todis++; + if (todis[0]==' ') todis++; + } + lines.Reset(); + longestline=0; + + // Don't attempt to display anything if the width is tiny + if (wii < 3) + return 0; + + int line_length; + + split_lines(todis, lines, wii, fonnt, max_lines); + + // Right-to-left just means reverse the text then + // write it as normal + if (game.options[OPT_RIGHTLEFTWRITE]) + for (size_t rr = 0; rr < lines.Count(); rr++) { + lines[rr].Reverse(); + line_length = wgettextwidth_compensate(lines[rr], fonnt); + if (line_length > longestline) + longestline = line_length; + } + else + for (size_t rr = 0; rr < lines.Count(); rr++) { + line_length = wgettextwidth_compensate(lines[rr], fonnt); + if (line_length > longestline) + longestline = line_length; + } + return lines.Count(); +} + +int MAXSTRLEN = MAX_MAXSTRLEN; +void check_strlen(char*ptt) { + MAXSTRLEN = MAX_MAXSTRLEN; + long charstart = (long)&game.chars[0]; + long charend = charstart + sizeof(CharacterInfo)*game.numcharacters; + if (((long)&ptt[0] >= charstart) && ((long)&ptt[0] <= charend)) + MAXSTRLEN=30; +} + +/*void GetLanguageString(int indxx,char*buffr) { +VALIDATE_STRING(buffr); +char*bptr=get_language_text(indxx); +if (bptr==NULL) strcpy(buffr,"[language string error]"); +else strncpy(buffr,bptr,199); +buffr[199]=0; +}*/ + +void my_strncpy(char *dest, const char *src, int len) { + // the normal strncpy pads out the string with zeros up to the + // max length -- we don't want that + if (strlen(src) >= (unsigned)len) { + strncpy(dest, src, len); + dest[len] = 0; + } + else + strcpy(dest, src); +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/math.h" + +// int (const char *thisString) +RuntimeScriptValue Sc_String_IsNullOrEmpty(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT_POBJ(String_IsNullOrEmpty, const char); +} + +// const char* (const char *thisString, const char *extrabit) +RuntimeScriptValue Sc_String_Append(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_POBJ(const char, const char, myScriptStringImpl, String_Append, const char); +} + +// const char* (const char *thisString, char extraOne) +RuntimeScriptValue Sc_String_AppendChar(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT(const char, const char, myScriptStringImpl, String_AppendChar); +} + +// int (const char *thisString, const char *otherString, bool caseSensitive) +RuntimeScriptValue Sc_String_CompareTo(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ_PBOOL(const char, String_CompareTo, const char); +} + +// int (const char *s1, const char *s2) +RuntimeScriptValue Sc_StrContains(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ(const char, StrContains, const char); +} + +// const char* (const char *srcString) +RuntimeScriptValue Sc_String_Copy(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(const char, const char, myScriptStringImpl, String_Copy); +} + +// int (const char *thisString, const char *checkForString, bool caseSensitive) +RuntimeScriptValue Sc_String_EndsWith(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ_PBOOL(const char, String_EndsWith, const char); +} + +// const char* (const char *texx, ...) +RuntimeScriptValue Sc_String_Format(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(String_Format, 1); + return RuntimeScriptValue().SetDynamicObject((void*)CreateNewScriptString(scsf_buffer), &myScriptStringImpl); +} + +// const char* (const char *thisString) +RuntimeScriptValue Sc_String_LowerCase(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(const char, const char, myScriptStringImpl, String_LowerCase); +} + +// const char* (const char *thisString, const char *lookForText, const char *replaceWithText, bool caseSensitive) +RuntimeScriptValue Sc_String_Replace(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_POBJ2_PBOOL(const char, const char, myScriptStringImpl, String_Replace, const char, const char); +} + +// const char* (const char *thisString, int index, char newChar) +RuntimeScriptValue Sc_String_ReplaceCharAt(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT2(const char, const char, myScriptStringImpl, String_ReplaceCharAt); +} + +// int (const char *thisString, const char *checkForString, bool caseSensitive) +RuntimeScriptValue Sc_String_StartsWith(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_POBJ_PBOOL(const char, String_StartsWith, const char); +} + +// const char* (const char *thisString, int index, int length) +RuntimeScriptValue Sc_String_Substring(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT2(const char, const char, myScriptStringImpl, String_Substring); +} + +// const char* (const char *thisString, int length) +RuntimeScriptValue Sc_String_Truncate(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ_PINT(const char, const char, myScriptStringImpl, String_Truncate); +} + +// const char* (const char *thisString) +RuntimeScriptValue Sc_String_UpperCase(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(const char, const char, myScriptStringImpl, String_UpperCase); +} + +// FLOAT_RETURN_TYPE (const char *theString); +RuntimeScriptValue Sc_StringToFloat(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_FLOAT(const char, StringToFloat); +} + +// int (char*stino) +RuntimeScriptValue Sc_StringToInt(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(const char, StringToInt); +} + +// int (const char *texx, int index) +RuntimeScriptValue Sc_String_GetChars(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT_PINT(const char, String_GetChars); +} + +RuntimeScriptValue Sc_strlen(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + ASSERT_SELF(strlen); + return RuntimeScriptValue().SetInt32(strlen((const char*)self)); +} + +//============================================================================= +// +// Exclusive API for Plugins +// +//============================================================================= + +// const char* (const char *texx, ...) +const char *ScPl_String_Format(const char *texx, ...) +{ + API_PLUGIN_SCRIPT_SPRINTF(texx); + return CreateNewScriptString(scsf_buffer); +} + + +void RegisterStringAPI() +{ + ccAddExternalStaticFunction("String::IsNullOrEmpty^1", Sc_String_IsNullOrEmpty); + ccAddExternalObjectFunction("String::Append^1", Sc_String_Append); + ccAddExternalObjectFunction("String::AppendChar^1", Sc_String_AppendChar); + ccAddExternalObjectFunction("String::CompareTo^2", Sc_String_CompareTo); + ccAddExternalObjectFunction("String::Contains^1", Sc_StrContains); + ccAddExternalObjectFunction("String::Copy^0", Sc_String_Copy); + ccAddExternalObjectFunction("String::EndsWith^2", Sc_String_EndsWith); + ccAddExternalStaticFunction("String::Format^101", Sc_String_Format); + ccAddExternalObjectFunction("String::IndexOf^1", Sc_StrContains); + ccAddExternalObjectFunction("String::LowerCase^0", Sc_String_LowerCase); + ccAddExternalObjectFunction("String::Replace^3", Sc_String_Replace); + ccAddExternalObjectFunction("String::ReplaceCharAt^2", Sc_String_ReplaceCharAt); + ccAddExternalObjectFunction("String::StartsWith^2", Sc_String_StartsWith); + ccAddExternalObjectFunction("String::Substring^2", Sc_String_Substring); + ccAddExternalObjectFunction("String::Truncate^1", Sc_String_Truncate); + ccAddExternalObjectFunction("String::UpperCase^0", Sc_String_UpperCase); + ccAddExternalObjectFunction("String::get_AsFloat", Sc_StringToFloat); + ccAddExternalObjectFunction("String::get_AsInt", Sc_StringToInt); + ccAddExternalObjectFunction("String::geti_Chars", Sc_String_GetChars); + ccAddExternalObjectFunction("String::get_Length", Sc_strlen); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("String::IsNullOrEmpty^1", (void*)String_IsNullOrEmpty); + ccAddExternalFunctionForPlugin("String::Append^1", (void*)String_Append); + ccAddExternalFunctionForPlugin("String::AppendChar^1", (void*)String_AppendChar); + ccAddExternalFunctionForPlugin("String::CompareTo^2", (void*)String_CompareTo); + ccAddExternalFunctionForPlugin("String::Contains^1", (void*)StrContains); + ccAddExternalFunctionForPlugin("String::Copy^0", (void*)String_Copy); + ccAddExternalFunctionForPlugin("String::EndsWith^2", (void*)String_EndsWith); + ccAddExternalFunctionForPlugin("String::Format^101", (void*)ScPl_String_Format); + ccAddExternalFunctionForPlugin("String::IndexOf^1", (void*)StrContains); + ccAddExternalFunctionForPlugin("String::LowerCase^0", (void*)String_LowerCase); + ccAddExternalFunctionForPlugin("String::Replace^3", (void*)String_Replace); + ccAddExternalFunctionForPlugin("String::ReplaceCharAt^2", (void*)String_ReplaceCharAt); + ccAddExternalFunctionForPlugin("String::StartsWith^2", (void*)String_StartsWith); + ccAddExternalFunctionForPlugin("String::Substring^2", (void*)String_Substring); + ccAddExternalFunctionForPlugin("String::Truncate^1", (void*)String_Truncate); + ccAddExternalFunctionForPlugin("String::UpperCase^0", (void*)String_UpperCase); + ccAddExternalFunctionForPlugin("String::get_AsFloat", (void*)StringToFloat); + ccAddExternalFunctionForPlugin("String::get_AsInt", (void*)StringToInt); + ccAddExternalFunctionForPlugin("String::geti_Chars", (void*)String_GetChars); + ccAddExternalFunctionForPlugin("String::get_Length", (void*)strlen); +} diff --git a/engines/ags/engine/ac/string.h b/engines/ags/engine/ac/string.h new file mode 100644 index 00000000000..0ae6114d2f6 --- /dev/null +++ b/engines/ags/engine/ac/string.h @@ -0,0 +1,56 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__STRING_H +#define __AGS_EE_AC__STRING_H + +#include +#include "ac/dynobj/cc_dynamicobject.h" + +// Check that a supplied buffer from a text script function was not null +#define VALIDATE_STRING(strin) if ((unsigned long)strin <= 4096) quit("!String argument was null: make sure you pass a string, not an int, as a buffer") + +int String_IsNullOrEmpty(const char *thisString); +const char* String_Copy(const char *srcString); +const char* String_Append(const char *thisString, const char *extrabit); +const char* String_AppendChar(const char *thisString, char extraOne); +const char* String_ReplaceCharAt(const char *thisString, int index, char newChar); +const char* String_Truncate(const char *thisString, int length); +const char* String_Substring(const char *thisString, int index, int length); +int String_CompareTo(const char *thisString, const char *otherString, bool caseSensitive); +int String_StartsWith(const char *thisString, const char *checkForString, bool caseSensitive); +int String_EndsWith(const char *thisString, const char *checkForString, bool caseSensitive); +const char* String_Replace(const char *thisString, const char *lookForText, const char *replaceWithText, bool caseSensitive); +const char* String_LowerCase(const char *thisString); +const char* String_UpperCase(const char *thisString); +int String_GetChars(const char *texx, int index); +int StringToInt(const char*stino); +int StrContains (const char *s1, const char *s2); + +//============================================================================= + +const char* CreateNewScriptString(const char *fromText, bool reAllocate = true); +DynObjectRef CreateNewScriptStringObj(const char *fromText, bool reAllocate = true); +class SplitLines; +// Break up the text into lines restricted by the given width; +// returns number of lines, or 0 if text cannot be split well to fit in this width. +// Does additional processing, like removal of voice-over tags and text reversal if right-to-left text display is on. +size_t break_up_text_into_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines = -1); +void check_strlen(char*ptt); +void my_strncpy(char *dest, const char *src, int len); + +#endif // __AGS_EE_AC__STRING_H diff --git a/engines/ags/engine/ac/sys_events.cpp b/engines/ags/engine/ac/sys_events.cpp new file mode 100644 index 00000000000..ddbe0d5105e --- /dev/null +++ b/engines/ags/engine/ac/sys_events.cpp @@ -0,0 +1,206 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/keycode.h" +#include "ac/mouse.h" +#include "ac/sys_events.h" +#include "device/mousew32.h" +#include "platform/base/agsplatformdriver.h" +#include "ac/timer.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern GameState play; + +extern volatile unsigned long globalTimerCounter; +extern int pluginSimulatedClick; +extern int displayed_room; +extern char check_dynamic_sprites_at_exit; + +extern void domouse(int str); +extern int mgetbutton(); +extern int misbuttondown(int buno); + +int mouse_z_was = 0; + +int ags_kbhit () { + return keypressed(); +} + +int ags_iskeypressed (int keycode) { + if (keycode >= 0 && keycode < __allegro_KEY_MAX) + { + return key[keycode] != 0; + } + return 0; +} + +int ags_misbuttondown (int but) { + return misbuttondown(but); +} + +int ags_mgetbutton() { + int result; + + if (pluginSimulatedClick > NONE) { + result = pluginSimulatedClick; + pluginSimulatedClick = NONE; + } + else { + result = mgetbutton(); + } + return result; +} + +void ags_domouse (int what) { + // do mouse is "update the mouse x,y and also the cursor position", unless DOMOUSE_NOCURSOR is set. + if (what == DOMOUSE_NOCURSOR) + mgetgraphpos(); + else + domouse(what); +} + +int ags_check_mouse_wheel () { + int result = 0; + if ((mouse_z != mouse_z_was) && (game.options[OPT_MOUSEWHEEL] != 0)) { + if (mouse_z > mouse_z_was) + result = 1; + else + result = -1; + mouse_z_was = mouse_z; + } + return result; +} + +int ags_getch() { + const int read_key_value = readkey(); + int gott = read_key_value; + const int scancode = ((gott >> 8) & 0x00ff); + const int ascii = (gott & 0x00ff); + + bool is_extended = (ascii == EXTENDED_KEY_CODE); + // On macos, the extended keycode is the ascii character '?' or '\0' if alt-key + // so check it's not actually the character '?' + #if AGS_PLATFORM_OS_MACOS && ! AGS_PLATFORM_OS_IOS + is_extended = is_extended || ((ascii == EXTENDED_KEY_CODE_MACOS) && (scancode != __allegro_KEY_SLASH)); + #endif + + /* char message[200]; + sprintf(message, "Scancode: %04X", gott); + Debug::Printf(message);*/ + + /*if ((scancode >= KEY_0_PAD) && (scancode <= KEY_9_PAD)) { + // fix numeric pad keys if numlock is off (allegro 4.2 changed this behaviour) + if ((key_shifts & KB_NUMLOCK_FLAG) == 0) + gott = (gott & 0xff00) | EXTENDED_KEY_CODE; + }*/ + + if (gott == READKEY_CODE_ALT_TAB) + { + // Alt+Tab, it gets stuck down unless we do this + gott = AGS_KEYCODE_ALT_TAB; + } + #if AGS_PLATFORM_OS_MACOS + else if (scancode == __allegro_KEY_BACKSPACE) + { + gott = eAGSKeyCodeBackspace; + } + #endif + else if (is_extended) + { + + // I believe we rely on a lot of keys being converted to ASCII, which is why + // the complete scan code list is not here. + + switch(scancode) + { + case __allegro_KEY_F1 : gott = eAGSKeyCodeF1 ; break; + case __allegro_KEY_F2 : gott = eAGSKeyCodeF2 ; break; + case __allegro_KEY_F3 : gott = eAGSKeyCodeF3 ; break; + case __allegro_KEY_F4 : gott = eAGSKeyCodeF4 ; break; + case __allegro_KEY_F5 : gott = eAGSKeyCodeF5 ; break; + case __allegro_KEY_F6 : gott = eAGSKeyCodeF6 ; break; + case __allegro_KEY_F7 : gott = eAGSKeyCodeF7 ; break; + case __allegro_KEY_F8 : gott = eAGSKeyCodeF8 ; break; + case __allegro_KEY_F9 : gott = eAGSKeyCodeF9 ; break; + case __allegro_KEY_F10 : gott = eAGSKeyCodeF10 ; break; + case __allegro_KEY_F11 : gott = eAGSKeyCodeF11 ; break; + case __allegro_KEY_F12 : gott = eAGSKeyCodeF12 ; break; + + case __allegro_KEY_INSERT : gott = eAGSKeyCodeInsert ; break; + case __allegro_KEY_DEL : gott = eAGSKeyCodeDelete ; break; + case __allegro_KEY_HOME : gott = eAGSKeyCodeHome ; break; + case __allegro_KEY_END : gott = eAGSKeyCodeEnd ; break; + case __allegro_KEY_PGUP : gott = eAGSKeyCodePageUp ; break; + case __allegro_KEY_PGDN : gott = eAGSKeyCodePageDown ; break; + case __allegro_KEY_LEFT : gott = eAGSKeyCodeLeftArrow ; break; + case __allegro_KEY_RIGHT : gott = eAGSKeyCodeRightArrow ; break; + case __allegro_KEY_UP : gott = eAGSKeyCodeUpArrow ; break; + case __allegro_KEY_DOWN : gott = eAGSKeyCodeDownArrow ; break; + + case __allegro_KEY_0_PAD : gott = eAGSKeyCodeInsert ; break; + case __allegro_KEY_1_PAD : gott = eAGSKeyCodeEnd ; break; + case __allegro_KEY_2_PAD : gott = eAGSKeyCodeDownArrow ; break; + case __allegro_KEY_3_PAD : gott = eAGSKeyCodePageDown ; break; + case __allegro_KEY_4_PAD : gott = eAGSKeyCodeLeftArrow ; break; + case __allegro_KEY_5_PAD : gott = eAGSKeyCodeNumPad5 ; break; + case __allegro_KEY_6_PAD : gott = eAGSKeyCodeRightArrow ; break; + case __allegro_KEY_7_PAD : gott = eAGSKeyCodeHome ; break; + case __allegro_KEY_8_PAD : gott = eAGSKeyCodeUpArrow ; break; + case __allegro_KEY_9_PAD : gott = eAGSKeyCodePageUp ; break; + case __allegro_KEY_DEL_PAD : gott = eAGSKeyCodeDelete ; break; + + default: + // no meaningful mappings + // this is how we accidentally got the alt-key mappings + gott = scancode + AGS_EXT_KEY_SHIFT; + } + } + else + { + // this includes ascii characters and ctrl-A-Z + gott = ascii; + } + + // Alt+X, abort (but only once game is loaded) + if ((gott == play.abort_key) && (displayed_room >= 0)) { + check_dynamic_sprites_at_exit = 0; + quit("!|"); + } + + //sprintf(message, "Keypress: %d", gott); + //Debug::Printf(message); + + return gott; +} + +void ags_clear_input_buffer() +{ + while (ags_kbhit()) ags_getch(); + while (mgetbutton() != NONE); +} + +void ags_wait_until_keypress() +{ + while (!ags_kbhit()) { + platform->YieldCPU(); + } + ags_getch(); +} diff --git a/engines/ags/engine/ac/sys_events.h b/engines/ags/engine/ac/sys_events.h new file mode 100644 index 00000000000..bc7dfee9d05 --- /dev/null +++ b/engines/ags/engine/ac/sys_events.h @@ -0,0 +1,36 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__SYS_EVENTS_H +#define __AGS_EE_AC__SYS_EVENTS_H + +int ags_getch (); +int ags_kbhit (); +int ags_iskeypressed (int keycode); + +int ags_misbuttondown (int but); +int ags_mgetbutton(); +void ags_domouse (int what); +int ags_check_mouse_wheel (); + +// Clears buffered keypresses and mouse clicks, if any +void ags_clear_input_buffer(); +// Halts execution until any user input +// TODO: seriously not a good design, replace with event listening +void ags_wait_until_keypress(); + +#endif // __AGS_EE_AC__SYS_EVENTS_H diff --git a/engines/ags/engine/ac/system.cpp b/engines/ags/engine/ac/system.cpp new file mode 100644 index 00000000000..c03004c5b77 --- /dev/null +++ b/engines/ags/engine/ac/system.cpp @@ -0,0 +1,467 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/mouse.h" +#include "ac/string.h" +#include "ac/system.h" +#include "ac/dynobj/scriptsystem.h" +#include "debug/debug_log.h" +#include "debug/out.h" +#include "main/engine.h" +#include "main/main.h" +#include "gfx/graphicsdriver.h" +#include "ac/dynobj/cc_audiochannel.h" +#include "main/graphics_mode.h" +#include "ac/global_debug.h" +#include "ac/global_translation.h" +#include "media/audio/audio_system.h" +#include "util/string_compat.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern GameSetup usetup; +extern GameState play; +extern ScriptAudioChannel scrAudioChannel[MAX_SOUND_CHANNELS + 1]; +extern ScriptSystem scsystem; +extern IGraphicsDriver *gfxDriver; +extern CCAudioChannel ccDynamicAudio; +extern volatile bool switched_away; + +bool System_HasInputFocus() +{ + return !switched_away; +} + +int System_GetColorDepth() { + return scsystem.coldepth; +} + +int System_GetOS() { + return scsystem.os; +} + +// [IKM] 2014-09-21 +// IMPORTANT NOTE on System.ScreenWidth and System.ScreenHeight: +// It appears that in AGS these properties were not defining actual window size +// in pixels, but rather game frame size, which could include black borders, +// in 'native' (unscaled) pixels. This was due the specifics of how graphics +// modes were implemented in previous versions. +// +// Quote from the old manual: +// "Returns the actual screen width that the game is running at. If a graphic +// filter is in use, the resolution returned will be that before any +// stretching by the filter has been applied. If widescreen side borders are +// enabled, the screen width reported will include the size of these borders." +// +// The key words are "the resolution returned will be that BEFORE any +// stretching by the filter has been applied". +// +// Since now the letterbox and pillarbox borders are handled by graphics +// renderer and are not part of the game anymore, these properties should +// return strictly native game size. This is required for backwards +// compatibility. +// +int System_GetScreenWidth() { + return game.GetGameRes().Width; +} + +int System_GetScreenHeight() { + return game.GetGameRes().Height; +} + +int System_GetViewportHeight() { + return game_to_data_coord(play.GetMainViewport().GetHeight()); +} + +int System_GetViewportWidth() { + return game_to_data_coord(play.GetMainViewport().GetWidth()); +} + +const char *System_GetVersion() { + return CreateNewScriptString(EngineVersion.LongString); +} + +int System_GetHardwareAcceleration() +{ + return gfxDriver->HasAcceleratedTransform() ? 1 : 0; +} + +int System_GetNumLock() +{ + return (key_shifts & KB_NUMLOCK_FLAG) ? 1 : 0; +} + +int System_GetCapsLock() +{ + return (key_shifts & KB_CAPSLOCK_FLAG) ? 1 : 0; +} + +int System_GetScrollLock() +{ + return (key_shifts & KB_SCROLOCK_FLAG) ? 1 : 0; +} + +void System_SetNumLock(int newValue) +{ + // doesn't work ... maybe allegro doesn't implement this on windows + int ledState = key_shifts & (KB_SCROLOCK_FLAG | KB_CAPSLOCK_FLAG); + if (newValue) + { + ledState |= KB_NUMLOCK_FLAG; + } + set_leds(ledState); +} + +int System_GetVsync() { + return scsystem.vsync; +} + +void System_SetVsync(int newValue) { + if(ags_stricmp(gfxDriver->GetDriverID(), "D3D9") != 0) + scsystem.vsync = newValue; +} + +int System_GetWindowed() { + return scsystem.windowed; +} + +void System_SetWindowed(int windowed) +{ + if (windowed != scsystem.windowed) + engine_try_switch_windowed_gfxmode(); +} + +int System_GetSupportsGammaControl() { + return gfxDriver->SupportsGammaControl(); +} + +int System_GetGamma() { + return play.gamma_adjustment; +} + +void System_SetGamma(int newValue) { + if ((newValue < 0) || (newValue > 200)) + quitprintf("!System.Gamma: value must be between 0-200 (not %d)", newValue); + + if (play.gamma_adjustment != newValue) { + debug_script_log("Gamma control set to %d", newValue); + play.gamma_adjustment = newValue; + + if (gfxDriver->SupportsGammaControl()) + gfxDriver->SetGamma(newValue); + } +} + +int System_GetAudioChannelCount() +{ + return MAX_SOUND_CHANNELS; +} + +ScriptAudioChannel* System_GetAudioChannels(int index) +{ + if ((index < 0) || (index >= MAX_SOUND_CHANNELS)) + quit("!System.AudioChannels: invalid sound channel index"); + + return &scrAudioChannel[index]; +} + +int System_GetVolume() +{ + return play.digital_master_volume; +} + +void System_SetVolume(int newvol) +{ + if ((newvol < 0) || (newvol > 100)) + quit("!System.Volume: invalid volume - must be from 0-100"); + + if (newvol == play.digital_master_volume) + return; + + play.digital_master_volume = newvol; + set_volume((newvol * 255) / 100, (newvol * 255) / 100); + + // allegro's set_volume can lose the volumes of all the channels + // if it was previously set low; so restore them + AudioChannelsLock lock; + for (int i = 0; i <= MAX_SOUND_CHANNELS; i++) + { + auto* ch = lock.GetChannelIfPlaying(i); + if (ch) + ch->adjust_volume(); + } +} + +const char* System_GetRuntimeInfo() +{ + String runtimeInfo = GetRuntimeInfo(); + + return CreateNewScriptString(runtimeInfo.GetCStr()); +} + +int System_GetRenderAtScreenResolution() +{ + return usetup.RenderAtScreenRes; +} + +void System_SetRenderAtScreenResolution(int enable) +{ + usetup.RenderAtScreenRes = enable != 0; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// int () +RuntimeScriptValue Sc_System_GetAudioChannelCount(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetAudioChannelCount); +} + +// ScriptAudioChannel* (int index) +RuntimeScriptValue Sc_System_GetAudioChannels(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ_PINT(ScriptAudioChannel, ccDynamicAudio, System_GetAudioChannels); +} + +// int () +RuntimeScriptValue Sc_System_GetCapsLock(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetCapsLock); +} + +// int () +RuntimeScriptValue Sc_System_GetColorDepth(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetColorDepth); +} + +// int () +RuntimeScriptValue Sc_System_GetGamma(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetGamma); +} + +// void (int newValue) +RuntimeScriptValue Sc_System_SetGamma(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(System_SetGamma); +} + +// int () +RuntimeScriptValue Sc_System_GetHardwareAcceleration(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetHardwareAcceleration); +} + +RuntimeScriptValue Sc_System_GetHasInputFocus(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_BOOL(System_HasInputFocus); +} + +// int () +RuntimeScriptValue Sc_System_GetNumLock(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetNumLock); +} + +// void (int newValue) +RuntimeScriptValue Sc_System_SetNumLock(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(System_SetNumLock); +} + +// int () +RuntimeScriptValue Sc_System_GetOS(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetOS); +} + +// int () +RuntimeScriptValue Sc_System_GetScreenHeight(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetScreenHeight); +} + +// int () +RuntimeScriptValue Sc_System_GetScreenWidth(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetScreenWidth); +} + +// int () +RuntimeScriptValue Sc_System_GetScrollLock(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetScrollLock); +} + +// int () +RuntimeScriptValue Sc_System_GetSupportsGammaControl(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetSupportsGammaControl); +} + +// const char *() +RuntimeScriptValue Sc_System_GetVersion(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ(const char, myScriptStringImpl, System_GetVersion); +} + +// int () +RuntimeScriptValue Sc_System_GetViewportHeight(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetViewportHeight); +} + +// int () +RuntimeScriptValue Sc_System_GetViewportWidth(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetViewportWidth); +} + +// int () +RuntimeScriptValue Sc_System_GetVolume(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetVolume); +} + +// void (int newvol) +RuntimeScriptValue Sc_System_SetVolume(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(System_SetVolume); +} + +// int () +RuntimeScriptValue Sc_System_GetVsync(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetVsync); +} + +// void (int newValue) +RuntimeScriptValue Sc_System_SetVsync(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(System_SetVsync); +} + +RuntimeScriptValue Sc_System_GetWindowed(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetWindowed); +} + +RuntimeScriptValue Sc_System_SetWindowed(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(System_SetWindowed); +} + +// const char *() +RuntimeScriptValue Sc_System_GetRuntimeInfo(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJ(const char, myScriptStringImpl, System_GetRuntimeInfo); +} + +RuntimeScriptValue Sc_System_GetRenderAtScreenResolution(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_INT(System_GetRenderAtScreenResolution); +} + +RuntimeScriptValue Sc_System_SetRenderAtScreenResolution(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_VOID_PINT(System_SetRenderAtScreenResolution); +} + +RuntimeScriptValue Sc_System_Log(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_SCRIPT_SPRINTF(Sc_System_Log, 2); + Debug::Printf(kDbgGroup_Script, (MessageType)params[0].IValue, "%s", scsf_buffer); + return RuntimeScriptValue((int32_t)0); +} + + + +void RegisterSystemAPI() +{ + ccAddExternalStaticFunction("System::get_AudioChannelCount", Sc_System_GetAudioChannelCount); + ccAddExternalStaticFunction("System::geti_AudioChannels", Sc_System_GetAudioChannels); + ccAddExternalStaticFunction("System::get_CapsLock", Sc_System_GetCapsLock); + ccAddExternalStaticFunction("System::get_ColorDepth", Sc_System_GetColorDepth); + ccAddExternalStaticFunction("System::get_Gamma", Sc_System_GetGamma); + ccAddExternalStaticFunction("System::set_Gamma", Sc_System_SetGamma); + ccAddExternalStaticFunction("System::get_HardwareAcceleration", Sc_System_GetHardwareAcceleration); + ccAddExternalStaticFunction("System::get_HasInputFocus", Sc_System_GetHasInputFocus); + ccAddExternalStaticFunction("System::get_NumLock", Sc_System_GetNumLock); + ccAddExternalStaticFunction("System::set_NumLock", Sc_System_SetNumLock); + ccAddExternalStaticFunction("System::get_OperatingSystem", Sc_System_GetOS); + ccAddExternalStaticFunction("System::get_RenderAtScreenResolution", Sc_System_GetRenderAtScreenResolution); + ccAddExternalStaticFunction("System::set_RenderAtScreenResolution", Sc_System_SetRenderAtScreenResolution); + ccAddExternalStaticFunction("System::get_RuntimeInfo", Sc_System_GetRuntimeInfo); + ccAddExternalStaticFunction("System::get_ScreenHeight", Sc_System_GetScreenHeight); + ccAddExternalStaticFunction("System::get_ScreenWidth", Sc_System_GetScreenWidth); + ccAddExternalStaticFunction("System::get_ScrollLock", Sc_System_GetScrollLock); + ccAddExternalStaticFunction("System::get_SupportsGammaControl", Sc_System_GetSupportsGammaControl); + ccAddExternalStaticFunction("System::get_Version", Sc_System_GetVersion); + ccAddExternalStaticFunction("SystemInfo::get_Version", Sc_System_GetVersion); + ccAddExternalStaticFunction("System::get_ViewportHeight", Sc_System_GetViewportHeight); + ccAddExternalStaticFunction("System::get_ViewportWidth", Sc_System_GetViewportWidth); + ccAddExternalStaticFunction("System::get_Volume", Sc_System_GetVolume); + ccAddExternalStaticFunction("System::set_Volume", Sc_System_SetVolume); + ccAddExternalStaticFunction("System::get_VSync", Sc_System_GetVsync); + ccAddExternalStaticFunction("System::set_VSync", Sc_System_SetVsync); + ccAddExternalStaticFunction("System::get_Windowed", Sc_System_GetWindowed); + ccAddExternalStaticFunction("System::set_Windowed", Sc_System_SetWindowed); + ccAddExternalStaticFunction("System::Log^102", Sc_System_Log); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("System::get_AudioChannelCount", (void*)System_GetAudioChannelCount); + ccAddExternalFunctionForPlugin("System::geti_AudioChannels", (void*)System_GetAudioChannels); + ccAddExternalFunctionForPlugin("System::get_CapsLock", (void*)System_GetCapsLock); + ccAddExternalFunctionForPlugin("System::get_ColorDepth", (void*)System_GetColorDepth); + ccAddExternalFunctionForPlugin("System::get_Gamma", (void*)System_GetGamma); + ccAddExternalFunctionForPlugin("System::set_Gamma", (void*)System_SetGamma); + ccAddExternalFunctionForPlugin("System::get_HardwareAcceleration", (void*)System_GetHardwareAcceleration); + ccAddExternalFunctionForPlugin("System::get_NumLock", (void*)System_GetNumLock); + ccAddExternalFunctionForPlugin("System::set_NumLock", (void*)System_SetNumLock); + ccAddExternalFunctionForPlugin("System::get_OperatingSystem", (void*)System_GetOS); + ccAddExternalFunctionForPlugin("System::get_RuntimeInfo", (void*)System_GetRuntimeInfo); + ccAddExternalFunctionForPlugin("System::get_ScreenHeight", (void*)System_GetScreenHeight); + ccAddExternalFunctionForPlugin("System::get_ScreenWidth", (void*)System_GetScreenWidth); + ccAddExternalFunctionForPlugin("System::get_ScrollLock", (void*)System_GetScrollLock); + ccAddExternalFunctionForPlugin("System::get_SupportsGammaControl", (void*)System_GetSupportsGammaControl); + ccAddExternalFunctionForPlugin("System::get_Version", (void*)System_GetVersion); + ccAddExternalFunctionForPlugin("SystemInfo::get_Version", (void*)System_GetVersion); + ccAddExternalFunctionForPlugin("System::get_ViewportHeight", (void*)System_GetViewportHeight); + ccAddExternalFunctionForPlugin("System::get_ViewportWidth", (void*)System_GetViewportWidth); + ccAddExternalFunctionForPlugin("System::get_Volume", (void*)System_GetVolume); + ccAddExternalFunctionForPlugin("System::set_Volume", (void*)System_SetVolume); + ccAddExternalFunctionForPlugin("System::get_VSync", (void*)System_GetVsync); + ccAddExternalFunctionForPlugin("System::set_VSync", (void*)System_SetVsync); + ccAddExternalFunctionForPlugin("System::get_Windowed", (void*)System_GetWindowed); +} diff --git a/engines/ags/engine/ac/system.h b/engines/ags/engine/ac/system.h new file mode 100644 index 00000000000..b1f42e884c4 --- /dev/null +++ b/engines/ags/engine/ac/system.h @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__SYSTEMAUDIO_H +#define __AGS_EE_AC__SYSTEMAUDIO_H + +#include "ac/dynobj/scriptaudiochannel.h" + +int System_GetColorDepth(); +int System_GetOS(); +int System_GetScreenWidth(); +int System_GetScreenHeight(); +int System_GetViewportHeight(); +int System_GetViewportWidth(); +const char *System_GetVersion(); +int System_GetHardwareAcceleration(); +int System_GetNumLock(); +int System_GetCapsLock(); +int System_GetScrollLock(); +void System_SetNumLock(int newValue); +int System_GetVsync(); +void System_SetVsync(int newValue); +int System_GetWindowed(); +int System_GetSupportsGammaControl(); +int System_GetGamma(); +void System_SetGamma(int newValue); +int System_GetAudioChannelCount(); +ScriptAudioChannel* System_GetAudioChannels(int index); +int System_GetVolume(); +void System_SetVolume(int newvol); +const char *System_GetRuntimeInfo(); + + +#endif // __AGS_EE_AC_SYSTEMAUDIO_H diff --git a/engines/ags/engine/ac/textbox.cpp b/engines/ags/engine/ac/textbox.cpp new file mode 100644 index 00000000000..f821b6113c4 --- /dev/null +++ b/engines/ags/engine/ac/textbox.cpp @@ -0,0 +1,171 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/textbox.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/string.h" + +extern GameSetupStruct game; + + +// ** TEXT BOX FUNCTIONS + +const char* TextBox_GetText_New(GUITextBox *texbox) { + return CreateNewScriptString(texbox->Text); +} + +void TextBox_GetText(GUITextBox *texbox, char *buffer) { + strcpy(buffer, texbox->Text); +} + +void TextBox_SetText(GUITextBox *texbox, const char *newtex) { + if (strcmp(texbox->Text, newtex)) { + texbox->Text = newtex; + guis_need_update = 1; + } +} + +int TextBox_GetTextColor(GUITextBox *guit) { + return guit->TextColor; +} + +void TextBox_SetTextColor(GUITextBox *guit, int colr) +{ + if (guit->TextColor != colr) + { + guit->TextColor = colr; + guis_need_update = 1; + } +} + +int TextBox_GetFont(GUITextBox *guit) { + return guit->Font; +} + +void TextBox_SetFont(GUITextBox *guit, int fontnum) { + if ((fontnum < 0) || (fontnum >= game.numfonts)) + quit("!SetTextBoxFont: invalid font number."); + + if (guit->Font != fontnum) { + guit->Font = fontnum; + guis_need_update = 1; + } +} + +bool TextBox_GetShowBorder(GUITextBox *guit) { + return guit->IsBorderShown(); +} + +void TextBox_SetShowBorder(GUITextBox *guit, bool on) +{ + if (guit->IsBorderShown() != on) + { + guit->SetShowBorder(on); + guis_need_update = 1; + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" +#include "ac/dynobj/scriptstring.h" + +extern ScriptString myScriptStringImpl; + +// void (GUITextBox *texbox, char *buffer) +RuntimeScriptValue Sc_TextBox_GetText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(GUITextBox, TextBox_GetText, char); +} + +// void (GUITextBox *texbox, const char *newtex) +RuntimeScriptValue Sc_TextBox_SetText(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(GUITextBox, TextBox_SetText, const char); +} + +// int (GUITextBox *guit) +RuntimeScriptValue Sc_TextBox_GetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUITextBox, TextBox_GetFont); +} + +// void (GUITextBox *guit, int fontnum) +RuntimeScriptValue Sc_TextBox_SetFont(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUITextBox, TextBox_SetFont); +} + +RuntimeScriptValue Sc_TextBox_GetShowBorder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(GUITextBox, TextBox_GetShowBorder); +} + +// void (GUITextBox *guit, int fontnum) +RuntimeScriptValue Sc_TextBox_SetShowBorder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PBOOL(GUITextBox, TextBox_SetShowBorder); +} + +// const char* (GUITextBox *texbox) +RuntimeScriptValue Sc_TextBox_GetText_New(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(GUITextBox, const char *, myScriptStringImpl, TextBox_GetText_New); +} + +// int (GUITextBox *guit) +RuntimeScriptValue Sc_TextBox_GetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(GUITextBox, TextBox_GetTextColor); +} + +// void (GUITextBox *guit, int colr) +RuntimeScriptValue Sc_TextBox_SetTextColor(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(GUITextBox, TextBox_SetTextColor); +} + + +void RegisterTextBoxAPI() +{ + ccAddExternalObjectFunction("TextBox::GetText^1", Sc_TextBox_GetText); + ccAddExternalObjectFunction("TextBox::SetText^1", Sc_TextBox_SetText); + ccAddExternalObjectFunction("TextBox::get_Font", Sc_TextBox_GetFont); + ccAddExternalObjectFunction("TextBox::set_Font", Sc_TextBox_SetFont); + ccAddExternalObjectFunction("TextBox::get_ShowBorder", Sc_TextBox_GetShowBorder); + ccAddExternalObjectFunction("TextBox::set_ShowBorder", Sc_TextBox_SetShowBorder); + ccAddExternalObjectFunction("TextBox::get_Text", Sc_TextBox_GetText_New); + ccAddExternalObjectFunction("TextBox::set_Text", Sc_TextBox_SetText); + ccAddExternalObjectFunction("TextBox::get_TextColor", Sc_TextBox_GetTextColor); + ccAddExternalObjectFunction("TextBox::set_TextColor", Sc_TextBox_SetTextColor); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("TextBox::GetText^1", (void*)TextBox_GetText); + ccAddExternalFunctionForPlugin("TextBox::SetText^1", (void*)TextBox_SetText); + ccAddExternalFunctionForPlugin("TextBox::get_Font", (void*)TextBox_GetFont); + ccAddExternalFunctionForPlugin("TextBox::set_Font", (void*)TextBox_SetFont); + ccAddExternalFunctionForPlugin("TextBox::get_Text", (void*)TextBox_GetText_New); + ccAddExternalFunctionForPlugin("TextBox::set_Text", (void*)TextBox_SetText); + ccAddExternalFunctionForPlugin("TextBox::get_TextColor", (void*)TextBox_GetTextColor); + ccAddExternalFunctionForPlugin("TextBox::set_TextColor", (void*)TextBox_SetTextColor); +} diff --git a/engines/ags/engine/ac/textbox.h b/engines/ags/engine/ac/textbox.h new file mode 100644 index 00000000000..fce247c9e6e --- /dev/null +++ b/engines/ags/engine/ac/textbox.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__TEXTBOX_H +#define __AGS_EE_AC__TEXTBOX_H + +#include "gui/guitextbox.h" + +using AGS::Common::GUITextBox; + +const char* TextBox_GetText_New(GUITextBox *texbox); +void TextBox_GetText(GUITextBox *texbox, char *buffer); +void TextBox_SetText(GUITextBox *texbox, const char *newtex); +int TextBox_GetTextColor(GUITextBox *guit); +void TextBox_SetTextColor(GUITextBox *guit, int colr); +int TextBox_GetFont(GUITextBox *guit); +void TextBox_SetFont(GUITextBox *guit, int fontnum); + +#endif // __AGS_EE_AC__TEXTBOX_H diff --git a/engines/ags/engine/ac/timer.cpp b/engines/ags/engine/ac/timer.cpp new file mode 100644 index 00000000000..5b4b01237c1 --- /dev/null +++ b/engines/ags/engine/ac/timer.cpp @@ -0,0 +1,120 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/timer.h" + +#include "core/platform.h" +#if AGS_PLATFORM_DEBUG && defined (__GNUC__) +#include +#include +#include +#endif +#include +#include "platform/base/agsplatformdriver.h" + +namespace { + +const auto MAXIMUM_FALL_BEHIND = 3; + +auto tick_duration = std::chrono::microseconds(1000000LL/40); +auto framerate_maxed = false; + +auto last_tick_time = AGS_Clock::now(); +auto next_frame_timestamp = AGS_Clock::now(); + +} + +std::chrono::microseconds GetFrameDuration() +{ + if (framerate_maxed) { + return std::chrono::microseconds(0); + } + return tick_duration; +} + +void setTimerFps(int new_fps) +{ + tick_duration = std::chrono::microseconds(1000000LL/new_fps); + framerate_maxed = new_fps >= 1000; + + last_tick_time = AGS_Clock::now(); + next_frame_timestamp = AGS_Clock::now(); +} + +bool isTimerFpsMaxed() +{ + return framerate_maxed; +} + +void WaitForNextFrame() +{ + auto now = AGS_Clock::now(); + auto frameDuration = GetFrameDuration(); + + // early exit if we're trying to maximise framerate + if (frameDuration <= std::chrono::milliseconds::zero()) { + next_frame_timestamp = now; + return; + } + + // jump ahead if we're lagging + if (next_frame_timestamp < (now - MAXIMUM_FALL_BEHIND*frameDuration)) { + next_frame_timestamp = now; + } + + auto frame_time_remaining = next_frame_timestamp - now; + if (frame_time_remaining > std::chrono::milliseconds::zero()) { + std::this_thread::sleep_for(frame_time_remaining); + } + + next_frame_timestamp += frameDuration; +} + +bool waitingForNextTick() +{ + auto now = AGS_Clock::now(); + + if (framerate_maxed) { + last_tick_time = now; + return false; + } + + auto is_lagging = (now - last_tick_time) > (MAXIMUM_FALL_BEHIND*tick_duration); + if (is_lagging) { +#if AGS_PLATFORM_DEBUG && defined (__GNUC__) + auto missed_ticks = ((now - last_tick_time)/tick_duration); + printf("Lagging! Missed %lld ticks!\n", (long long)missed_ticks); + void *array[10]; + auto size = backtrace(array, 10); + backtrace_symbols_fd(array, size, STDOUT_FILENO); + printf("\n"); +#endif + last_tick_time = now; + return false; + } + + auto next_tick_time = last_tick_time + tick_duration; + if (next_tick_time <= now) { + last_tick_time = next_tick_time; + return false; + } + + return true; +} + +void skipMissedTicks() +{ + last_tick_time = AGS_Clock::now(); + next_frame_timestamp = AGS_Clock::now(); +} diff --git a/engines/ags/engine/ac/timer.h b/engines/ags/engine/ac/timer.h new file mode 100644 index 00000000000..5ad275f7e4a --- /dev/null +++ b/engines/ags/engine/ac/timer.h @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__TIMER_H +#define __AGS_EE_AC__TIMER_H + +#include +#include + +// use high resolution clock only if we know it is monotonic/steady. +// refer to https://stackoverflow.com/a/38253266/84262 +using AGS_Clock = std::conditional< + std::chrono::high_resolution_clock::is_steady, + std::chrono::high_resolution_clock, std::chrono::steady_clock + >::type; + +extern void WaitForNextFrame(); + +// Sets real FPS to the given number of frames per second; pass 1000+ for maxed FPS mode +extern void setTimerFps(int new_fps); +// Tells whether maxed FPS mode is currently set +extern bool isTimerFpsMaxed(); +extern bool waitingForNextTick(); // store last tick time. +extern void skipMissedTicks(); // if more than N frames, just skip all, start a fresh. + +#endif // __AGS_EE_AC__TIMER_H diff --git a/engines/ags/engine/ac/topbarsettings.h b/engines/ags/engine/ac/topbarsettings.h new file mode 100644 index 00000000000..2060848b4ca --- /dev/null +++ b/engines/ags/engine/ac/topbarsettings.h @@ -0,0 +1,35 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__TOPBARSETTINGS_H +#define __AGS_EE_AC__TOPBARSETTINGS_H + +struct TopBarSettings { + int wantIt; + int height; + int font; + char text[200]; + + TopBarSettings() { + wantIt = 0; + height = 0; + font = 0; + text[0] = 0; + } +}; + +#endif // __AGS_EE_AC__TOPBARSETTINGS_H diff --git a/engines/ags/engine/ac/translation.cpp b/engines/ags/engine/ac/translation.cpp new file mode 100644 index 00000000000..2375c8cd403 --- /dev/null +++ b/engines/ags/engine/ac/translation.cpp @@ -0,0 +1,180 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/asset_helper.h" +#include "ac/common.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_game.h" +#include "ac/runtime_defines.h" +#include "ac/translation.h" +#include "ac/tree_map.h" +#include "ac/wordsdictionary.h" +#include "debug/out.h" +#include "util/misc.h" +#include "util/stream.h" +#include "core/assetmanager.h" + +using namespace AGS::Common; + +extern GameSetup usetup; +extern GameSetupStruct game; +extern GameState play; +extern char transFileName[MAX_PATH]; + + +TreeMap *transtree = nullptr; +long lang_offs_start = 0; +char transFileName[MAX_PATH] = "\0"; + +void close_translation () { + if (transtree != nullptr) { + delete transtree; + transtree = nullptr; + } +} + +bool parse_translation(Stream *language_file, String &parse_error); + +bool init_translation (const String &lang, const String &fallback_lang, bool quit_on_error) { + + if (lang.IsEmpty()) + return false; + sprintf(transFileName, "%s.tra", lang.GetCStr()); + + Stream *language_file = find_open_asset(transFileName); + if (language_file == nullptr) + { + Debug::Printf(kDbgMsg_Error, "Cannot open translation: %s", transFileName); + return false; + } + // in case it's inside a library file, record the offset + lang_offs_start = language_file->GetPosition(); + + char transsig[16] = {0}; + language_file->Read(transsig, 15); + if (strcmp(transsig, "AGSTranslation") != 0) { + Debug::Printf(kDbgMsg_Error, "Translation signature mismatch: %s", transFileName); + delete language_file; + return false; + } + + if (transtree != nullptr) + { + close_translation(); + } + transtree = new TreeMap(); + + String parse_error; + bool result = parse_translation(language_file, parse_error); + delete language_file; + + if (!result) + { + close_translation(); + parse_error.Prepend(String::FromFormat("Failed to read translation file: %s:\n", transFileName)); + if (quit_on_error) + { + parse_error.PrependChar('!'); + quit(parse_error); + } + else + { + Debug::Printf(kDbgMsg_Error, parse_error); + if (!fallback_lang.IsEmpty()) + { + Debug::Printf("Fallback to translation: %s", fallback_lang.GetCStr()); + init_translation(fallback_lang, "", false); + } + return false; + } + } + Debug::Printf("Translation initialized: %s", transFileName); + return true; +} + +bool parse_translation(Stream *language_file, String &parse_error) +{ + while (!language_file->EOS()) { + int blockType = language_file->ReadInt32(); + if (blockType == -1) + break; + // MACPORT FIX 9/6/5: remove warning + /* int blockSize = */ language_file->ReadInt32(); + + if (blockType == 1) { + char original[STD_BUFFER_SIZE], translation[STD_BUFFER_SIZE]; + while (1) { + read_string_decrypt (language_file, original, STD_BUFFER_SIZE); + read_string_decrypt (language_file, translation, STD_BUFFER_SIZE); + if ((strlen (original) < 1) && (strlen(translation) < 1)) + break; + if (language_file->EOS()) + { + parse_error = "Translation file is corrupt"; + return false; + } + transtree->addText (original, translation); + } + + } + else if (blockType == 2) { + int uidfrom; + char wasgamename[100]; + uidfrom = language_file->ReadInt32(); + read_string_decrypt (language_file, wasgamename, sizeof(wasgamename)); + if ((uidfrom != game.uniqueid) || (strcmp (wasgamename, game.gamename) != 0)) { + parse_error.Format("The translation file is not compatible with this game. The translation is designed for '%s'.", + wasgamename); + return false; + } + } + else if (blockType == 3) { + // game settings + int temp = language_file->ReadInt32(); + // normal font + if (temp >= 0) + SetNormalFont (temp); + temp = language_file->ReadInt32(); + // speech font + if (temp >= 0) + SetSpeechFont (temp); + temp = language_file->ReadInt32(); + // text direction + if (temp == 1) { + play.text_align = kHAlignLeft; + game.options[OPT_RIGHTLEFTWRITE] = 0; + } + else if (temp == 2) { + play.text_align = kHAlignRight; + game.options[OPT_RIGHTLEFTWRITE] = 1; + } + } + else + { + parse_error.Format("Unknown block type in translation file (%d).", blockType); + return false; + } + } + + if (transtree->text == nullptr) + { + parse_error = "The translation file was empty."; + return false; + } + + return true; +} diff --git a/engines/ags/engine/ac/translation.h b/engines/ags/engine/ac/translation.h new file mode 100644 index 00000000000..5494d89593d --- /dev/null +++ b/engines/ags/engine/ac/translation.h @@ -0,0 +1,28 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__TRANSLATION_H +#define __AGS_EE_AC__TRANSLATION_H + +#include "util/string.h" + +using AGS::Common::String; + +void close_translation (); +bool init_translation (const String &lang, const String &fallback_lang, bool quit_on_error); + +#endif // __AGS_EE_AC__TRANSLATION_H diff --git a/engines/ags/engine/ac/tree_map.cpp b/engines/ags/engine/ac/tree_map.cpp new file mode 100644 index 00000000000..7cf92bea51d --- /dev/null +++ b/engines/ags/engine/ac/tree_map.cpp @@ -0,0 +1,98 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "ac/common.h" +#include "ac/tree_map.h" + +TreeMap::TreeMap() { + left = nullptr; + right = nullptr; + text = nullptr; + translation = nullptr; +} + +char* TreeMap::findValue (const char* key) { + if (text == nullptr) + return nullptr; + + if (strcmp(key, text) == 0) + return translation; + //debug_script_warn("Compare: '%s' with '%s'", key, text); + + if (strcmp (key, text) < 0) { + if (left == nullptr) + return nullptr; + return left->findValue (key); + } + else { + if (right == nullptr) + return nullptr; + return right->findValue (key); + } +} + +void TreeMap::addText (const char* ntx, char *trans) { + if ((ntx == nullptr) || (ntx[0] == 0) || + ((text != nullptr) && (strcmp(ntx, text) == 0))) + // don't add if it's an empty string or if it's already here + return; + + if (text == nullptr) { + text = (char*)malloc(strlen(ntx)+1); + translation = (char*)malloc(strlen(trans)+1); + if (translation == nullptr) + quit("load_translation: out of memory"); + strcpy(text, ntx); + strcpy(translation, trans); + } + else if (strcmp(ntx, text) < 0) { + // Earlier in alphabet, add to left + if (left == nullptr) + left = new TreeMap(); + + left->addText (ntx, trans); + } + else if (strcmp(ntx, text) > 0) { + // Later in alphabet, add to right + if (right == nullptr) + right = new TreeMap(); + + right->addText (ntx, trans); + } +} + +void TreeMap::clear() { + if (left) { + left->clear(); + delete left; + } + if (right) { + right->clear(); + delete right; + } + if (text) + free(text); + if (translation) + free(translation); + left = nullptr; + right = nullptr; + text = nullptr; + translation = nullptr; +} + +TreeMap::~TreeMap() { + clear(); +} diff --git a/engines/ags/engine/ac/tree_map.h b/engines/ags/engine/ac/tree_map.h new file mode 100644 index 00000000000..c2fd0417c1d --- /dev/null +++ b/engines/ags/engine/ac/tree_map.h @@ -0,0 +1,35 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__TREEMAP_H +#define __AGS_EE_AC__TREEMAP_H + +// Binary tree structure for holding translations, allows fast +// access +struct TreeMap { + TreeMap *left, *right; + char *text; + char *translation; + + TreeMap(); + char* findValue (const char* key); + void addText (const char* ntx, char *trans); + void clear(); + ~TreeMap(); +}; + +#endif // __AGS_EE_AC__TREEMAP_H diff --git a/engines/ags/engine/ac/viewframe.cpp b/engines/ags/engine/ac/viewframe.cpp new file mode 100644 index 00000000000..baaa1ecb3d0 --- /dev/null +++ b/engines/ags/engine/ac/viewframe.cpp @@ -0,0 +1,287 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/gamesetupstruct.h" +#include "ac/viewframe.h" +#include "debug/debug_log.h" +#include "ac/spritecache.h" +#include "gfx/bitmap.h" +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_audioclip.h" +#include "ac/draw.h" +#include "ac/game_version.h" +#include "media/audio/audio_system.h" + +using AGS::Common::Bitmap; +using AGS::Common::Graphics; + +extern GameSetupStruct game; +extern ViewStruct*views; +extern SpriteCache spriteset; +extern CCAudioClip ccDynamicAudioClip; + + +int ViewFrame_GetFlipped(ScriptViewFrame *svf) { + if (views[svf->view].loops[svf->loop].frames[svf->frame].flags & VFLG_FLIPSPRITE) + return 1; + return 0; +} + +int ViewFrame_GetGraphic(ScriptViewFrame *svf) { + return views[svf->view].loops[svf->loop].frames[svf->frame].pic; +} + +void ViewFrame_SetGraphic(ScriptViewFrame *svf, int newPic) { + views[svf->view].loops[svf->loop].frames[svf->frame].pic = newPic; +} + +ScriptAudioClip* ViewFrame_GetLinkedAudio(ScriptViewFrame *svf) +{ + int soundIndex = views[svf->view].loops[svf->loop].frames[svf->frame].sound; + if (soundIndex < 0) + return nullptr; + + return &game.audioClips[soundIndex]; +} + +void ViewFrame_SetLinkedAudio(ScriptViewFrame *svf, ScriptAudioClip* clip) +{ + int newSoundIndex = -1; + if (clip != nullptr) + newSoundIndex = clip->id; + + views[svf->view].loops[svf->loop].frames[svf->frame].sound = newSoundIndex; +} + +int ViewFrame_GetSound(ScriptViewFrame *svf) { + // convert audio clip to old-style sound number + return get_old_style_number_for_sound(views[svf->view].loops[svf->loop].frames[svf->frame].sound); +} + +void ViewFrame_SetSound(ScriptViewFrame *svf, int newSound) +{ + if (newSound < 1) + { + views[svf->view].loops[svf->loop].frames[svf->frame].sound = -1; + } + else + { + // convert sound number to audio clip + ScriptAudioClip* clip = GetAudioClipForOldStyleNumber(game, false, newSound); + if (clip == nullptr) + quitprintf("!SetFrameSound: audio clip aSound%d not found", newSound); + + views[svf->view].loops[svf->loop].frames[svf->frame].sound = clip->id + (game.IsLegacyAudioSystem() ? 0x10000000 : 0); + } +} + +int ViewFrame_GetSpeed(ScriptViewFrame *svf) { + return views[svf->view].loops[svf->loop].frames[svf->frame].speed; +} + +int ViewFrame_GetView(ScriptViewFrame *svf) { + return svf->view + 1; +} + +int ViewFrame_GetLoop(ScriptViewFrame *svf) { + return svf->loop; +} + +int ViewFrame_GetFrame(ScriptViewFrame *svf) { + return svf->frame; +} + +//============================================================================= + +void precache_view(int view) +{ + if (view < 0) + return; + + for (int i = 0; i < views[view].numLoops; i++) { + for (int j = 0; j < views[view].loops[i].numFrames; j++) + spriteset.Precache(views[view].loops[i].frames[j].pic); + } +} + +// the specified frame has just appeared, see if we need +// to play a sound or whatever +void CheckViewFrame (int view, int loop, int frame, int sound_volume) { + ScriptAudioChannel *channel = nullptr; + if (game.IsLegacyAudioSystem()) + { + if (views[view].loops[loop].frames[frame].sound > 0) + { + if (views[view].loops[loop].frames[frame].sound < 0x10000000) + { + ScriptAudioClip* clip = GetAudioClipForOldStyleNumber(game, false, views[view].loops[loop].frames[frame].sound); + if (clip) + views[view].loops[loop].frames[frame].sound = clip->id + 0x10000000; + else + { + views[view].loops[loop].frames[frame].sound = 0; + return; + } + } + channel = play_audio_clip_by_index(views[view].loops[loop].frames[frame].sound - 0x10000000); + } + } + else + { + if (views[view].loops[loop].frames[frame].sound >= 0) { + // play this sound (eg. footstep) + channel = play_audio_clip_by_index(views[view].loops[loop].frames[frame].sound); + } + } + if (sound_volume != SCR_NO_VALUE && channel != nullptr) + { + AudioChannelsLock lock; + auto* ch = lock.GetChannel(channel->id); + if (ch) + ch->set_volume_percent(ch->get_volume() * sound_volume / 100); + } + +} + +// draws a view frame, flipped if appropriate +void DrawViewFrame(Bitmap *ds, const ViewFrame *vframe, int x, int y, bool alpha_blend) +{ + // NOTE: DrawViewFrame supports alpha blending only since OPT_SPRITEALPHA; + // this is why there's no sense in blending if it's not set (will do no good anyway). + if (alpha_blend && game.options[OPT_SPRITEALPHA] == kSpriteAlphaRender_Proper) + { + Bitmap *vf_bmp = spriteset[vframe->pic]; + Bitmap *src = vf_bmp; + if (vframe->flags & VFLG_FLIPSPRITE) + { + src = new Bitmap(vf_bmp->GetWidth(), vf_bmp->GetHeight(), vf_bmp->GetColorDepth()); + src->FlipBlt(vf_bmp, 0, 0, Common::kBitmap_HFlip); + } + draw_sprite_support_alpha(ds, true, x, y, src, (game.SpriteInfos[vframe->pic].Flags & SPF_ALPHACHANNEL) != 0); + if (src != vf_bmp) + delete src; + } + else + { + if (vframe->flags & VFLG_FLIPSPRITE) + ds->FlipBlt(spriteset[vframe->pic], x, y, Common::kBitmap_HFlip); + else + ds->Blit(spriteset[vframe->pic], x, y, Common::kBitmap_Transparency); + } +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "debug/out.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +// int (ScriptViewFrame *svf) +RuntimeScriptValue Sc_ViewFrame_GetFlipped(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewFrame, ViewFrame_GetFlipped); +} + +// int (ScriptViewFrame *svf) +RuntimeScriptValue Sc_ViewFrame_GetFrame(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewFrame, ViewFrame_GetFrame); +} +// int (ScriptViewFrame *svf) +RuntimeScriptValue Sc_ViewFrame_GetGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewFrame, ViewFrame_GetGraphic); +} + +// void (ScriptViewFrame *svf, int newPic) +RuntimeScriptValue Sc_ViewFrame_SetGraphic(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptViewFrame, ViewFrame_SetGraphic); +} + +// ScriptAudioClip* (ScriptViewFrame *svf) +RuntimeScriptValue Sc_ViewFrame_GetLinkedAudio(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJ(ScriptViewFrame, ScriptAudioClip, ccDynamicAudioClip, ViewFrame_GetLinkedAudio); +} + +// void (ScriptViewFrame *svf, ScriptAudioClip* clip) +RuntimeScriptValue Sc_ViewFrame_SetLinkedAudio(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(ScriptViewFrame, ViewFrame_SetLinkedAudio, ScriptAudioClip); +} + +// int (ScriptViewFrame *svf) +RuntimeScriptValue Sc_ViewFrame_GetLoop(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewFrame, ViewFrame_GetLoop); +} + +// int (ScriptViewFrame *svf) +RuntimeScriptValue Sc_ViewFrame_GetSound(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewFrame, ViewFrame_GetSound); +} + +// void (ScriptViewFrame *svf, int newSound) +RuntimeScriptValue Sc_ViewFrame_SetSound(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptViewFrame, ViewFrame_SetSound); +} + +// int (ScriptViewFrame *svf) +RuntimeScriptValue Sc_ViewFrame_GetSpeed(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewFrame, ViewFrame_GetSpeed); +} + +// int (ScriptViewFrame *svf) +RuntimeScriptValue Sc_ViewFrame_GetView(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewFrame, ViewFrame_GetView); +} + + +void RegisterViewFrameAPI() +{ + ccAddExternalObjectFunction("ViewFrame::get_Flipped", Sc_ViewFrame_GetFlipped); + ccAddExternalObjectFunction("ViewFrame::get_Frame", Sc_ViewFrame_GetFrame); + ccAddExternalObjectFunction("ViewFrame::get_Graphic", Sc_ViewFrame_GetGraphic); + ccAddExternalObjectFunction("ViewFrame::set_Graphic", Sc_ViewFrame_SetGraphic); + ccAddExternalObjectFunction("ViewFrame::get_LinkedAudio", Sc_ViewFrame_GetLinkedAudio); + ccAddExternalObjectFunction("ViewFrame::set_LinkedAudio", Sc_ViewFrame_SetLinkedAudio); + ccAddExternalObjectFunction("ViewFrame::get_Loop", Sc_ViewFrame_GetLoop); + ccAddExternalObjectFunction("ViewFrame::get_Sound", Sc_ViewFrame_GetSound); + ccAddExternalObjectFunction("ViewFrame::set_Sound", Sc_ViewFrame_SetSound); + ccAddExternalObjectFunction("ViewFrame::get_Speed", Sc_ViewFrame_GetSpeed); + ccAddExternalObjectFunction("ViewFrame::get_View", Sc_ViewFrame_GetView); + + /* ----------------------- Registering unsafe exports for plugins -----------------------*/ + + ccAddExternalFunctionForPlugin("ViewFrame::get_Flipped", (void*)ViewFrame_GetFlipped); + ccAddExternalFunctionForPlugin("ViewFrame::get_Frame", (void*)ViewFrame_GetFrame); + ccAddExternalFunctionForPlugin("ViewFrame::get_Graphic", (void*)ViewFrame_GetGraphic); + ccAddExternalFunctionForPlugin("ViewFrame::set_Graphic", (void*)ViewFrame_SetGraphic); + ccAddExternalFunctionForPlugin("ViewFrame::get_LinkedAudio", (void*)ViewFrame_GetLinkedAudio); + ccAddExternalFunctionForPlugin("ViewFrame::set_LinkedAudio", (void*)ViewFrame_SetLinkedAudio); + ccAddExternalFunctionForPlugin("ViewFrame::get_Loop", (void*)ViewFrame_GetLoop); + ccAddExternalFunctionForPlugin("ViewFrame::get_Sound", (void*)ViewFrame_GetSound); + ccAddExternalFunctionForPlugin("ViewFrame::set_Sound", (void*)ViewFrame_SetSound); + ccAddExternalFunctionForPlugin("ViewFrame::get_Speed", (void*)ViewFrame_GetSpeed); + ccAddExternalFunctionForPlugin("ViewFrame::get_View", (void*)ViewFrame_GetView); +} diff --git a/engines/ags/engine/ac/viewframe.h b/engines/ags/engine/ac/viewframe.h new file mode 100644 index 00000000000..353bfc74bbb --- /dev/null +++ b/engines/ags/engine/ac/viewframe.h @@ -0,0 +1,47 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__VIEWFRAME_H +#define __AGS_EE_AC__VIEWFRAME_H + +#include "ac/runtime_defines.h" +#include "ac/view.h" +#include "ac/dynobj/scriptaudioclip.h" +#include "ac/dynobj/scriptviewframe.h" +#include "gfx/bitmap.h" + +namespace AGS { namespace Common { class Graphics; } } +using namespace AGS; // FIXME later + +int ViewFrame_GetFlipped(ScriptViewFrame *svf); +int ViewFrame_GetGraphic(ScriptViewFrame *svf); +void ViewFrame_SetGraphic(ScriptViewFrame *svf, int newPic); +ScriptAudioClip* ViewFrame_GetLinkedAudio(ScriptViewFrame *svf); +void ViewFrame_SetLinkedAudio(ScriptViewFrame *svf, ScriptAudioClip* clip); +int ViewFrame_GetSound(ScriptViewFrame *svf); +void ViewFrame_SetSound(ScriptViewFrame *svf, int newSound); +int ViewFrame_GetSpeed(ScriptViewFrame *svf); +int ViewFrame_GetView(ScriptViewFrame *svf); +int ViewFrame_GetLoop(ScriptViewFrame *svf); +int ViewFrame_GetFrame(ScriptViewFrame *svf); + +void precache_view(int view); +void CheckViewFrame (int view, int loop, int frame, int sound_volume=SCR_NO_VALUE); +// draws a view frame, flipped if appropriate +void DrawViewFrame(Common::Bitmap *ds, const ViewFrame *vframe, int x, int y, bool alpha_blend = false); + +#endif // __AGS_EE_AC__VIEWFRAME_H diff --git a/engines/ags/engine/ac/viewport_script.cpp b/engines/ags/engine/ac/viewport_script.cpp new file mode 100644 index 00000000000..c819873ea5b --- /dev/null +++ b/engines/ags/engine/ac/viewport_script.cpp @@ -0,0 +1,527 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Viewport and Camera script API. +// +//============================================================================= + +#include "ac/dynobj/scriptcamera.h" +#include "ac/dynobj/scriptviewport.h" +#include "ac/dynobj/scriptuserobject.h" +#include "ac/draw.h" +#include "ac/gamestate.h" +#include "debug/debug_log.h" +#include "script/script_api.h" +#include "script/script_runtime.h" + +using namespace AGS::Common; + +//============================================================================= +// +// Camera script API. +// +//============================================================================= + +ScriptCamera* Camera_Create() +{ + auto cam = play.CreateRoomCamera(); + if (!cam) + return NULL; + return play.RegisterRoomCamera(cam->GetID()); +} + +void Camera_Delete(ScriptCamera *scam) +{ + play.DeleteRoomCamera(scam->GetID()); +} + +int Camera_GetX(ScriptCamera *scam) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.X: trying to use deleted camera"); return 0; } + int x = play.GetRoomCamera(scam->GetID())->GetRect().Left; + return game_to_data_coord(x); +} + +void Camera_SetX(ScriptCamera *scam, int x) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.X: trying to use deleted camera"); return; } + x = data_to_game_coord(x); + auto cam = play.GetRoomCamera(scam->GetID()); + cam->LockAt(x, cam->GetRect().Top); +} + +int Camera_GetY(ScriptCamera *scam) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.Y: trying to use deleted camera"); return 0; } + int y = play.GetRoomCamera(scam->GetID())->GetRect().Top; + return game_to_data_coord(y); +} + +void Camera_SetY(ScriptCamera *scam, int y) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.Y: trying to use deleted camera"); return; } + y = data_to_game_coord(y); + auto cam = play.GetRoomCamera(scam->GetID()); + cam->LockAt(cam->GetRect().Left, y); +} + +int Camera_GetWidth(ScriptCamera *scam) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.Width: trying to use deleted camera"); return 0; } + int width = play.GetRoomCamera(scam->GetID())->GetRect().GetWidth(); + return game_to_data_coord(width); +} + +void Camera_SetWidth(ScriptCamera *scam, int width) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.Width: trying to use deleted camera"); return; } + width = data_to_game_coord(width); + auto cam = play.GetRoomCamera(scam->GetID()); + cam->SetSize(Size(width, cam->GetRect().GetHeight())); +} + +int Camera_GetHeight(ScriptCamera *scam) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.Height: trying to use deleted camera"); return 0; } + int height = play.GetRoomCamera(scam->GetID())->GetRect().GetHeight(); + return game_to_data_coord(height); +} + +void Camera_SetHeight(ScriptCamera *scam, int height) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.Height: trying to use deleted camera"); return; } + height = data_to_game_coord(height); + auto cam = play.GetRoomCamera(scam->GetID()); + cam->SetSize(Size(cam->GetRect().GetWidth(), height)); +} + +bool Camera_GetAutoTracking(ScriptCamera *scam) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.AutoTracking: trying to use deleted camera"); return false; } + return !play.GetRoomCamera(scam->GetID())->IsLocked(); +} + +void Camera_SetAutoTracking(ScriptCamera *scam, bool on) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.AutoTracking: trying to use deleted camera"); return; } + auto cam = play.GetRoomCamera(scam->GetID()); + if (on) + cam->Release(); + else + cam->Lock(); +} + +void Camera_SetAt(ScriptCamera *scam, int x, int y) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.SetAt: trying to use deleted camera"); return; } + data_to_game_coords(&x, &y); + play.GetRoomCamera(scam->GetID())->LockAt(x, y); +} + +void Camera_SetSize(ScriptCamera *scam, int width, int height) +{ + if (scam->GetID() < 0) { debug_script_warn("Camera.SetSize: trying to use deleted camera"); return; } + data_to_game_coords(&width, &height); + play.GetRoomCamera(scam->GetID())->SetSize(Size(width, height)); +} + +RuntimeScriptValue Sc_Camera_Create(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO(ScriptCamera, Camera_Create); +} + +RuntimeScriptValue Sc_Camera_Delete(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptCamera, Camera_Delete); +} + +RuntimeScriptValue Sc_Camera_GetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptCamera, Camera_GetX); +} + +RuntimeScriptValue Sc_Camera_SetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptCamera, Camera_SetX); +} + +RuntimeScriptValue Sc_Camera_GetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptCamera, Camera_GetY); +} + +RuntimeScriptValue Sc_Camera_SetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptCamera, Camera_SetY); +} + +RuntimeScriptValue Sc_Camera_GetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptCamera, Camera_GetWidth); +} + +RuntimeScriptValue Sc_Camera_SetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptCamera, Camera_SetWidth); +} + +RuntimeScriptValue Sc_Camera_GetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptCamera, Camera_GetHeight); +} + +RuntimeScriptValue Sc_Camera_SetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptCamera, Camera_SetHeight); +} + +RuntimeScriptValue Sc_Camera_GetAutoTracking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(ScriptCamera, Camera_GetAutoTracking); +} + +RuntimeScriptValue Sc_Camera_SetAutoTracking(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PBOOL(ScriptCamera, Camera_SetAutoTracking); +} + +RuntimeScriptValue Sc_Camera_SetAt(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(ScriptCamera, Camera_SetAt); +} + +RuntimeScriptValue Sc_Camera_SetSize(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT2(ScriptCamera, Camera_SetSize); +} + + +//============================================================================= +// +// Viewport script API. +// +//============================================================================= + +ScriptViewport* Viewport_Create() +{ + auto view = play.CreateRoomViewport(); + if (!view) + return NULL; + return play.RegisterRoomViewport(view->GetID()); +} + +void Viewport_Delete(ScriptViewport *scv) +{ + play.DeleteRoomViewport(scv->GetID()); +} + +int Viewport_GetX(ScriptViewport *scv) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.X: trying to use deleted viewport"); return 0; } + int x = play.GetRoomViewport(scv->GetID())->GetRect().Left; + return game_to_data_coord(x); +} + +void Viewport_SetX(ScriptViewport *scv, int x) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.X: trying to use deleted viewport"); return; } + x = data_to_game_coord(x); + auto view = play.GetRoomViewport(scv->GetID()); + view->SetAt(x, view->GetRect().Top); +} + +int Viewport_GetY(ScriptViewport *scv) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Y: trying to use deleted viewport"); return 0; } + int y = play.GetRoomViewport(scv->GetID())->GetRect().Top; + return game_to_data_coord(y); +} + +void Viewport_SetY(ScriptViewport *scv, int y) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Y: trying to use deleted viewport"); return; } + y = data_to_game_coord(y); + auto view = play.GetRoomViewport(scv->GetID()); + view->SetAt(view->GetRect().Left, y); +} + +int Viewport_GetWidth(ScriptViewport *scv) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Width: trying to use deleted viewport"); return 0; } + int width = play.GetRoomViewport(scv->GetID())->GetRect().GetWidth(); + return game_to_data_coord(width); +} + +void Viewport_SetWidth(ScriptViewport *scv, int width) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Width: trying to use deleted viewport"); return; } + width = data_to_game_coord(width); + auto view = play.GetRoomViewport(scv->GetID()); + view->SetSize(Size(width, view->GetRect().GetHeight())); +} + +int Viewport_GetHeight(ScriptViewport *scv) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Height: trying to use deleted viewport"); return 0; } + int height = play.GetRoomViewport(scv->GetID())->GetRect().GetHeight(); + return game_to_data_coord(height); +} + +void Viewport_SetHeight(ScriptViewport *scv, int height) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Height: trying to use deleted viewport"); return; } + height = data_to_game_coord(height); + auto view = play.GetRoomViewport(scv->GetID()); + view->SetSize(Size(view->GetRect().GetWidth(), height)); +} + +ScriptCamera* Viewport_GetCamera(ScriptViewport *scv) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Camera: trying to use deleted viewport"); return nullptr; } + auto view = play.GetRoomViewport(scv->GetID()); + auto cam = view->GetCamera(); + if (!cam) + return nullptr; + return play.GetScriptCamera(cam->GetID()); +} + +void Viewport_SetCamera(ScriptViewport *scv, ScriptCamera *scam) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Camera: trying to use deleted viewport"); return; } + if (scam != nullptr && scam->GetID() < 0) { debug_script_warn("Viewport.Camera: trying to link deleted camera"); return; } + auto view = play.GetRoomViewport(scv->GetID()); + // unlink previous camera + auto cam = view->GetCamera(); + if (cam) + cam->UnlinkFromViewport(view->GetID()); + // link new one + if (scam != nullptr) + { + cam = play.GetRoomCamera(scam->GetID()); + view->LinkCamera(cam); + cam->LinkToViewport(view); + } + else + { + view->LinkCamera(nullptr); + } +} + +bool Viewport_GetVisible(ScriptViewport *scv) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Visible: trying to use deleted viewport"); return false; } + return play.GetRoomViewport(scv->GetID())->IsVisible(); +} + +void Viewport_SetVisible(ScriptViewport *scv, bool on) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.Visible: trying to use deleted viewport"); return; } + play.GetRoomViewport(scv->GetID())->SetVisible(on); +} + +int Viewport_GetZOrder(ScriptViewport *scv) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.ZOrder: trying to use deleted viewport"); return 0; } + return play.GetRoomViewport(scv->GetID())->GetZOrder(); +} + +void Viewport_SetZOrder(ScriptViewport *scv, int zorder) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.ZOrder: trying to use deleted viewport"); return; } + play.GetRoomViewport(scv->GetID())->SetZOrder(zorder); + play.InvalidateViewportZOrder(); +} + +ScriptViewport* Viewport_GetAtScreenXY(int x, int y) +{ + data_to_game_coords(&x, &y); + PViewport view = play.GetRoomViewportAt(x, y); + if (!view) + return nullptr; + return play.GetScriptViewport(view->GetID()); +} + +void Viewport_SetPosition(ScriptViewport *scv, int x, int y, int width, int height) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.SetPosition: trying to use deleted viewport"); return; } + data_to_game_coords(&x, &y); + data_to_game_coords(&width, &height); + play.GetRoomViewport(scv->GetID())->SetRect(RectWH(x, y, width, height)); +} + +ScriptUserObject *Viewport_ScreenToRoomPoint(ScriptViewport *scv, int scrx, int scry, bool clipViewport) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.ScreenToRoomPoint: trying to use deleted viewport"); return nullptr; } + data_to_game_coords(&scrx, &scry); + + VpPoint vpt = play.GetRoomViewport(scv->GetID())->ScreenToRoom(scrx, scry, clipViewport); + if (vpt.second < 0) + return nullptr; + + game_to_data_coords(vpt.first.X, vpt.first.Y); + return ScriptStructHelpers::CreatePoint(vpt.first.X, vpt.first.Y); +} + +ScriptUserObject *Viewport_RoomToScreenPoint(ScriptViewport *scv, int roomx, int roomy, bool clipViewport) +{ + if (scv->GetID() < 0) { debug_script_warn("Viewport.RoomToScreenPoint: trying to use deleted viewport"); return nullptr; } + data_to_game_coords(&roomx, &roomy); + Point pt = play.RoomToScreen(roomx, roomy); + if (clipViewport && !play.GetRoomViewport(scv->GetID())->GetRect().IsInside(pt.X, pt.Y)) + return nullptr; + + game_to_data_coords(pt.X, pt.Y); + return ScriptStructHelpers::CreatePoint(pt.X, pt.Y); +} + +RuntimeScriptValue Sc_Viewport_Create(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO(ScriptViewport, Viewport_Create); +} + +RuntimeScriptValue Sc_Viewport_Delete(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID(ScriptViewport, Viewport_Delete); +} + +RuntimeScriptValue Sc_Viewport_GetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewport, Viewport_GetX); +} + +RuntimeScriptValue Sc_Viewport_SetX(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptViewport, Viewport_SetX); +} + +RuntimeScriptValue Sc_Viewport_GetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewport, Viewport_GetY); +} + +RuntimeScriptValue Sc_Viewport_SetY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptViewport, Viewport_SetY); +} + +RuntimeScriptValue Sc_Viewport_GetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewport, Viewport_GetWidth); +} + +RuntimeScriptValue Sc_Viewport_SetWidth(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptViewport, Viewport_SetWidth); +} + +RuntimeScriptValue Sc_Viewport_GetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewport, Viewport_GetHeight); +} + +RuntimeScriptValue Sc_Viewport_SetHeight(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptViewport, Viewport_SetHeight); +} + +RuntimeScriptValue Sc_Viewport_GetCamera(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJAUTO(ScriptViewport, ScriptCamera, Viewport_GetCamera); +} + +RuntimeScriptValue Sc_Viewport_SetCamera(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_POBJ(ScriptViewport, Viewport_SetCamera, ScriptCamera); +} + +RuntimeScriptValue Sc_Viewport_GetVisible(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_BOOL(ScriptViewport, Viewport_GetVisible); +} + +RuntimeScriptValue Sc_Viewport_SetVisible(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PBOOL(ScriptViewport, Viewport_SetVisible); +} + +RuntimeScriptValue Sc_Viewport_GetZOrder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_INT(ScriptViewport, Viewport_GetZOrder); +} + +RuntimeScriptValue Sc_Viewport_SetZOrder(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT(ScriptViewport, Viewport_SetZOrder); +} + +RuntimeScriptValue Sc_Viewport_GetAtScreenXY(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT2(ScriptViewport, Viewport_GetAtScreenXY); +} + +RuntimeScriptValue Sc_Viewport_SetPosition(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_VOID_PINT4(ScriptViewport, Viewport_SetPosition); +} + +RuntimeScriptValue Sc_Viewport_ScreenToRoomPoint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJAUTO_PINT2_PBOOL(ScriptViewport, ScriptUserObject, Viewport_ScreenToRoomPoint); +} + +RuntimeScriptValue Sc_Viewport_RoomToScreenPoint(void *self, const RuntimeScriptValue *params, int32_t param_count) +{ + API_OBJCALL_OBJAUTO_PINT2_PBOOL(ScriptViewport, ScriptUserObject, Viewport_RoomToScreenPoint); +} + + + +void RegisterViewportAPI() +{ + ccAddExternalStaticFunction("Camera::Create", Sc_Camera_Create); + ccAddExternalObjectFunction("Camera::Delete", Sc_Camera_Delete); + ccAddExternalObjectFunction("Camera::get_X", Sc_Camera_GetX); + ccAddExternalObjectFunction("Camera::set_X", Sc_Camera_SetX); + ccAddExternalObjectFunction("Camera::get_Y", Sc_Camera_GetY); + ccAddExternalObjectFunction("Camera::set_Y", Sc_Camera_SetY); + ccAddExternalObjectFunction("Camera::get_Width", Sc_Camera_GetWidth); + ccAddExternalObjectFunction("Camera::set_Width", Sc_Camera_SetWidth); + ccAddExternalObjectFunction("Camera::get_Height", Sc_Camera_GetHeight); + ccAddExternalObjectFunction("Camera::set_Height", Sc_Camera_SetHeight); + ccAddExternalObjectFunction("Camera::get_AutoTracking", Sc_Camera_GetAutoTracking); + ccAddExternalObjectFunction("Camera::set_AutoTracking", Sc_Camera_SetAutoTracking); + ccAddExternalObjectFunction("Camera::SetAt", Sc_Camera_SetAt); + ccAddExternalObjectFunction("Camera::SetSize", Sc_Camera_SetSize); + + ccAddExternalStaticFunction("Viewport::Create", Sc_Viewport_Create); + ccAddExternalObjectFunction("Viewport::Delete", Sc_Viewport_Delete); + ccAddExternalObjectFunction("Viewport::get_X", Sc_Viewport_GetX); + ccAddExternalObjectFunction("Viewport::set_X", Sc_Viewport_SetX); + ccAddExternalObjectFunction("Viewport::get_Y", Sc_Viewport_GetY); + ccAddExternalObjectFunction("Viewport::set_Y", Sc_Viewport_SetY); + ccAddExternalObjectFunction("Viewport::get_Width", Sc_Viewport_GetWidth); + ccAddExternalObjectFunction("Viewport::set_Width", Sc_Viewport_SetWidth); + ccAddExternalObjectFunction("Viewport::get_Height", Sc_Viewport_GetHeight); + ccAddExternalObjectFunction("Viewport::set_Height", Sc_Viewport_SetHeight); + ccAddExternalObjectFunction("Viewport::get_Camera", Sc_Viewport_GetCamera); + ccAddExternalObjectFunction("Viewport::set_Camera", Sc_Viewport_SetCamera); + ccAddExternalObjectFunction("Viewport::get_Visible", Sc_Viewport_GetVisible); + ccAddExternalObjectFunction("Viewport::set_Visible", Sc_Viewport_SetVisible); + ccAddExternalObjectFunction("Viewport::get_ZOrder", Sc_Viewport_GetZOrder); + ccAddExternalObjectFunction("Viewport::set_ZOrder", Sc_Viewport_SetZOrder); + ccAddExternalObjectFunction("Viewport::GetAtScreenXY", Sc_Viewport_GetAtScreenXY); + ccAddExternalObjectFunction("Viewport::SetPosition", Sc_Viewport_SetPosition); + ccAddExternalObjectFunction("Viewport::ScreenToRoomPoint", Sc_Viewport_ScreenToRoomPoint); + ccAddExternalObjectFunction("Viewport::RoomToScreenPoint", Sc_Viewport_RoomToScreenPoint); +} diff --git a/engines/ags/engine/ac/walkablearea.cpp b/engines/ags/engine/ac/walkablearea.cpp new file mode 100644 index 00000000000..c6631789920 --- /dev/null +++ b/engines/ags/engine/ac/walkablearea.cpp @@ -0,0 +1,238 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "ac/object.h" +#include "ac/character.h" +#include "ac/gamestate.h" +#include "ac/gamesetupstruct.h" +#include "ac/object.h" +#include "ac/room.h" +#include "ac/roomobject.h" +#include "ac/roomstatus.h" +#include "ac/walkablearea.h" +#include "game/roomstruct.h" +#include "gfx/bitmap.h" + +using namespace AGS::Common; + +extern RoomStruct thisroom; +extern GameState play; +extern GameSetupStruct game; +extern int displayed_room; +extern RoomStatus*croom; +extern RoomObject*objs; + +Bitmap *walkareabackup=nullptr, *walkable_areas_temp = nullptr; + +void redo_walkable_areas() { + + // since this is an 8-bit memory bitmap, we can just use direct + // memory access + if ((!thisroom.WalkAreaMask->IsLinearBitmap()) || (thisroom.WalkAreaMask->GetColorDepth() != 8)) + quit("Walkable areas bitmap not linear"); + + thisroom.WalkAreaMask->Blit(walkareabackup, 0, 0, 0, 0, thisroom.WalkAreaMask->GetWidth(), thisroom.WalkAreaMask->GetHeight()); + + int hh,ww; + for (hh=0;hhGetHeight();hh++) { + uint8_t *walls_scanline = thisroom.WalkAreaMask->GetScanLineForWriting(hh); + for (ww=0;wwGetWidth();ww++) { + // if (play.walkable_areas_on[_getpixel(thisroom.WalkAreaMask,ww,hh)]==0) + if (play.walkable_areas_on[walls_scanline[ww]]==0) + walls_scanline[ww] = 0; + } + } + +} + +int get_walkable_area_pixel(int x, int y) +{ + return thisroom.WalkAreaMask->GetPixel(room_to_mask_coord(x), room_to_mask_coord(y)); +} + +int get_area_scaling (int onarea, int xx, int yy) { + + int zoom_level = 100; + xx = room_to_mask_coord(xx); + yy = room_to_mask_coord(yy); + + if ((onarea >= 0) && (onarea <= MAX_WALK_AREAS) && + (thisroom.WalkAreas[onarea].ScalingNear != NOT_VECTOR_SCALED)) { + // We have vector scaling! + // In case the character is off the screen, limit the Y co-ordinate + // to within the area range (otherwise we get silly zoom levels + // that cause Out Of Memory crashes) + if (yy > thisroom.WalkAreas[onarea].Bottom) + yy = thisroom.WalkAreas[onarea].Bottom; + if (yy < thisroom.WalkAreas[onarea].Top) + yy = thisroom.WalkAreas[onarea].Top; + // Work it all out without having to use floats + // Percent = ((y - top) * 100) / (areabottom - areatop) + // Zoom level = ((max - min) * Percent) / 100 + if (thisroom.WalkAreas[onarea].Bottom != thisroom.WalkAreas[onarea].Top) + { + int percent = ((yy - thisroom.WalkAreas[onarea].Top) * 100) + / (thisroom.WalkAreas[onarea].Bottom - thisroom.WalkAreas[onarea].Top); + zoom_level = ((thisroom.WalkAreas[onarea].ScalingNear - thisroom.WalkAreas[onarea].ScalingFar) * (percent)) / 100 + thisroom.WalkAreas[onarea].ScalingFar; + } + else + { + // Special case for 1px tall walkable area: take bottom line scaling + zoom_level = thisroom.WalkAreas[onarea].ScalingNear; + } + zoom_level += 100; + } + else if ((onarea >= 0) & (onarea <= MAX_WALK_AREAS)) + zoom_level = thisroom.WalkAreas[onarea].ScalingFar + 100; + + if (zoom_level == 0) + zoom_level = 100; + + return zoom_level; +} + +void scale_sprite_size(int sppic, int zoom_level, int *newwidth, int *newheight) { + newwidth[0] = (game.SpriteInfos[sppic].Width * zoom_level) / 100; + newheight[0] = (game.SpriteInfos[sppic].Height * zoom_level) / 100; + if (newwidth[0] < 1) + newwidth[0] = 1; + if (newheight[0] < 1) + newheight[0] = 1; +} + +void remove_walkable_areas_from_temp(int fromx, int cwidth, int starty, int endy) { + + fromx = room_to_mask_coord(fromx); + cwidth = room_to_mask_coord(cwidth); + starty = room_to_mask_coord(starty); + endy = room_to_mask_coord(endy); + + int yyy; + if (endy >= walkable_areas_temp->GetHeight()) + endy = walkable_areas_temp->GetHeight() - 1; + if (starty < 0) + starty = 0; + + for (; cwidth > 0; cwidth --) { + for (yyy = starty; yyy <= endy; yyy++) + walkable_areas_temp->PutPixel (fromx, yyy, 0); + fromx ++; + } + +} + +int is_point_in_rect(int x, int y, int left, int top, int right, int bottom) { + if ((x >= left) && (x < right) && (y >= top ) && (y <= bottom)) + return 1; + return 0; +} + +Bitmap *prepare_walkable_areas (int sourceChar) { + // copy the walkable areas to the temp bitmap + walkable_areas_temp->Blit(thisroom.WalkAreaMask.get(), 0,0,0,0,thisroom.WalkAreaMask->GetWidth(),thisroom.WalkAreaMask->GetHeight()); + // if the character who's moving doesn't Bitmap *, don't bother checking + if (sourceChar < 0) ; + else if (game.chars[sourceChar].flags & CHF_NOBLOCKING) + return walkable_areas_temp; + + int ww; + // for each character in the current room, make the area under + // them unwalkable + for (ww = 0; ww < game.numcharacters; ww++) { + if (game.chars[ww].on != 1) continue; + if (game.chars[ww].room != displayed_room) continue; + if (ww == sourceChar) continue; + if (game.chars[ww].flags & CHF_NOBLOCKING) continue; + if (room_to_mask_coord(game.chars[ww].y) >= walkable_areas_temp->GetHeight()) continue; + if (room_to_mask_coord(game.chars[ww].x) >= walkable_areas_temp->GetWidth()) continue; + if ((game.chars[ww].y < 0) || (game.chars[ww].x < 0)) continue; + + CharacterInfo *char1 = &game.chars[ww]; + int cwidth, fromx; + + if (is_char_on_another(sourceChar, ww, &fromx, &cwidth)) + continue; + if ((sourceChar >= 0) && (is_char_on_another(ww, sourceChar, nullptr, nullptr))) + continue; + + remove_walkable_areas_from_temp(fromx, cwidth, char1->get_blocking_top(), char1->get_blocking_bottom()); + } + + // check for any blocking objects in the room, and deal with them + // as well + for (ww = 0; ww < croom->numobj; ww++) { + if (objs[ww].on != 1) continue; + if ((objs[ww].flags & OBJF_SOLID) == 0) + continue; + if (room_to_mask_coord(objs[ww].y) >= walkable_areas_temp->GetHeight()) continue; + if (room_to_mask_coord(objs[ww].x) >= walkable_areas_temp->GetWidth()) continue; + if ((objs[ww].y < 0) || (objs[ww].x < 0)) continue; + + int x1, y1, width, y2; + get_object_blocking_rect(ww, &x1, &y1, &width, &y2); + + // if the character is currently standing on the object, ignore + // it so as to allow him to escape + if ((sourceChar >= 0) && + (is_point_in_rect(game.chars[sourceChar].x, game.chars[sourceChar].y, + x1, y1, x1 + width, y2))) + continue; + + remove_walkable_areas_from_temp(x1, width, y1, y2); + } + + return walkable_areas_temp; +} + +// return the walkable area at the character's feet, taking into account +// that he might just be off the edge of one +int get_walkable_area_at_location(int xx, int yy) { + + int onarea = get_walkable_area_pixel(xx, yy); + + if (onarea < 0) { + // the character has walked off the edge of the screen, so stop them + // jumping up to full size when leaving + if (xx >= thisroom.Width) + onarea = get_walkable_area_pixel(thisroom.Width-1, yy); + else if (xx < 0) + onarea = get_walkable_area_pixel(0, yy); + else if (yy >= thisroom.Height) + onarea = get_walkable_area_pixel(xx, thisroom.Height - 1); + else if (yy < 0) + onarea = get_walkable_area_pixel(xx, 1); + } + if (onarea==0) { + // the path finder sometimes slightly goes into non-walkable areas; + // so check for scaling in adjacent pixels + const int TRYGAP=2; + onarea = get_walkable_area_pixel(xx + TRYGAP, yy); + if (onarea<=0) + onarea = get_walkable_area_pixel(xx - TRYGAP, yy); + if (onarea<=0) + onarea = get_walkable_area_pixel(xx, yy + TRYGAP); + if (onarea<=0) + onarea = get_walkable_area_pixel(xx, yy - TRYGAP); + if (onarea < 0) + onarea = 0; + } + + return onarea; +} + +int get_walkable_area_at_character (int charnum) { + CharacterInfo *chin = &game.chars[charnum]; + return get_walkable_area_at_location(chin->x, chin->y); +} diff --git a/engines/ags/engine/ac/walkablearea.h b/engines/ags/engine/ac/walkablearea.h new file mode 100644 index 00000000000..391d93a920f --- /dev/null +++ b/engines/ags/engine/ac/walkablearea.h @@ -0,0 +1,31 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__WALKABLEAREA_H +#define __AGS_EE_AC__WALKABLEAREA_H + +void redo_walkable_areas(); +int get_walkable_area_pixel(int x, int y); +int get_area_scaling (int onarea, int xx, int yy); +void scale_sprite_size(int sppic, int zoom_level, int *newwidth, int *newheight); +void remove_walkable_areas_from_temp(int fromx, int cwidth, int starty, int endy); +int is_point_in_rect(int x, int y, int left, int top, int right, int bottom); +Common::Bitmap *prepare_walkable_areas (int sourceChar); +int get_walkable_area_at_location(int xx, int yy); +int get_walkable_area_at_character (int charnum); + +#endif // __AGS_EE_AC__WALKABLEAREA_H diff --git a/engines/ags/engine/ac/walkbehind.cpp b/engines/ags/engine/ac/walkbehind.cpp new file mode 100644 index 00000000000..f254800f4be --- /dev/null +++ b/engines/ags/engine/ac/walkbehind.cpp @@ -0,0 +1,143 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/walkbehind.h" +#include "ac/common.h" +#include "ac/common_defines.h" +#include "ac/gamestate.h" +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern RoomStruct thisroom; +extern GameState play; +extern IGraphicsDriver *gfxDriver; + + +char *walkBehindExists = nullptr; // whether a WB area is in this column +int *walkBehindStartY = nullptr, *walkBehindEndY = nullptr; +char noWalkBehindsAtAll = 0; +int walkBehindLeft[MAX_WALK_BEHINDS], walkBehindTop[MAX_WALK_BEHINDS]; +int walkBehindRight[MAX_WALK_BEHINDS], walkBehindBottom[MAX_WALK_BEHINDS]; +IDriverDependantBitmap *walkBehindBitmap[MAX_WALK_BEHINDS]; +int walkBehindsCachedForBgNum = 0; +WalkBehindMethodEnum walkBehindMethod = DrawOverCharSprite; +int walk_behind_baselines_changed = 0; + +void update_walk_behind_images() +{ + int ee, rr; + int bpp = (thisroom.BgFrames[play.bg_frame].Graphic->GetColorDepth() + 7) / 8; + Bitmap *wbbmp; + for (ee = 1; ee < MAX_WALK_BEHINDS; ee++) + { + update_polled_stuff_if_runtime(); + + if (walkBehindRight[ee] > 0) + { + wbbmp = BitmapHelper::CreateTransparentBitmap( + (walkBehindRight[ee] - walkBehindLeft[ee]) + 1, + (walkBehindBottom[ee] - walkBehindTop[ee]) + 1, + thisroom.BgFrames[play.bg_frame].Graphic->GetColorDepth()); + int yy, startX = walkBehindLeft[ee], startY = walkBehindTop[ee]; + for (rr = startX; rr <= walkBehindRight[ee]; rr++) + { + for (yy = startY; yy <= walkBehindBottom[ee]; yy++) + { + if (thisroom.WalkBehindMask->GetScanLine(yy)[rr] == ee) + { + for (int ii = 0; ii < bpp; ii++) + wbbmp->GetScanLineForWriting(yy - startY)[(rr - startX) * bpp + ii] = thisroom.BgFrames[play.bg_frame].Graphic->GetScanLine(yy)[rr * bpp + ii]; + } + } + } + + update_polled_stuff_if_runtime(); + + if (walkBehindBitmap[ee] != nullptr) + { + gfxDriver->DestroyDDB(walkBehindBitmap[ee]); + } + walkBehindBitmap[ee] = gfxDriver->CreateDDBFromBitmap(wbbmp, false); + delete wbbmp; + } + } + + walkBehindsCachedForBgNum = play.bg_frame; +} + + +void recache_walk_behinds () { + if (walkBehindExists) { + free (walkBehindExists); + free (walkBehindStartY); + free (walkBehindEndY); + } + + walkBehindExists = (char*)malloc (thisroom.WalkBehindMask->GetWidth()); + walkBehindStartY = (int*)malloc (thisroom.WalkBehindMask->GetWidth() * sizeof(int)); + walkBehindEndY = (int*)malloc (thisroom.WalkBehindMask->GetWidth() * sizeof(int)); + noWalkBehindsAtAll = 1; + + int ee,rr,tmm; + const int NO_WALK_BEHIND = 100000; + for (ee = 0; ee < MAX_WALK_BEHINDS; ee++) + { + walkBehindLeft[ee] = NO_WALK_BEHIND; + walkBehindTop[ee] = NO_WALK_BEHIND; + walkBehindRight[ee] = 0; + walkBehindBottom[ee] = 0; + + if (walkBehindBitmap[ee] != nullptr) + { + gfxDriver->DestroyDDB(walkBehindBitmap[ee]); + walkBehindBitmap[ee] = nullptr; + } + } + + update_polled_stuff_if_runtime(); + + // since this is an 8-bit memory bitmap, we can just use direct + // memory access + if ((!thisroom.WalkBehindMask->IsLinearBitmap()) || (thisroom.WalkBehindMask->GetColorDepth() != 8)) + quit("Walk behinds bitmap not linear"); + + for (ee=0;eeGetWidth();ee++) { + walkBehindExists[ee] = 0; + for (rr=0;rrGetHeight();rr++) { + tmm = thisroom.WalkBehindMask->GetScanLine(rr)[ee]; + //tmm = _getpixel(thisroom.WalkBehindMask,ee,rr); + if ((tmm >= 1) && (tmm < MAX_WALK_BEHINDS)) { + if (!walkBehindExists[ee]) { + walkBehindStartY[ee] = rr; + walkBehindExists[ee] = tmm; + noWalkBehindsAtAll = 0; + } + walkBehindEndY[ee] = rr + 1; // +1 to allow bottom line of screen to work + + if (ee < walkBehindLeft[tmm]) walkBehindLeft[tmm] = ee; + if (rr < walkBehindTop[tmm]) walkBehindTop[tmm] = rr; + if (ee > walkBehindRight[tmm]) walkBehindRight[tmm] = ee; + if (rr > walkBehindBottom[tmm]) walkBehindBottom[tmm] = rr; + } + } + } + + if (walkBehindMethod == DrawAsSeparateSprite) + { + update_walk_behind_images(); + } +} diff --git a/engines/ags/engine/ac/walkbehind.h b/engines/ags/engine/ac/walkbehind.h new file mode 100644 index 00000000000..01d1565a27b --- /dev/null +++ b/engines/ags/engine/ac/walkbehind.h @@ -0,0 +1,31 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_AC__WALKBEHIND_H +#define __AGS_EE_AC__WALKBEHIND_H + +enum WalkBehindMethodEnum +{ + DrawOverCharSprite, + DrawAsSeparateSprite, + DrawAsSeparateCharSprite +}; + +void update_walk_behind_images(); +void recache_walk_behinds (); + +#endif // __AGS_EE_AC__WALKBEHIND_H diff --git a/engines/ags/engine/debugging/agseditordebugger.h b/engines/ags/engine/debugging/agseditordebugger.h new file mode 100644 index 00000000000..f53b4d4fc3e --- /dev/null +++ b/engines/ags/engine/debugging/agseditordebugger.h @@ -0,0 +1,34 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_DEBUG__AGSEDITORDEBUGGER_H +#define __AGS_EE_DEBUG__AGSEDITORDEBUGGER_H + +struct IAGSEditorDebugger +{ +public: + virtual ~IAGSEditorDebugger() = default; + + virtual bool Initialize() = 0; + virtual void Shutdown() = 0; + virtual bool SendMessageToEditor(const char *message) = 0; + virtual bool IsMessageAvailable() = 0; + // Message will be allocated on heap with malloc + virtual char* GetNextMessage() = 0; +}; + +#endif // __AGS_EE_DEBUG__AGSEDITORDEBUGGER_H diff --git a/engines/ags/engine/debugging/consoleoutputtarget.cpp b/engines/ags/engine/debugging/consoleoutputtarget.cpp new file mode 100644 index 00000000000..2442e953365 --- /dev/null +++ b/engines/ags/engine/debugging/consoleoutputtarget.cpp @@ -0,0 +1,42 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "consoleoutputtarget.h" +#include "debug/debug_log.h" + +namespace AGS +{ +namespace Engine +{ + +ConsoleOutputTarget::ConsoleOutputTarget() +{ +} + +ConsoleOutputTarget::~ConsoleOutputTarget() = default; + +void ConsoleOutputTarget::PrintMessage(const DebugMessage &msg) +{ + // limit number of characters for console + // TODO: is there a way to find out how many characters can fit in? + debug_line[last_debug_line] = msg.Text.Left(99); + + last_debug_line = (last_debug_line + 1) % DEBUG_CONSOLE_NUMLINES; + if (last_debug_line == first_debug_line) + first_debug_line = (first_debug_line + 1) % DEBUG_CONSOLE_NUMLINES; +} + +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/debugging/consoleoutputtarget.h b/engines/ags/engine/debugging/consoleoutputtarget.h new file mode 100644 index 00000000000..13ae01a394a --- /dev/null +++ b/engines/ags/engine/debugging/consoleoutputtarget.h @@ -0,0 +1,44 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// ConsoleOutputTarget prints messages onto in-game console GUI (available +// only if the game was compiled in debug mode). +// +//============================================================================= +#ifndef __AGS_EE_DEBUG__CONSOLEOUTPUTTARGET_H +#define __AGS_EE_DEBUG__CONSOLEOUTPUTTARGET_H + +#include "debug/outputhandler.h" + +namespace AGS +{ +namespace Engine +{ + +using Common::String; +using Common::DebugMessage; + +class ConsoleOutputTarget : public AGS::Common::IOutputHandler +{ +public: + ConsoleOutputTarget(); + virtual ~ConsoleOutputTarget(); + + void PrintMessage(const DebugMessage &msg) override; +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_DEBUG__CONSOLEOUTPUTTARGET_H diff --git a/engines/ags/engine/debugging/debug.cpp b/engines/ags/engine/debugging/debug.cpp new file mode 100644 index 00000000000..4c0807cd1f7 --- /dev/null +++ b/engines/ags/engine/debugging/debug.cpp @@ -0,0 +1,629 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "core/platform.h" +#include "ac/common.h" +#include "ac/gamesetupstruct.h" +#include "ac/runtime_defines.h" +#include "debug/agseditordebugger.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "debug/debugmanager.h" +#include "debug/out.h" +#include "debug/consoleoutputtarget.h" +#include "debug/logfile.h" +#include "debug/messagebuffer.h" +#include "main/config.h" +#include "media/audio/audio_system.h" +#include "platform/base/agsplatformdriver.h" +#include "plugin/plugin_engine.h" +#include "script/script.h" +#include "script/script_common.h" +#include "script/cc_error.h" +#include "util/string_utils.h" +#include "util/textstreamwriter.h" + +#if AGS_PLATFORM_OS_WINDOWS +#include +#endif + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern char check_dynamic_sprites_at_exit; +extern int displayed_room; +extern RoomStruct thisroom; +extern char pexbuf[STD_BUFFER_SIZE]; +extern volatile char want_exit, abort_engine; +extern GameSetupStruct game; + + +int editor_debugging_enabled = 0; +int editor_debugging_initialized = 0; +char editor_debugger_instance_token[100]; +IAGSEditorDebugger *editor_debugger = nullptr; +int break_on_next_script_step = 0; +volatile int game_paused_in_debugger = 0; + +#if AGS_PLATFORM_OS_WINDOWS + +#include "platform/windows/debug/namedpipesagsdebugger.h" + +HWND editor_window_handle = 0; + +IAGSEditorDebugger *GetEditorDebugger(const char *instanceToken) +{ + return new NamedPipesAGSDebugger(instanceToken); +} + +#else // AGS_PLATFORM_OS_WINDOWS + +IAGSEditorDebugger *GetEditorDebugger(const char *instanceToken) +{ + return nullptr; +} + +#endif + +int debug_flags=0; + +String debug_line[DEBUG_CONSOLE_NUMLINES]; +int first_debug_line = 0, last_debug_line = 0, display_console = 0; + +float fps = std::numeric_limits::quiet_NaN(); +FPSDisplayMode display_fps = kFPS_Hide; + +std::unique_ptr DebugMsgBuff; +std::unique_ptr DebugLogFile; +std::unique_ptr DebugConsole; + +const String OutputMsgBufID = "buffer"; +const String OutputFileID = "file"; +const String OutputSystemID = "stdout"; +const String OutputGameConsoleID = "console"; + + + +PDebugOutput create_log_output(const String &name, const String &path = "", LogFile::OpenMode open_mode = LogFile::kLogFile_Overwrite) +{ + // Else create new one, if we know this ID + if (name.CompareNoCase(OutputSystemID) == 0) + { + return DbgMgr.RegisterOutput(OutputSystemID, AGSPlatformDriver::GetDriver(), kDbgMsg_None); + } + else if (name.CompareNoCase(OutputFileID) == 0) + { + DebugLogFile.reset(new LogFile()); + String logfile_path = !path.IsEmpty() ? path : String::FromFormat("%s/ags.log", platform->GetAppOutputDirectory()); + if (!DebugLogFile->OpenFile(logfile_path, open_mode)) + return nullptr; + platform->WriteStdOut("Logging to %s", logfile_path.GetCStr()); + auto dbgout = DbgMgr.RegisterOutput(OutputFileID, DebugLogFile.get(), kDbgMsg_None); + return dbgout; + } + else if (name.CompareNoCase(OutputGameConsoleID) == 0) + { + DebugConsole.reset(new ConsoleOutputTarget()); + return DbgMgr.RegisterOutput(OutputGameConsoleID, DebugConsole.get(), kDbgMsg_None); + } + return nullptr; +} + +// Parses a string where each character defines a single log group; returns list of real group names. +std::vector parse_log_multigroup(const String &group_str) +{ + std::vector grplist; + for (size_t i = 0; i < group_str.GetLength(); ++i) + { + switch (group_str[i]) + { + case 'm': grplist.push_back("main"); break; + case 'g': grplist.push_back("game"); break; + case 's': grplist.push_back("script"); break; + case 'c': grplist.push_back("sprcache"); break; + case 'o': grplist.push_back("manobj"); break; + } + } + return grplist; +} + +MessageType get_messagetype_from_string(const String &mt) +{ + int mtype; + if (StrUtil::StringToInt(mt, mtype, 0) == StrUtil::kNoError) + return (MessageType)mtype; + + if (mt.CompareNoCase("alert") == 0) return kDbgMsg_Alert; + else if (mt.CompareNoCase("fatal") == 0) return kDbgMsg_Fatal; + else if (mt.CompareNoCase("error") == 0) return kDbgMsg_Error; + else if (mt.CompareNoCase("warn") == 0) return kDbgMsg_Warn; + else if (mt.CompareNoCase("info") == 0) return kDbgMsg_Info; + else if (mt.CompareNoCase("debug") == 0) return kDbgMsg_Debug; + else if (mt.CompareNoCase("all") == 0) return kDbgMsg_All; + return kDbgMsg_None; +} + +typedef std::pair DbgGroupOption; + +void apply_log_config(const ConfigTree &cfg, const String &log_id, + bool def_enabled, + std::initializer_list def_opts) +{ + String value = INIreadstring(cfg, "log", log_id); + if (value.IsEmpty() && !def_enabled) + return; + + // First test if already registered, if not then try create it + auto dbgout = DbgMgr.GetOutput(log_id); + const bool was_created_earlier = dbgout != nullptr; + if (!dbgout) + { + String path = INIreadstring(cfg, "log", String::FromFormat("%s-path", log_id.GetCStr())); + dbgout = create_log_output(log_id, path); + if (!dbgout) + return; // unknown output type + } + dbgout->ClearGroupFilters(); + + if (value.IsEmpty() || value.CompareNoCase("default") == 0) + { + for (const auto opt : def_opts) + dbgout->SetGroupFilter(opt.first, opt.second); + } + else + { + const auto options = value.Split(','); + for (const auto &opt : options) + { + String groupname = opt.LeftSection(':'); + MessageType msgtype = kDbgMsg_All; + if (opt.GetLength() >= groupname.GetLength() + 1) + { + String msglevel = opt.Mid(groupname.GetLength() + 1); + msglevel.Trim(); + if (msglevel.GetLength() > 0) + msgtype = get_messagetype_from_string(msglevel); + } + groupname.Trim(); + if (groupname.CompareNoCase("all") == 0 || groupname.IsEmpty()) + { + dbgout->SetAllGroupFilters(msgtype); + } + else if (groupname[0u] != '+') + { + dbgout->SetGroupFilter(groupname, msgtype); + } + else + { + const auto groups = parse_log_multigroup(groupname); + for (const auto &g : groups) + dbgout->SetGroupFilter(g, msgtype); + } + } + } + + // Delegate buffered messages to this new output + if (DebugMsgBuff && !was_created_earlier) + DebugMsgBuff->Send(log_id); +} + +void init_debug(const ConfigTree &cfg, bool stderr_only) +{ + // Register outputs + apply_debug_config(cfg); + platform->SetOutputToErr(stderr_only); + + if (stderr_only) + return; + + // Message buffer to save all messages in case we read different log settings from config file + DebugMsgBuff.reset(new MessageBuffer()); + DbgMgr.RegisterOutput(OutputMsgBufID, DebugMsgBuff.get(), kDbgMsg_All); +} + +void apply_debug_config(const ConfigTree &cfg) +{ + apply_log_config(cfg, OutputSystemID, /* defaults */ true, { DbgGroupOption(kDbgGroup_Main, kDbgMsg_Info) }); + bool legacy_log_enabled = INIreadint(cfg, "misc", "log", 0) != 0; + apply_log_config(cfg, OutputFileID, + /* defaults */ + legacy_log_enabled, + { DbgGroupOption(kDbgGroup_Main, kDbgMsg_All), + DbgGroupOption(kDbgGroup_Game, kDbgMsg_Info), + DbgGroupOption(kDbgGroup_Script, kDbgMsg_All), +#ifdef DEBUG_SPRITECACHE + DbgGroupOption(kDbgGroup_SprCache, kDbgMsg_All), +#else + DbgGroupOption(kDbgGroup_SprCache, kDbgMsg_Info), +#endif +#ifdef DEBUG_MANAGED_OBJECTS + DbgGroupOption(kDbgGroup_ManObj, kDbgMsg_All), +#else + DbgGroupOption(kDbgGroup_ManObj, kDbgMsg_Info), +#endif + }); + + // Init game console if the game was compiled in Debug mode + if (game.options[OPT_DEBUGMODE] != 0) + { + apply_log_config(cfg, OutputGameConsoleID, + /* defaults */ + true, + { DbgGroupOption(kDbgGroup_Main, kDbgMsg_All), + DbgGroupOption(kDbgGroup_Game, kDbgMsg_All), + DbgGroupOption(kDbgGroup_Script, kDbgMsg_All) + }); + debug_set_console(true); + } + + // If the game was compiled in Debug mode *and* there's no regular file log, + // then open "warnings.log" for printing script warnings. + if (game.options[OPT_DEBUGMODE] != 0 && !DebugLogFile) + { + auto dbgout = create_log_output(OutputFileID, "warnings.log", LogFile::kLogFile_OverwriteAtFirstMessage); + if (dbgout) + { + dbgout->SetGroupFilter(kDbgGroup_Game, kDbgMsg_Warn); + dbgout->SetGroupFilter(kDbgGroup_Script, kDbgMsg_Warn); + } + } + + // We don't need message buffer beyond this point + DbgMgr.UnregisterOutput(OutputMsgBufID); + DebugMsgBuff.reset(); +} + +void shutdown_debug() +{ + // Shutdown output subsystem + DbgMgr.UnregisterAll(); + + DebugMsgBuff.reset(); + DebugLogFile.reset(); + DebugConsole.reset(); +} + +void debug_set_console(bool enable) +{ + if (DebugConsole) + DbgMgr.GetOutput(OutputGameConsoleID)->SetEnabled(enable); +} + +// Prepends message text with current room number and running script info, then logs result +void debug_script_print(const String &msg, MessageType mt) +{ + String script_ref; + ccInstance *curinst = ccInstance::GetCurrentInstance(); + if (curinst != nullptr) { + String scriptname; + if (curinst->instanceof == gamescript) + scriptname = "G "; + else if (curinst->instanceof == thisroom.CompiledScript) + scriptname = "R "; + else if (curinst->instanceof == dialogScriptsScript) + scriptname = "D "; + else + scriptname = "? "; + script_ref.Format("[%s%d]", scriptname.GetCStr(), currentline); + } + + Debug::Printf(kDbgGroup_Game, mt, "(room:%d)%s %s", displayed_room, script_ref.GetCStr(), msg.GetCStr()); +} + +void debug_script_warn(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + String full_msg = String::FromFormatV(msg, ap); + va_end(ap); + debug_script_print(full_msg, kDbgMsg_Warn); +} + +void debug_script_log(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + String full_msg = String::FromFormatV(msg, ap); + va_end(ap); + debug_script_print(full_msg, kDbgMsg_Debug); +} + + +String get_cur_script(int numberOfLinesOfCallStack) +{ + String callstack; + ccInstance *sci = ccInstance::GetCurrentInstance(); + if (sci) + callstack = sci->GetCallStack(numberOfLinesOfCallStack); + if (callstack.IsEmpty()) + callstack = ccErrorCallStack; + return callstack; +} + +bool get_script_position(ScriptPosition &script_pos) +{ + ccInstance *cur_instance = ccInstance::GetCurrentInstance(); + if (cur_instance) + { + cur_instance->GetScriptPosition(script_pos); + return true; + } + return false; +} + +struct Breakpoint +{ + char scriptName[80]; + int lineNumber; +}; + +std::vector breakpoints; +int numBreakpoints = 0; + +bool send_message_to_editor(const char *msg, const char *errorMsg) +{ + String callStack = get_cur_script(25); + if (callStack.IsEmpty()) + return false; + + char messageToSend[STD_BUFFER_SIZE]; + sprintf(messageToSend, "", msg); +#if AGS_PLATFORM_OS_WINDOWS + sprintf(&messageToSend[strlen(messageToSend)], " %d ", (int)win_get_window()); +#endif + sprintf(&messageToSend[strlen(messageToSend)], " ", callStack.GetCStr()); + if (errorMsg != nullptr) + { + sprintf(&messageToSend[strlen(messageToSend)], " ", errorMsg); + } + strcat(messageToSend, ""); + + editor_debugger->SendMessageToEditor(messageToSend); + + return true; +} + +bool send_message_to_editor(const char *msg) +{ + return send_message_to_editor(msg, nullptr); +} + +bool init_editor_debugging() +{ +#if AGS_PLATFORM_OS_WINDOWS + editor_debugger = GetEditorDebugger(editor_debugger_instance_token); +#else + // Editor isn't ported yet + editor_debugger = nullptr; +#endif + + if (editor_debugger == nullptr) + quit("editor_debugger is NULL but debugger enabled"); + + if (editor_debugger->Initialize()) + { + editor_debugging_initialized = 1; + + // Wait for the editor to send the initial breakpoints + // and then its READY message + while (check_for_messages_from_editor() != 2) + { + platform->Delay(10); + } + + send_message_to_editor("START"); + return true; + } + + return false; +} + +int check_for_messages_from_editor() +{ + if (editor_debugger->IsMessageAvailable()) + { + char *msg = editor_debugger->GetNextMessage(); + if (msg == nullptr) + { + return 0; + } + + if (strncmp(msg, "Delay(10); + } +#endif + return true; +} + + +void break_into_debugger() +{ +#if AGS_PLATFORM_OS_WINDOWS + + if (editor_window_handle != NULL) + SetForegroundWindow(editor_window_handle); + + send_message_to_editor("BREAK"); + game_paused_in_debugger = 1; + + while (game_paused_in_debugger) + { + update_polled_stuff_if_runtime(); + platform->YieldCPU(); + } + +#endif +} + +int scrDebugWait = 0; +extern int pluginsWantingDebugHooks; + +// allow LShift to single-step, RShift to pause flow +void scriptDebugHook (ccInstance *ccinst, int linenum) { + + if (pluginsWantingDebugHooks > 0) { + // a plugin is handling the debugging + String scname = GetScriptName(ccinst); + pl_run_plugin_debug_hooks(scname, linenum); + return; + } + + // no plugin, use built-in debugger + + if (ccinst == nullptr) + { + // come out of script + return; + } + + if (break_on_next_script_step) + { + break_on_next_script_step = 0; + break_into_debugger(); + return; + } + + const char *scriptName = ccinst->runningInst->instanceof->GetSectionName(ccinst->pc); + + for (int i = 0; i < numBreakpoints; i++) + { + if ((breakpoints[i].lineNumber == linenum) && + (strcmp(breakpoints[i].scriptName, scriptName) == 0)) + { + break_into_debugger(); + break; + } + } +} + +int scrlockWasDown = 0; + +void check_debug_keys() { + if (play.debug_mode) { + // do the run-time script debugging + + if ((!key[KEY_SCRLOCK]) && (scrlockWasDown)) + scrlockWasDown = 0; + else if ((key[KEY_SCRLOCK]) && (!scrlockWasDown)) { + + break_on_next_script_step = 1; + scrlockWasDown = 1; + } + + } + +} diff --git a/engines/ags/engine/debugging/debug_log.h b/engines/ags/engine/debugging/debug_log.h new file mode 100644 index 00000000000..268b846aecb --- /dev/null +++ b/engines/ags/engine/debugging/debug_log.h @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_DEBUG_LOG_H +#define __AC_DEBUG_LOG_H + +#include "script/cc_instance.h" +#include "ac/runtime_defines.h" +#include "ac/gamestate.h" +#include "platform/base/agsplatformdriver.h" +#include "util/ini_util.h" + +void init_debug(const AGS::Common::ConfigTree &cfg, bool stderr_only); +void apply_debug_config(const AGS::Common::ConfigTree &cfg); +void shutdown_debug(); + +void debug_set_console(bool enable); + +// debug_script_log prints debug warnings tagged with kDbgGroup_Script, +// prepending it with current room number and script position identification +void debug_script_warn(const char *texx, ...); +// debug_script_log prints debug message tagged with kDbgGroup_Script, +// prepending it with current room number and script position identification +void debug_script_log(const char *msg, ...); +void quitprintf(const char *texx, ...); +bool init_editor_debugging(); + +// allow LShift to single-step, RShift to pause flow +void scriptDebugHook (ccInstance *ccinst, int linenum) ; + +extern AGS::Common::String debug_line[DEBUG_CONSOLE_NUMLINES]; +extern int first_debug_line, last_debug_line, display_console; + + +extern AGSPlatformDriver *platform; + +#endif // __AC_DEBUG_LOG_H diff --git a/engines/ags/engine/debugging/debugger.h b/engines/ags/engine/debugging/debugger.h new file mode 100644 index 00000000000..77c85d6a9ae --- /dev/null +++ b/engines/ags/engine/debugging/debugger.h @@ -0,0 +1,60 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_DEBUGGER_H +#define __AC_DEBUGGER_H + +#include "util/string.h" + +struct IAGSEditorDebugger; +struct ScriptPosition; + +extern int editor_debugging_enabled; +extern int editor_debugging_initialized; +extern char editor_debugger_instance_token[100]; +extern IAGSEditorDebugger *editor_debugger; +extern int break_on_next_script_step; + +int check_for_messages_from_editor(); +bool send_message_to_editor(const char *msg); +bool send_exception_to_editor(const char *qmsg); +// Returns current script's location and callstack +AGS::Common::String get_cur_script(int numberOfLinesOfCallStack); +bool get_script_position(ScriptPosition &script_pos); +void check_debug_keys(); + +#define DBG_NOIFACE 1 +#define DBG_NODRAWSPRITES 2 +#define DBG_NOOBJECTS 4 +#define DBG_NOUPDATE 8 +#define DBG_NOSFX 0x10 +#define DBG_NOMUSIC 0x20 +#define DBG_NOSCRIPT 0x40 +#define DBG_DBGSCRIPT 0x80 +#define DBG_DEBUGMODE 0x100 +#define DBG_REGONLY 0x200 +#define DBG_NOVIDEO 0x400 + +enum FPSDisplayMode +{ + kFPS_Hide = 0, // hid by the script/user command + kFPS_Display = 1, // shown by the script/user command + kFPS_Forced = 2 // forced shown by the engine arg +}; + +extern float fps; +extern FPSDisplayMode display_fps; +extern int debug_flags; + +#endif // __AC_DEBUGGER_H diff --git a/engines/ags/engine/debugging/dummyagsdebugger.h b/engines/ags/engine/debugging/dummyagsdebugger.h new file mode 100644 index 00000000000..38c02634811 --- /dev/null +++ b/engines/ags/engine/debugging/dummyagsdebugger.h @@ -0,0 +1,31 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_DUMMYAGSDEBUGGER_H +#define __AC_DUMMYAGSDEBUGGER_H + +#include "debug/debugger.h" + +struct DummyAGSDebugger : IAGSEditorDebugger +{ +public: + + virtual bool Initialize() override { return false; } + virtual void Shutdown() override { } + virtual bool SendMessageToEditor(const char *message) override { return false; } + virtual bool IsMessageAvailable() override { return false; } + virtual char* GetNextMessage() override { return NULL; } +}; + +#endif // __AC_DUMMYAGSDEBUGGER_H diff --git a/engines/ags/engine/debugging/filebasedagsdebugger.cpp b/engines/ags/engine/debugging/filebasedagsdebugger.cpp new file mode 100644 index 00000000000..263798725e0 --- /dev/null +++ b/engines/ags/engine/debugging/filebasedagsdebugger.cpp @@ -0,0 +1,77 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "debug/filebasedagsdebugger.h" +#include "ac/file.h" // filelength() +#include "util/stream.h" +#include "util/textstreamwriter.h" +#include "util/wgt2allg.h" // exists() +#include "platform/base/agsplatformdriver.h" + +using AGS::Common::Stream; +using AGS::Common::TextStreamWriter; + +const char* SENT_MESSAGE_FILE_NAME = "dbgrecv.tmp"; + +bool FileBasedAGSDebugger::Initialize() +{ + if (exists(SENT_MESSAGE_FILE_NAME)) + { + ::remove(SENT_MESSAGE_FILE_NAME); + } + return true; +} + +void FileBasedAGSDebugger::Shutdown() +{ +} + +bool FileBasedAGSDebugger::SendMessageToEditor(const char *message) +{ + while (exists(SENT_MESSAGE_FILE_NAME)) + { + platform->YieldCPU(); + } + + Stream *out = Common::File::CreateFile(SENT_MESSAGE_FILE_NAME); + // CHECKME: originally the file was opened as "wb" for some reason, + // which means the message should be written as a binary array; + // or shouldn't it? + out->Write(message, strlen(message)); + delete out; + return true; +} + +bool FileBasedAGSDebugger::IsMessageAvailable() +{ + return (exists("dbgsend.tmp") != 0); +} + +char* FileBasedAGSDebugger::GetNextMessage() +{ + Stream *in = Common::File::OpenFileRead("dbgsend.tmp"); + if (in == nullptr) + { + // check again, because the editor might have deleted the file in the meantime + return nullptr; + } + int fileSize = in->GetLength(); + char *msg = (char*)malloc(fileSize + 1); + in->Read(msg, fileSize); + delete in; + ::remove("dbgsend.tmp"); + msg[fileSize] = 0; + return msg; +} diff --git a/engines/ags/engine/debugging/filebasedagsdebugger.h b/engines/ags/engine/debugging/filebasedagsdebugger.h new file mode 100644 index 00000000000..3fcda091b54 --- /dev/null +++ b/engines/ags/engine/debugging/filebasedagsdebugger.h @@ -0,0 +1,34 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_FILEBASEDAGSDEBUGGER_H +#define __AC_FILEBASEDAGSDEBUGGER_H + +#include "debug/agseditordebugger.h" + +struct FileBasedAGSDebugger : IAGSEditorDebugger +{ +public: + + bool Initialize() override; + void Shutdown() override; + bool SendMessageToEditor(const char *message) override; + bool IsMessageAvailable() override; + char* GetNextMessage() override; + +}; + +extern const char* SENT_MESSAGE_FILE_NAME; + +#endif // __AC_FILEBASEDAGSDEBUGGER_H diff --git a/engines/ags/engine/debugging/logfile.cpp b/engines/ags/engine/debugging/logfile.cpp new file mode 100644 index 00000000000..4dad1cc4fda --- /dev/null +++ b/engines/ags/engine/debugging/logfile.cpp @@ -0,0 +1,88 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "debug/logfile.h" +#include "util/file.h" +#include "util/stream.h" + + +namespace AGS +{ +namespace Engine +{ + +using namespace Common; + +LogFile::LogFile() + : _openMode(kLogFile_Overwrite) +{ +} + +void LogFile::PrintMessage(const DebugMessage &msg) +{ + if (!_file.get()) + { + if (_filePath.IsEmpty()) + return; + _file.reset(File::OpenFile(_filePath, _openMode == kLogFile_Append ? Common::kFile_Create : Common::kFile_CreateAlways, + Common::kFile_Write)); + if (!_file) + { + Debug::Printf("Unable to write log to '%s'.", _filePath.GetCStr()); + _filePath = ""; + return; + } + } + + if (!msg.GroupName.IsEmpty()) + { + _file->Write(msg.GroupName, msg.GroupName.GetLength()); + _file->Write(" : ", 3); + } + _file->Write(msg.Text, msg.Text.GetLength()); + _file->WriteInt8('\n'); + // We should flush after every write to the log; this will make writing + // bit slower, but will increase the chances that all latest output + // will get to the disk in case of program crash. + _file->Flush(); +} + +bool LogFile::OpenFile(const String &file_path, OpenMode open_mode) +{ + CloseFile(); + + _filePath = file_path; + _openMode = open_mode; + if (open_mode == OpenMode::kLogFile_OverwriteAtFirstMessage) + { + return File::TestWriteFile(_filePath); + } + else + { + _file.reset(File::OpenFile(file_path, + open_mode == kLogFile_Append ? Common::kFile_Create : Common::kFile_CreateAlways, + Common::kFile_Write)); + return _file.get() != nullptr; + } +} + +void LogFile::CloseFile() +{ + _file.reset(); + _filePath.Empty(); +} + +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/debugging/logfile.h b/engines/ags/engine/debugging/logfile.h new file mode 100644 index 00000000000..702842891fd --- /dev/null +++ b/engines/ags/engine/debugging/logfile.h @@ -0,0 +1,78 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// LogFile, the IOutputHandler implementation that writes to file. +// +// When created LogFile may open file right away or delay doing this. +// In the latter case it will buffer output up to certain size limit. +// When told to open the file, it will first flush its buffer. This allows to +// log events even before the log path is decided (for example, before or +// during reading configuration and/or parsing command line). +// +//============================================================================= +#ifndef __AGS_EE_DEBUG__LOGFILE_H +#define __AGS_EE_DEBUG__LOGFILE_H + +#include +#include "debug/outputhandler.h" + +namespace AGS +{ + +namespace Common { class Stream; } + +namespace Engine +{ + +using Common::DebugMessage; +using Common::Stream; +using Common::String; + +class LogFile : public AGS::Common::IOutputHandler +{ +public: + enum OpenMode + { + kLogFile_Overwrite, + kLogFile_OverwriteAtFirstMessage, + kLogFile_Append + }; + +public: + LogFile(); + + void PrintMessage(const Common::DebugMessage &msg) override; + + // Open file using given file path, optionally appending if one exists + // + // TODO: filepath parameter here may be actually used as a pattern + // or prefix, while the actual filename could be made by combining + // this prefix with current date, game name, and similar additional + // useful information. Whether this is to be determined here or on + // high-level side remains a question. + // + bool OpenFile(const String &file_path, OpenMode open_mode = kLogFile_Overwrite); + // Close file + void CloseFile(); + +private: + std::unique_ptr _file; + String _filePath; + OpenMode _openMode; +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_DEBUG__LOGFILE_H diff --git a/engines/ags/engine/debugging/messagebuffer.cpp b/engines/ags/engine/debugging/messagebuffer.cpp new file mode 100644 index 00000000000..dc336e7a60b --- /dev/null +++ b/engines/ags/engine/debugging/messagebuffer.cpp @@ -0,0 +1,67 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include "debug/debugmanager.h" +#include "debug/messagebuffer.h" + +namespace AGS +{ +namespace Engine +{ + +using namespace Common; + +MessageBuffer::MessageBuffer(size_t buffer_limit) + : _bufferLimit(buffer_limit) + , _msgLost(0) +{ +} + +void MessageBuffer::PrintMessage(const DebugMessage &msg) +{ + if (_buffer.size() < _bufferLimit) + _buffer.push_back(msg); + else + _msgLost++; +} + +void MessageBuffer::Clear() +{ + _buffer.clear(); + _msgLost = 0; +} + +void MessageBuffer::Send(const String &out_id) +{ + if (_buffer.empty()) + return; + if (_msgLost > 0) + { + DebugGroup gr = DbgMgr.GetGroup(kDbgGroup_Main); + DbgMgr.SendMessage(out_id, DebugMessage(String::FromFormat("WARNING: output %s lost exceeding buffer: %u debug messages\n", out_id.GetCStr(), (unsigned)_msgLost), + gr.UID.ID, gr.OutputName, kDbgMsg_All)); + } + for (std::vector::const_iterator it = _buffer.begin(); it != _buffer.end(); ++it) + { + DbgMgr.SendMessage(out_id, *it); + } +} + +void MessageBuffer::Flush(const String &out_id) +{ + Send(out_id); + Clear(); +} + +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/debugging/messagebuffer.h b/engines/ags/engine/debugging/messagebuffer.h new file mode 100644 index 00000000000..b8a31a2cb82 --- /dev/null +++ b/engines/ags/engine/debugging/messagebuffer.h @@ -0,0 +1,57 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// MessageBuffer, the IOutputHandler implementation that stores debug messages +// in a vector. Could be handy if you need to temporarily buffer debug log +// while specifying how to actually print it. +// +//============================================================================= +#ifndef __AGS_EE_DEBUG__MESSAGEBUFFER_H +#define __AGS_EE_DEBUG__MESSAGEBUFFER_H + +#include +#include "debug/outputhandler.h" + +namespace AGS +{ +namespace Engine +{ + +using Common::String; +using Common::DebugMessage; + +class MessageBuffer : public AGS::Common::IOutputHandler +{ +public: + MessageBuffer(size_t buffer_limit = 1024); + + void PrintMessage(const DebugMessage &msg) override; + + // Clears buffer + void Clear(); + // Sends buffered messages into given output target + void Send(const String &out_id); + // Sends buffered messages into given output target and clears buffer + void Flush(const String &out_id); + +private: + const size_t _bufferLimit; + std::vector _buffer; + size_t _msgLost; +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_DEBUG__MESSAGEBUFFER_H diff --git a/engines/ags/engine/device/mousew32.cpp b/engines/ags/engine/device/mousew32.cpp new file mode 100644 index 00000000000..b939916fe99 --- /dev/null +++ b/engines/ags/engine/device/mousew32.cpp @@ -0,0 +1,387 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// MOUSELIBW32.CPP +// +// Library of mouse functions for graphics and text mode +// +// (c) 1994 Chris Jones +// Win32 (allegro) update (c) 1999 Chris Jones +// +//============================================================================= + +#include "core/platform.h" + +#define AGS_SIMULATE_RIGHT_CLICK (AGS_PLATFORM_OS_MACOS) + +#if AGS_PLATFORM_OS_WINDOWS +#include +#include +#include +#endif + +#include "util/wgt2allg.h" + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +#include "ac/gamestate.h" +#include "debug/out.h" +#include "device/mousew32.h" +#include "gfx/bitmap.h" +#include "gfx/gfx_util.h" +#include "main/graphics_mode.h" +#include "platform/base/agsplatformdriver.h" +#include "util/math.h" +#if AGS_SIMULATE_RIGHT_CLICK +#include "ac/sys_events.h" // j for ags_iskeypressed +#endif + +using namespace AGS::Common; +using namespace AGS::Engine; + + +extern char lib_file_name[13]; + +const char *mouselibcopyr = "MouseLib32 (c) 1994, 1998 Chris Jones"; +const int NONE = -1, LEFT = 0, RIGHT = 1, MIDDLE = 2; +char currentcursor = 0; +// virtual mouse cursor coordinates +int mousex = 0, mousey = 0, numcurso = -1, hotx = 0, hoty = 0; +// real mouse coordinates and bounds +int real_mouse_x = 0, real_mouse_y = 0; +int boundx1 = 0, boundx2 = 99999, boundy1 = 0, boundy2 = 99999; +int disable_mgetgraphpos = 0; +char ignore_bounds = 0; +extern char alpha_blend_cursor ; +Bitmap *mousecurs[MAXCURSORS]; +extern color palette[256]; +extern volatile bool switched_away; + +namespace Mouse +{ + // Tells whether mouse was locked to the game window + bool LockedToWindow = false; + + // Screen rectangle, in which the mouse movement is controlled by engine + Rect ControlRect; + // Mouse control enabled flag + bool ControlEnabled = false; + // Flag that tells whether the mouse must be forced to stay inside control rect + bool ConfineInCtrlRect = false; + // Mouse speed value provided by user + float SpeedVal = 1.f; + // Mouse speed unit + float SpeedUnit = 1.f; + // Actual speed factor (cached) + float Speed = 1.f; + + + void AdjustPosition(int &x, int &y); +} + +void mgraphconfine(int x1, int y1, int x2, int y2) +{ + Mouse::ControlRect = Rect(x1, y1, x2, y2); + set_mouse_range(Mouse::ControlRect.Left, Mouse::ControlRect.Top, Mouse::ControlRect.Right, Mouse::ControlRect.Bottom); + Debug::Printf("Mouse confined: (%d,%d)-(%d,%d) (%dx%d)", + Mouse::ControlRect.Left, Mouse::ControlRect.Top, Mouse::ControlRect.Right, Mouse::ControlRect.Bottom, + Mouse::ControlRect.GetWidth(), Mouse::ControlRect.GetHeight()); +} + +void mgetgraphpos() +{ + poll_mouse(); + if (disable_mgetgraphpos) + { + // The cursor coordinates are provided from alternate source; + // in this case we completely ignore actual cursor movement. + if (!ignore_bounds && + (mousex < boundx1 || mousey < boundy1 || mousex > boundx2 || mousey > boundy2)) + { + mousex = Math::Clamp(mousex, boundx1, boundx2); + mousey = Math::Clamp(mousey, boundy1, boundy2); + msetgraphpos(mousex, mousey); + } + return; + } + + if (!switched_away && Mouse::ControlEnabled) + { + // Control mouse movement by querying mouse mickeys (movement deltas) + // and applying them to saved mouse coordinates. + int mickey_x, mickey_y; + get_mouse_mickeys(&mickey_x, &mickey_y); + + // Apply mouse speed + int dx = Mouse::Speed * mickey_x; + int dy = Mouse::Speed * mickey_y; + + // + // Perform actual cursor update + //--------------------------------------------------------------------- + // If the real cursor is inside the control rectangle (read - game window), + // then apply sensitivity factors and adjust real cursor position + if (Mouse::ControlRect.IsInside(real_mouse_x + dx, real_mouse_y + dy)) + { + real_mouse_x += dx; + real_mouse_y += dy; + position_mouse(real_mouse_x, real_mouse_y); + } + // Otherwise, if real cursor was moved outside the control rect, yet we + // are required to confine cursor inside one, then adjust cursor position + // to stay inside the rect's bounds. + else if (Mouse::ConfineInCtrlRect) + { + real_mouse_x = Math::Clamp(real_mouse_x + dx, Mouse::ControlRect.Left, Mouse::ControlRect.Right); + real_mouse_y = Math::Clamp(real_mouse_y + dy, Mouse::ControlRect.Top, Mouse::ControlRect.Bottom); + position_mouse(real_mouse_x, real_mouse_y); + } + // Lastly, if the real cursor is out of the control rect, simply add + // actual movement to keep up with the system cursor coordinates. + else + { + real_mouse_x += mickey_x; + real_mouse_y += mickey_y; + } + + // Do not update the game cursor if the real cursor is beyond the control rect + if (!Mouse::ControlRect.IsInside(real_mouse_x, real_mouse_y)) + return; + } + else + { + // Save real cursor coordinates provided by system + real_mouse_x = mouse_x; + real_mouse_y = mouse_y; + } + + // Set new in-game cursor position + mousex = real_mouse_x; + mousey = real_mouse_y; + + if (!ignore_bounds && + (mousex < boundx1 || mousey < boundy1 || mousex > boundx2 || mousey > boundy2)) + { + mousex = Math::Clamp(mousex, boundx1, boundx2); + mousey = Math::Clamp(mousey, boundy1, boundy2); + msetgraphpos(mousex, mousey); + } + + // Convert to virtual coordinates + Mouse::AdjustPosition(mousex, mousey); +} + +void msetcursorlimit(int x1, int y1, int x2, int y2) +{ + boundx1 = x1; + boundy1 = y1; + boundx2 = x2; + boundy2 = y2; +} + +int hotxwas = 0, hotywas = 0; +void domouse(int str) +{ + /* + TO USE THIS ROUTINE YOU MUST LOAD A MOUSE CURSOR USING mloadcursor. + YOU MUST ALSO REMEMBER TO CALL mfreemem AT THE END OF THE PROGRAM. + */ + int poow = mousecurs[currentcursor]->GetWidth(); + int pooh = mousecurs[currentcursor]->GetHeight(); + int smx = mousex - hotxwas, smy = mousey - hotywas; + const Rect &viewport = play.GetMainViewport(); + + mgetgraphpos(); + mousex -= hotx; + mousey -= hoty; + + if (mousex + poow >= viewport.GetWidth()) + poow = viewport.GetWidth() - mousex; + + if (mousey + pooh >= viewport.GetHeight()) + pooh = viewport.GetHeight() - mousey; + + mousex += hotx; + mousey += hoty; + hotxwas = hotx; + hotywas = hoty; +} + +int ismouseinbox(int lf, int tp, int rt, int bt) +{ + if ((mousex >= lf) & (mousex <= rt) & (mousey >= tp) & (mousey <= bt)) + return TRUE; + else + return FALSE; +} + +void mfreemem() +{ + for (int re = 0; re < numcurso; re++) { + delete mousecurs[re]; + } +} + + + + +void mloadwcursor(char *namm) +{ + color dummypal[256]; + if (wloadsprites(&dummypal[0], namm, mousecurs, 0, MAXCURSORS)) { + //printf("C_Load_wCursor: Error reading mouse cursor file\n"); + exit(1); + } +} + +int butwas = 0; +int mgetbutton() +{ + int toret = NONE; + poll_mouse(); + int butis = mouse_b; + + if ((butis > 0) & (butwas > 0)) + return NONE; // don't allow holding button down + + if (butis & 1) + { + toret = LEFT; +#if AGS_SIMULATE_RIGHT_CLICK + // j Ctrl-left click should be right-click + if (ags_iskeypressed(__allegro_KEY_LCONTROL) || ags_iskeypressed(__allegro_KEY_RCONTROL)) + { + toret = RIGHT; + } +#endif + } + else if (butis & 2) + toret = RIGHT; + else if (butis & 4) + toret = MIDDLE; + + butwas = butis; + return toret; +} + +const int MB_ARRAY[3] = { 1, 2, 4 }; +int misbuttondown(int buno) +{ + poll_mouse(); + if (mouse_b & MB_ARRAY[buno]) + return TRUE; + return FALSE; +} + +void msetgraphpos(int xa, int ya) +{ + real_mouse_x = xa; + real_mouse_y = ya; + position_mouse(real_mouse_x, real_mouse_y); +} + +void msethotspot(int xx, int yy) +{ + hotx = xx; // mousex -= hotx; mousey -= hoty; + hoty = yy; // mousex += hotx; mousey += hoty; +} + +int minstalled() +{ + return install_mouse(); +} + +void Mouse::AdjustPosition(int &x, int &y) +{ + x = GameScaling.X.UnScalePt(x) - play.GetMainViewport().Left; + y = GameScaling.Y.UnScalePt(y) - play.GetMainViewport().Top; +} + +void Mouse::SetGraphicArea() +{ + Rect dst_r = GameScaling.ScaleRange(play.GetMainViewport()); + mgraphconfine(dst_r.Left, dst_r.Top, dst_r.Right, dst_r.Bottom); +} + +void Mouse::SetMoveLimit(const Rect &r) +{ + Rect src_r = OffsetRect(r, play.GetMainViewport().GetLT()); + Rect dst_r = GameScaling.ScaleRange(src_r); + msetcursorlimit(dst_r.Left, dst_r.Top, dst_r.Right, dst_r.Bottom); +} + +void Mouse::SetPosition(const Point p) +{ + msetgraphpos(GameScaling.X.ScalePt(p.X + play.GetMainViewport().Left), GameScaling.Y.ScalePt(p.Y + play.GetMainViewport().Top)); +} + +bool Mouse::IsLockedToWindow() +{ + return LockedToWindow; +} + +bool Mouse::TryLockToWindow() +{ + if (!LockedToWindow) + LockedToWindow = platform->LockMouseToWindow(); + return LockedToWindow; +} + +void Mouse::UnlockFromWindow() +{ + platform->UnlockMouse(); + LockedToWindow = false; +} + +void Mouse::EnableControl(bool confine) +{ + ControlEnabled = true; + ConfineInCtrlRect = confine; +} + +void Mouse::DisableControl() +{ + ControlEnabled = false; + ConfineInCtrlRect = false; +} + +bool Mouse::IsControlEnabled() +{ + return ControlEnabled; +} + +void Mouse::SetSpeedUnit(float f) +{ + SpeedUnit = f; + Speed = SpeedVal / SpeedUnit; +} + +float Mouse::GetSpeedUnit() +{ + return SpeedUnit; +} + +void Mouse::SetSpeed(float speed) +{ + SpeedVal = Math::Max(0.f, speed); + Speed = SpeedUnit * SpeedVal; +} + +float Mouse::GetSpeed() +{ + return SpeedVal; +} diff --git a/engines/ags/engine/device/mousew32.h b/engines/ags/engine/device/mousew32.h new file mode 100644 index 00000000000..165dfd80db2 --- /dev/null +++ b/engines/ags/engine/device/mousew32.h @@ -0,0 +1,90 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// MOUSELIBW32.CPP +// +// Library of mouse functions for graphics and text mode +// +// (c) 1994 Chris Jones +// Win32 (allegro) update (c) 1999 Chris Jones +// +//============================================================================= + +#include "util/geometry.h" + +#define MAXCURSORS 20 + +#include "util/geometry.h" + +namespace AGS { namespace Common { class Bitmap; } } +using namespace AGS; // FIXME later + +void msetgraphpos(int,int); +// Sets the area of the screen within which the mouse can move +void mgraphconfine(int x1, int y1, int x2, int y2); +void mgetgraphpos(); +// Sets the area of the game frame (zero-based coordinates) where the mouse cursor is allowed to move; +// this function was meant to be used to achieve gameplay effect +void msetcursorlimit(int x1, int y1, int x2, int y2); +int ismouseinbox(int lf, int tp, int rt, int bt); +void mfreemem(); +void mloadwcursor(char *namm); +void msetgraphpos(int xa, int ya); +void msethotspot(int xx, int yy); +int minstalled(); + +namespace Mouse +{ + // Get if mouse is locked to the game window + bool IsLockedToWindow(); + // Try locking mouse to the game window + bool TryLockToWindow(); + // Unlock mouse from the game window + void UnlockFromWindow(); + + // Enable mouse movement control + void EnableControl(bool confine); + // Disable mouse movement control + void DisableControl(); + // Tell if the mouse movement control is enabled + bool IsControlEnabled(); + // Set base speed factor, which would serve as a mouse speed unit + void SetSpeedUnit(float f); + // Get base speed factor + float GetSpeedUnit(); + // Set speed factors + void SetSpeed(float speed); + // Get speed factor + float GetSpeed(); +} + +namespace Mouse +{ + // Updates limits of the area inside which the standard OS cursor is not shown; + // uses game's main viewport (in native coordinates) to calculate real area on screen + void SetGraphicArea(); + // Limits the area where the game cursor can move on virtual screen; + // parameter must be in native game coordinates + void SetMoveLimit(const Rect &r); + // Set actual OS cursor position on screen; parameter must be in native game coordinates + void SetPosition(const Point p); +} + + +extern int mousex, mousey; +extern int hotx, hoty; +extern int disable_mgetgraphpos; +extern char currentcursor; + +extern Common::Bitmap *mousecurs[MAXCURSORS]; diff --git a/engines/ags/engine/font/fonts_engine.cpp b/engines/ags/engine/font/fonts_engine.cpp new file mode 100644 index 00000000000..ebb17c04497 --- /dev/null +++ b/engines/ags/engine/font/fonts_engine.cpp @@ -0,0 +1,37 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Implementation from acfonts.cpp specific to Engine runtime +// +//============================================================================= + +#include +#include "ac/gamesetupstruct.h" + +extern int our_eip; +extern GameSetupStruct game; + +//============================================================================= +// Engine-specific implementation split out of acfonts.cpp +//============================================================================= + +void set_our_eip(int eip) +{ + our_eip = eip; +} + +int get_our_eip() +{ + return our_eip; +} diff --git a/engines/ags/engine/game/game_init.cpp b/engines/ags/engine/game/game_init.cpp new file mode 100644 index 00000000000..4aabcacbcb6 --- /dev/null +++ b/engines/ags/engine/game/game_init.cpp @@ -0,0 +1,477 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/character.h" +#include "ac/charactercache.h" +#include "ac/dialog.h" +#include "ac/draw.h" +#include "ac/file.h" +#include "ac/game.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/gui.h" +#include "ac/movelist.h" +#include "ac/dynobj/all_dynamicclasses.h" +#include "ac/dynobj/all_scriptclasses.h" +#include "ac/statobj/agsstaticobject.h" +#include "ac/statobj/staticarray.h" +#include "debug/debug_log.h" +#include "debug/out.h" +#include "font/agsfontrenderer.h" +#include "font/fonts.h" +#include "game/game_init.h" +#include "gfx/bitmap.h" +#include "gfx/ddb.h" +#include "gui/guilabel.h" +#include "plugin/plugin_engine.h" +#include "script/cc_error.h" +#include "script/exports.h" +#include "script/script.h" +#include "script/script_runtime.h" +#include "util/string_utils.h" +#include "media/audio/audio_system.h" + +using namespace Common; +using namespace Engine; + +extern GameSetupStruct game; +extern int actSpsCount; +extern Bitmap **actsps; +extern IDriverDependantBitmap* *actspsbmp; +extern Bitmap **actspswb; +extern IDriverDependantBitmap* *actspswbbmp; +extern CachedActSpsData* actspswbcache; +extern CharacterCache *charcache; + +extern CCGUIObject ccDynamicGUIObject; +extern CCCharacter ccDynamicCharacter; +extern CCHotspot ccDynamicHotspot; +extern CCRegion ccDynamicRegion; +extern CCInventory ccDynamicInv; +extern CCGUI ccDynamicGUI; +extern CCObject ccDynamicObject; +extern CCDialog ccDynamicDialog; +extern CCAudioChannel ccDynamicAudio; +extern CCAudioClip ccDynamicAudioClip; +extern ScriptString myScriptStringImpl; +extern ScriptObject scrObj[MAX_ROOM_OBJECTS]; +extern ScriptGUI *scrGui; +extern ScriptHotspot scrHotspot[MAX_ROOM_HOTSPOTS]; +extern ScriptRegion scrRegion[MAX_ROOM_REGIONS]; +extern ScriptInvItem scrInv[MAX_INV]; +extern ScriptAudioChannel scrAudioChannel[MAX_SOUND_CHANNELS + 1]; + +extern ScriptDialogOptionsRendering ccDialogOptionsRendering; +extern ScriptDrawingSurface* dialogOptionsRenderingSurface; + +extern AGSStaticObject GlobalStaticManager; + +extern StaticArray StaticCharacterArray; +extern StaticArray StaticObjectArray; +extern StaticArray StaticGUIArray; +extern StaticArray StaticHotspotArray; +extern StaticArray StaticRegionArray; +extern StaticArray StaticInventoryArray; +extern StaticArray StaticDialogArray; + +extern std::vector moduleInst; +extern std::vector moduleInstFork; +extern std::vector moduleRepExecAddr; + +// Old dialog support (defined in ac/dialog) +extern std::vector< std::shared_ptr > old_dialog_scripts; +extern std::vector old_speech_lines; + +StaticArray StaticCharacterArray; +StaticArray StaticObjectArray; +StaticArray StaticGUIArray; +StaticArray StaticHotspotArray; +StaticArray StaticRegionArray; +StaticArray StaticInventoryArray; +StaticArray StaticDialogArray; + + +namespace AGS +{ +namespace Engine +{ + +String GetGameInitErrorText(GameInitErrorType err) +{ + switch (err) + { + case kGameInitErr_NoError: + return "No error."; + case kGameInitErr_NoFonts: + return "No fonts specified to be used in this game."; + case kGameInitErr_TooManyAudioTypes: + return "Too many audio types for this engine to handle."; + case kGameInitErr_EntityInitFail: + return "Failed to initialize game entities."; + case kGameInitErr_TooManyPlugins: + return "Too many plugins for this engine to handle."; + case kGameInitErr_PluginNameInvalid: + return "Plugin name is invalid."; + case kGameInitErr_ScriptLinkFailed: + return "Script link failed."; + } + return "Unknown error."; +} + +// Initializes audio channels and clips and registers them in the script system +void InitAndRegisterAudioObjects() +{ + for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) + { + scrAudioChannel[i].id = i; + ccRegisterManagedObject(&scrAudioChannel[i], &ccDynamicAudio); + } + + for (size_t i = 0; i < game.audioClips.size(); ++i) + { + // Note that as of 3.5.0 data format the clip IDs are still restricted + // to actual item index in array, so we don't make any difference + // between game versions, for now. + game.audioClips[i].id = i; + ccRegisterManagedObject(&game.audioClips[i], &ccDynamicAudioClip); + ccAddExternalDynamicObject(game.audioClips[i].scriptName, &game.audioClips[i], &ccDynamicAudioClip); + } +} + +// Initializes characters and registers them in the script system +void InitAndRegisterCharacters() +{ + characterScriptObjNames.resize(game.numcharacters); + for (int i = 0; i < game.numcharacters; ++i) + { + game.chars[i].walking = 0; + game.chars[i].animating = 0; + game.chars[i].pic_xoffs = 0; + game.chars[i].pic_yoffs = 0; + game.chars[i].blinkinterval = 140; + game.chars[i].blinktimer = game.chars[i].blinkinterval; + game.chars[i].index_id = i; + game.chars[i].blocking_width = 0; + game.chars[i].blocking_height = 0; + game.chars[i].prevroom = -1; + game.chars[i].loop = 0; + game.chars[i].frame = 0; + game.chars[i].walkwait = -1; + ccRegisterManagedObject(&game.chars[i], &ccDynamicCharacter); + + // export the character's script object + characterScriptObjNames[i] = game.chars[i].scrname; + ccAddExternalDynamicObject(characterScriptObjNames[i], &game.chars[i], &ccDynamicCharacter); + } +} + +// Initializes dialog and registers them in the script system +void InitAndRegisterDialogs() +{ + scrDialog = new ScriptDialog[game.numdialog]; + for (int i = 0; i < game.numdialog; ++i) + { + scrDialog[i].id = i; + scrDialog[i].reserved = 0; + ccRegisterManagedObject(&scrDialog[i], &ccDynamicDialog); + + if (!game.dialogScriptNames[i].IsEmpty()) + ccAddExternalDynamicObject(game.dialogScriptNames[i], &scrDialog[i], &ccDynamicDialog); + } +} + +// Initializes dialog options rendering objects and registers them in the script system +void InitAndRegisterDialogOptions() +{ + ccRegisterManagedObject(&ccDialogOptionsRendering, &ccDialogOptionsRendering); + + dialogOptionsRenderingSurface = new ScriptDrawingSurface(); + dialogOptionsRenderingSurface->isLinkedBitmapOnly = true; + long dorsHandle = ccRegisterManagedObject(dialogOptionsRenderingSurface, dialogOptionsRenderingSurface); + ccAddObjectReference(dorsHandle); +} + +// Initializes gui and registers them in the script system +HError InitAndRegisterGUI() +{ + scrGui = (ScriptGUI*)malloc(sizeof(ScriptGUI) * game.numgui); + for (int i = 0; i < game.numgui; ++i) + { + scrGui[i].id = -1; + } + + guiScriptObjNames.resize(game.numgui); + for (int i = 0; i < game.numgui; ++i) + { + // link controls to their parent guis + HError err = guis[i].RebuildArray(); + if (!err) + return err; + // export all the GUI's controls + export_gui_controls(i); + // copy the script name to its own memory location + // because ccAddExtSymbol only keeps a reference + guiScriptObjNames[i] = guis[i].Name; + scrGui[i].id = i; + ccAddExternalDynamicObject(guiScriptObjNames[i], &scrGui[i], &ccDynamicGUI); + ccRegisterManagedObject(&scrGui[i], &ccDynamicGUI); + } + return HError::None(); +} + +// Initializes inventory items and registers them in the script system +void InitAndRegisterInvItems() +{ + for (int i = 0; i < MAX_INV; ++i) + { + scrInv[i].id = i; + scrInv[i].reserved = 0; + ccRegisterManagedObject(&scrInv[i], &ccDynamicInv); + + if (!game.invScriptNames[i].IsEmpty()) + ccAddExternalDynamicObject(game.invScriptNames[i], &scrInv[i], &ccDynamicInv); + } +} + +// Initializes room hotspots and registers them in the script system +void InitAndRegisterHotspots() +{ + for (int i = 0; i < MAX_ROOM_HOTSPOTS; ++i) + { + scrHotspot[i].id = i; + scrHotspot[i].reserved = 0; + ccRegisterManagedObject(&scrHotspot[i], &ccDynamicHotspot); + } +} + +// Initializes room objects and registers them in the script system +void InitAndRegisterRoomObjects() +{ + for (int i = 0; i < MAX_ROOM_OBJECTS; ++i) + { + ccRegisterManagedObject(&scrObj[i], &ccDynamicObject); + } +} + +// Initializes room regions and registers them in the script system +void InitAndRegisterRegions() +{ + for (int i = 0; i < MAX_ROOM_REGIONS; ++i) + { + scrRegion[i].id = i; + scrRegion[i].reserved = 0; + ccRegisterManagedObject(&scrRegion[i], &ccDynamicRegion); + } +} + +// Registers static entity arrays in the script system +void RegisterStaticArrays() +{ + StaticCharacterArray.Create(&ccDynamicCharacter, sizeof(CharacterInfo), sizeof(CharacterInfo)); + StaticObjectArray.Create(&ccDynamicObject, sizeof(ScriptObject), sizeof(ScriptObject)); + StaticGUIArray.Create(&ccDynamicGUI, sizeof(ScriptGUI), sizeof(ScriptGUI)); + StaticHotspotArray.Create(&ccDynamicHotspot, sizeof(ScriptHotspot), sizeof(ScriptHotspot)); + StaticRegionArray.Create(&ccDynamicRegion, sizeof(ScriptRegion), sizeof(ScriptRegion)); + StaticInventoryArray.Create(&ccDynamicInv, sizeof(ScriptInvItem), sizeof(ScriptInvItem)); + StaticDialogArray.Create(&ccDynamicDialog, sizeof(ScriptDialog), sizeof(ScriptDialog)); + + ccAddExternalStaticArray("character",&game.chars[0], &StaticCharacterArray); + ccAddExternalStaticArray("object",&scrObj[0], &StaticObjectArray); + ccAddExternalStaticArray("gui",&scrGui[0], &StaticGUIArray); + ccAddExternalStaticArray("hotspot",&scrHotspot[0], &StaticHotspotArray); + ccAddExternalStaticArray("region",&scrRegion[0], &StaticRegionArray); + ccAddExternalStaticArray("inventory",&scrInv[0], &StaticInventoryArray); + ccAddExternalStaticArray("dialog", &scrDialog[0], &StaticDialogArray); +} + +// Initializes various game entities and registers them in the script system +HError InitAndRegisterGameEntities() +{ + InitAndRegisterAudioObjects(); + InitAndRegisterCharacters(); + InitAndRegisterDialogs(); + InitAndRegisterDialogOptions(); + HError err = InitAndRegisterGUI(); + if (!err) + return err; + InitAndRegisterInvItems(); + + InitAndRegisterHotspots(); + InitAndRegisterRegions(); + InitAndRegisterRoomObjects(); + play.CreatePrimaryViewportAndCamera(); + + RegisterStaticArrays(); + + setup_player_character(game.playercharacter); + if (loaded_game_file_version >= kGameVersion_270) + ccAddExternalStaticObject("player", &_sc_PlayerCharPtr, &GlobalStaticManager); + return HError::None(); +} + +void LoadFonts(GameDataVersion data_ver) +{ + for (int i = 0; i < game.numfonts; ++i) + { + if (!wloadfont_size(i, game.fonts[i])) + quitprintf("Unable to load font %d, no renderer could load a matching file", i); + } +} + +void AllocScriptModules() +{ + moduleInst.resize(numScriptModules, nullptr); + moduleInstFork.resize(numScriptModules, nullptr); + moduleRepExecAddr.resize(numScriptModules); + repExecAlways.moduleHasFunction.resize(numScriptModules, true); + lateRepExecAlways.moduleHasFunction.resize(numScriptModules, true); + getDialogOptionsDimensionsFunc.moduleHasFunction.resize(numScriptModules, true); + renderDialogOptionsFunc.moduleHasFunction.resize(numScriptModules, true); + getDialogOptionUnderCursorFunc.moduleHasFunction.resize(numScriptModules, true); + runDialogOptionMouseClickHandlerFunc.moduleHasFunction.resize(numScriptModules, true); + runDialogOptionKeyPressHandlerFunc.moduleHasFunction.resize(numScriptModules, true); + runDialogOptionRepExecFunc.moduleHasFunction.resize(numScriptModules, true); + for (int i = 0; i < numScriptModules; ++i) + { + moduleRepExecAddr[i].Invalidate(); + } +} + +HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion data_ver) +{ + const ScriptAPIVersion base_api = (ScriptAPIVersion)game.options[OPT_BASESCRIPTAPI]; + const ScriptAPIVersion compat_api = (ScriptAPIVersion)game.options[OPT_SCRIPTCOMPATLEV]; + if (data_ver >= kGameVersion_341) + { + // TODO: find a way to either automate this list of strings or make it more visible (shared & easier to find in engine code) + // TODO: stack-allocated strings, here and in other similar places + const String scapi_names[kScriptAPI_Current + 1] = {"v3.2.1", "v3.3.0", "v3.3.4", "v3.3.5", "v3.4.0", "v3.4.1", "v3.5.0", "v3.5.0.7"}; + Debug::Printf(kDbgMsg_Info, "Requested script API: %s (%d), compat level: %s (%d)", + base_api >= 0 && base_api <= kScriptAPI_Current ? scapi_names[base_api].GetCStr() : "unknown", base_api, + compat_api >= 0 && compat_api <= kScriptAPI_Current ? scapi_names[compat_api].GetCStr() : "unknown", compat_api); + } + // If the game was compiled using unsupported version of the script API, + // we warn about potential incompatibilities but proceed further. + if (game.options[OPT_BASESCRIPTAPI] > kScriptAPI_Current) + platform->DisplayAlert("Warning: this game requests a higher version of AGS script API, it may not run correctly or run at all."); + + // + // 1. Check that the loaded data is valid and compatible with the current + // engine capabilities. + // + if (game.numfonts == 0) + return new GameInitError(kGameInitErr_NoFonts); + if (game.audioClipTypes.size() > MAX_AUDIO_TYPES) + return new GameInitError(kGameInitErr_TooManyAudioTypes, String::FromFormat("Required: %u, max: %d", game.audioClipTypes.size(), MAX_AUDIO_TYPES)); + + // + // 2. Apply overriding config settings + // + // The earlier versions of AGS provided support for "upscaling" low-res + // games (320x200 and 320x240) to hi-res (640x400 and 640x480 + // respectively). The script API has means for detecting if the game is + // running upscaled, and game developer could use this opportunity to setup + // game accordingly (e.g. assign hi-res fonts, etc). + // This feature is officially deprecated since 3.1.0, however the engine + // itself still supports it, technically. + // This overriding option re-enables "upscaling". It works ONLY for low-res + // resolutions, such as 320x200 and 320x240. + if (usetup.override_upscale) + { + if (game.GetResolutionType() == kGameResolution_320x200) + game.SetGameResolution(kGameResolution_640x400); + else if (game.GetResolutionType() == kGameResolution_320x240) + game.SetGameResolution(kGameResolution_640x480); + } + + // + // 3. Allocate and init game objects + // + charextra = (CharacterExtras*)calloc(game.numcharacters, sizeof(CharacterExtras)); + charcache = (CharacterCache*)calloc(1,sizeof(CharacterCache)*game.numcharacters+5); + mls = (MoveList*)calloc(game.numcharacters + MAX_ROOM_OBJECTS + 1, sizeof(MoveList)); + actSpsCount = game.numcharacters + MAX_ROOM_OBJECTS + 2; + actsps = (Bitmap **)calloc(actSpsCount, sizeof(Bitmap *)); + actspsbmp = (IDriverDependantBitmap**)calloc(actSpsCount, sizeof(IDriverDependantBitmap*)); + actspswb = (Bitmap **)calloc(actSpsCount, sizeof(Bitmap *)); + actspswbbmp = (IDriverDependantBitmap**)calloc(actSpsCount, sizeof(IDriverDependantBitmap*)); + actspswbcache = (CachedActSpsData*)calloc(actSpsCount, sizeof(CachedActSpsData)); + play.charProps.resize(game.numcharacters); + old_dialog_scripts = ents.OldDialogScripts; + old_speech_lines = ents.OldSpeechLines; + HError err = InitAndRegisterGameEntities(); + if (!err) + return new GameInitError(kGameInitErr_EntityInitFail, err); + LoadFonts(data_ver); + + // + // 4. Initialize certain runtime variables + // + game_paused = 0; // reset the game paused flag + ifacepopped = -1; + + String svg_suffix; + if (game.saveGameFileExtension[0] != 0) + svg_suffix.Format(".%s", game.saveGameFileExtension); + set_save_game_suffix(svg_suffix); + + play.score_sound = game.scoreClipID; + play.fade_effect = game.options[OPT_FADETYPE]; + + // + // 5. Initialize runtime state of certain game objects + // + for (int i = 0; i < numguilabels; ++i) + { + // labels are not clickable by default + guilabels[i].SetClickable(false); + } + play.gui_draw_order = (int*)calloc(game.numgui * sizeof(int), 1); + update_gui_zorder(); + calculate_reserved_channel_count(); + + // + // 6. Register engine API exports + // NOTE: we must do this before plugin start, because some plugins may + // require access to script API at initialization time. + // + ccSetScriptAliveTimer(150000); + ccSetStringClassImpl(&myScriptStringImpl); + setup_script_exports(base_api, compat_api); + + // + // 7. Start up plugins + // + pl_register_plugins(ents.PluginInfos); + pl_startup_plugins(); + + // + // 8. Create script modules + // NOTE: we must do this after plugins, because some plugins may export + // script symbols too. + // + gamescript = ents.GlobalScript; + dialogScriptsScript = ents.DialogScript; + numScriptModules = ents.ScriptModules.size(); + scriptModules = ents.ScriptModules; + AllocScriptModules(); + if (create_global_script()) + return new GameInitError(kGameInitErr_ScriptLinkFailed, ccErrorString); + + return HGameInitError::None(); +} + +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/game/game_init.h b/engines/ags/engine/game/game_init.h new file mode 100644 index 00000000000..64f3fcad495 --- /dev/null +++ b/engines/ags/engine/game/game_init.h @@ -0,0 +1,57 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// This unit provides game initialization routine, which takes place after +// main game file was successfully loaded. +// +//============================================================================= + +#ifndef __AGS_EE_GAME__GAMEINIT_H +#define __AGS_EE_GAME__GAMEINIT_H + +#include "game/main_game_file.h" +#include "util/string.h" + +namespace AGS +{ +namespace Engine +{ + +using namespace Common; + +// Error codes for initializing the game +enum GameInitErrorType +{ + kGameInitErr_NoError, + // currently AGS requires at least one font to be present in game + kGameInitErr_NoFonts, + kGameInitErr_TooManyAudioTypes, + kGameInitErr_EntityInitFail, + kGameInitErr_TooManyPlugins, + kGameInitErr_PluginNameInvalid, + kGameInitErr_ScriptLinkFailed +}; + +String GetGameInitErrorText(GameInitErrorType err); + +typedef TypedCodeError GameInitError; +typedef ErrorHandle HGameInitError; + +// Sets up game state for play using preloaded data +HGameInitError InitGameState(const LoadedGameEntities &ents, GameDataVersion data_ver); + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GAME__GAMEINIT_H diff --git a/engines/ags/engine/game/savegame.cpp b/engines/ags/engine/game/savegame.cpp new file mode 100644 index 00000000000..006ef030f87 --- /dev/null +++ b/engines/ags/engine/game/savegame.cpp @@ -0,0 +1,798 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/character.h" +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/dynamicsprite.h" +#include "ac/event.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/gamesetup.h" +#include "ac/global_audio.h" +#include "ac/global_character.h" +#include "ac/gui.h" +#include "ac/mouse.h" +#include "ac/overlay.h" +#include "ac/region.h" +#include "ac/richgamemedia.h" +#include "ac/room.h" +#include "ac/roomstatus.h" +#include "ac/spritecache.h" +#include "ac/system.h" +#include "ac/timer.h" +#include "debug/out.h" +#include "device/mousew32.h" +#include "gfx/bitmap.h" +#include "gfx/ddb.h" +#include "gfx/graphicsdriver.h" +#include "game/savegame.h" +#include "game/savegame_components.h" +#include "game/savegame_internal.h" +#include "main/engine.h" +#include "main/main.h" +#include "platform/base/agsplatformdriver.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "script/script.h" +#include "script/cc_error.h" +#include "util/alignedstream.h" +#include "util/file.h" +#include "util/stream.h" +#include "util/string_utils.h" +#include "media/audio/audio_system.h" + +using namespace Common; +using namespace Engine; + +// function is currently implemented in game.cpp +HSaveError restore_game_data(Stream *in, SavegameVersion svg_version, const PreservedParams &pp, RestoredData &r_data); + +extern GameSetupStruct game; +extern Bitmap **guibg; +extern AGS::Engine::IDriverDependantBitmap **guibgbmp; +extern AGS::Engine::IGraphicsDriver *gfxDriver; +extern Bitmap *dynamicallyCreatedSurfaces[MAX_DYNAMIC_SURFACES]; +extern Bitmap *raw_saved_screen; +extern RoomStatus troom; +extern RoomStatus *croom; + + +namespace AGS +{ +namespace Engine +{ + +const String SavegameSource::LegacySignature = "Adventure Game Studio saved game"; +const String SavegameSource::Signature = "Adventure Game Studio saved game v2"; + +SavegameSource::SavegameSource() + : Version(kSvgVersion_Undefined) +{ +} + +SavegameDescription::SavegameDescription() + : MainDataVersion(kGameVersion_Undefined) + , ColorDepth(0) +{ +} + +PreservedParams::PreservedParams() + : SpeechVOX(0) + , MusicVOX(0) + , GlScDataSize(0) +{ +} + +RestoredData::ScriptData::ScriptData() + : Len(0) +{ +} + +RestoredData::RestoredData() + : FPS(0) + , RoomVolume(kRoomVolumeNormal) + , CursorID(0) + , CursorMode(0) +{ + memset(RoomLightLevels, 0, sizeof(RoomLightLevels)); + memset(RoomTintLevels, 0, sizeof(RoomTintLevels)); + memset(RoomZoomLevels1, 0, sizeof(RoomZoomLevels1)); + memset(RoomZoomLevels2, 0, sizeof(RoomZoomLevels2)); + memset(DoAmbient, 0, sizeof(DoAmbient)); +} + +String GetSavegameErrorText(SavegameErrorType err) +{ + switch (err) + { + case kSvgErr_NoError: + return "No error."; + case kSvgErr_FileOpenFailed: + return "File not found or could not be opened."; + case kSvgErr_SignatureFailed: + return "Not an AGS saved game or unsupported format."; + case kSvgErr_FormatVersionNotSupported: + return "Save format version not supported."; + case kSvgErr_IncompatibleEngine: + return "Save was written by incompatible engine, or file is corrupted."; + case kSvgErr_GameGuidMismatch: + return "Game GUID does not match, saved by a different game."; + case kSvgErr_ComponentListOpeningTagFormat: + return "Failed to parse opening tag of the components list."; + case kSvgErr_ComponentListClosingTagMissing: + return "Closing tag of the components list was not met."; + case kSvgErr_ComponentOpeningTagFormat: + return "Failed to parse opening component tag."; + case kSvgErr_ComponentClosingTagFormat: + return "Failed to parse closing component tag."; + case kSvgErr_ComponentSizeMismatch: + return "Component data size mismatch."; + case kSvgErr_UnsupportedComponent: + return "Unknown and/or unsupported component."; + case kSvgErr_ComponentSerialization: + return "Failed to write the savegame component."; + case kSvgErr_ComponentUnserialization: + return "Failed to restore the savegame component."; + case kSvgErr_InconsistentFormat: + return "Inconsistent format, or file is corrupted."; + case kSvgErr_UnsupportedComponentVersion: + return "Component data version not supported."; + case kSvgErr_GameContentAssertion: + return "Saved content does not match current game."; + case kSvgErr_InconsistentData: + return "Inconsistent save data, or file is corrupted."; + case kSvgErr_InconsistentPlugin: + return "One of the game plugins did not restore its game data correctly."; + case kSvgErr_DifferentColorDepth: + return "Saved with the engine running at a different colour depth."; + case kSvgErr_GameObjectInitFailed: + return "Game object initialization failed after save restoration."; + } + return "Unknown error."; +} + +Bitmap *RestoreSaveImage(Stream *in) +{ + if (in->ReadInt32()) + return read_serialized_bitmap(in); + return nullptr; +} + +void SkipSaveImage(Stream *in) +{ + if (in->ReadInt32()) + skip_serialized_bitmap(in); +} + +HSaveError ReadDescription(Stream *in, SavegameVersion &svg_ver, SavegameDescription &desc, SavegameDescElem elems) +{ + svg_ver = (SavegameVersion)in->ReadInt32(); + if (svg_ver < kSvgVersion_LowestSupported || svg_ver > kSvgVersion_Current) + return new SavegameError(kSvgErr_FormatVersionNotSupported, + String::FromFormat("Required: %d, supported: %d - %d.", svg_ver, kSvgVersion_LowestSupported, kSvgVersion_Current)); + + // Enviroment information + if (elems & kSvgDesc_EnvInfo) + { + desc.EngineName = StrUtil::ReadString(in); + desc.EngineVersion.SetFromString(StrUtil::ReadString(in)); + desc.GameGuid = StrUtil::ReadString(in); + desc.GameTitle = StrUtil::ReadString(in); + desc.MainDataFilename = StrUtil::ReadString(in); + if (svg_ver >= kSvgVersion_Cmp_64bit) + desc.MainDataVersion = (GameDataVersion)in->ReadInt32(); + desc.ColorDepth = in->ReadInt32(); + } + else + { + StrUtil::SkipString(in); + StrUtil::SkipString(in); + StrUtil::SkipString(in); + StrUtil::SkipString(in); + StrUtil::SkipString(in); + if (svg_ver >= kSvgVersion_Cmp_64bit) + in->ReadInt32(); // game data version + in->ReadInt32(); // color depth + } + // User description + if (elems & kSvgDesc_UserText) + desc.UserText = StrUtil::ReadString(in); + else + StrUtil::SkipString(in); + if (elems & kSvgDesc_UserImage) + desc.UserImage.reset(RestoreSaveImage(in)); + else + SkipSaveImage(in); + + return HSaveError::None(); +} + +HSaveError ReadDescription_v321(Stream *in, SavegameVersion &svg_ver, SavegameDescription &desc, SavegameDescElem elems) +{ + // Legacy savegame header + if (elems & kSvgDesc_UserText) + desc.UserText.Read(in); + else + StrUtil::SkipCStr(in); + svg_ver = (SavegameVersion)in->ReadInt32(); + + // Check saved game format version + if (svg_ver < kSvgVersion_LowestSupported || + svg_ver > kSvgVersion_Current) + { + return new SavegameError(kSvgErr_FormatVersionNotSupported, + String::FromFormat("Required: %d, supported: %d - %d.", svg_ver, kSvgVersion_LowestSupported, kSvgVersion_Current)); + } + + if (elems & kSvgDesc_UserImage) + desc.UserImage.reset(RestoreSaveImage(in)); + else + SkipSaveImage(in); + + String version_str = String::FromStream(in); + Version eng_version(version_str); + if (eng_version > EngineVersion || + eng_version < SavedgameLowestBackwardCompatVersion) + { + // Engine version is either non-forward or non-backward compatible + return new SavegameError(kSvgErr_IncompatibleEngine, + String::FromFormat("Required: %s, supported: %s - %s.", eng_version.LongString.GetCStr(), SavedgameLowestBackwardCompatVersion.LongString.GetCStr(), EngineVersion.LongString.GetCStr())); + } + if (elems & kSvgDesc_EnvInfo) + { + desc.MainDataFilename.Read(in); + in->ReadInt32(); // unscaled game height with borders, now obsolete + desc.ColorDepth = in->ReadInt32(); + } + else + { + StrUtil::SkipCStr(in); + in->ReadInt32(); // unscaled game height with borders, now obsolete + in->ReadInt32(); // color depth + } + + return HSaveError::None(); +} + +HSaveError OpenSavegameBase(const String &filename, SavegameSource *src, SavegameDescription *desc, SavegameDescElem elems) +{ + UStream in(File::OpenFileRead(filename)); + if (!in.get()) + return new SavegameError(kSvgErr_FileOpenFailed, String::FromFormat("Requested filename: %s.", filename.GetCStr())); + + // Skip MS Windows Vista rich media header + RICH_GAME_MEDIA_HEADER rich_media_header; + rich_media_header.ReadFromFile(in.get()); + + // Check saved game signature + bool is_new_save = false; + size_t pre_sig_pos = in->GetPosition(); + String svg_sig = String::FromStreamCount(in.get(), SavegameSource::Signature.GetLength()); + if (svg_sig.Compare(SavegameSource::Signature) == 0) + { + is_new_save = true; + } + else + { + in->Seek(pre_sig_pos, kSeekBegin); + svg_sig = String::FromStreamCount(in.get(), SavegameSource::LegacySignature.GetLength()); + if (svg_sig.Compare(SavegameSource::LegacySignature) != 0) + return new SavegameError(kSvgErr_SignatureFailed); + } + + SavegameVersion svg_ver; + SavegameDescription temp_desc; + HSaveError err; + if (is_new_save) + err = ReadDescription(in.get(), svg_ver, temp_desc, desc ? elems : kSvgDesc_None); + else + err = ReadDescription_v321(in.get(), svg_ver, temp_desc, desc ? elems : kSvgDesc_None); + if (!err) + return err; + + if (src) + { + src->Filename = filename; + src->Version = svg_ver; + src->InputStream.reset(in.release()); // give the stream away to the caller + } + if (desc) + { + if (elems & kSvgDesc_EnvInfo) + { + desc->EngineName = temp_desc.EngineName; + desc->EngineVersion = temp_desc.EngineVersion; + desc->GameGuid = temp_desc.GameGuid; + desc->GameTitle = temp_desc.GameTitle; + desc->MainDataFilename = temp_desc.MainDataFilename; + desc->MainDataVersion = temp_desc.MainDataVersion; + desc->ColorDepth = temp_desc.ColorDepth; + } + if (elems & kSvgDesc_UserText) + desc->UserText = temp_desc.UserText; + if (elems & kSvgDesc_UserImage) + desc->UserImage.reset(temp_desc.UserImage.release()); + } + return err; +} + +HSaveError OpenSavegame(const String &filename, SavegameSource &src, SavegameDescription &desc, SavegameDescElem elems) +{ + return OpenSavegameBase(filename, &src, &desc, elems); +} + +HSaveError OpenSavegame(const String &filename, SavegameDescription &desc, SavegameDescElem elems) +{ + return OpenSavegameBase(filename, nullptr, &desc, elems); +} + +// Prepares engine for actual save restore (stops processes, cleans up memory) +void DoBeforeRestore(PreservedParams &pp) +{ + pp.SpeechVOX = play.want_speech; + pp.MusicVOX = play.separate_music_lib; + + unload_old_room(); + delete raw_saved_screen; + raw_saved_screen = nullptr; + remove_screen_overlay(-1); + is_complete_overlay = 0; + is_text_overlay = 0; + + // cleanup dynamic sprites + // NOTE: sprite 0 is a special constant sprite that cannot be dynamic + for (int i = 1; i < spriteset.GetSpriteSlotCount(); ++i) + { + if (game.SpriteInfos[i].Flags & SPF_DYNAMICALLOC) + { + // do this early, so that it changing guibuts doesn't + // affect the restored data + free_dynamic_sprite(i); + } + } + + // cleanup GUI backgrounds + for (int i = 0; i < game.numgui; ++i) + { + delete guibg[i]; + guibg[i] = nullptr; + + if (guibgbmp[i]) + gfxDriver->DestroyDDB(guibgbmp[i]); + guibgbmp[i] = nullptr; + } + + // preserve script data sizes and cleanup scripts + pp.GlScDataSize = gameinst->globaldatasize; + delete gameinstFork; + delete gameinst; + gameinstFork = nullptr; + gameinst = nullptr; + pp.ScMdDataSize.resize(numScriptModules); + for (int i = 0; i < numScriptModules; ++i) + { + pp.ScMdDataSize[i] = moduleInst[i]->globaldatasize; + delete moduleInstFork[i]; + delete moduleInst[i]; + moduleInst[i] = nullptr; + } + + play.FreeProperties(); + play.FreeViewportsAndCameras(); + + delete roominstFork; + delete roominst; + roominstFork = nullptr; + roominst = nullptr; + + delete dialogScriptsInst; + dialogScriptsInst = nullptr; + + resetRoomStatuses(); + troom.FreeScriptData(); + troom.FreeProperties(); + free_do_once_tokens(); + + // unregister gui controls from API exports + // TODO: find out why are we doing this here? is this really necessary? + for (int i = 0; i < game.numgui; ++i) + { + unexport_gui_controls(i); + } + // Clear the managed object pool + ccUnregisterAllObjects(); + + // NOTE: channels are array of MAX_SOUND_CHANNELS+1 size + for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) + { + stop_and_destroy_channel_ex(i, false); + } + + clear_music_cache(); +} + +void RestoreViewportsAndCameras(const RestoredData &r_data) +{ + for (size_t i = 0; i < r_data.Cameras.size(); ++i) + { + const auto &cam_dat = r_data.Cameras[i]; + auto cam = play.GetRoomCamera(i); + cam->SetID(cam_dat.ID); + if ((cam_dat.Flags & kSvgCamPosLocked) != 0) + cam->Lock(); + else + cam->Release(); + cam->SetAt(cam_dat.Left, cam_dat.Top); + cam->SetSize(Size(cam_dat.Width, cam_dat.Height)); + } + for (size_t i = 0; i < r_data.Viewports.size(); ++i) + { + const auto &view_dat = r_data.Viewports[i]; + auto view = play.GetRoomViewport(i); + view->SetID(view_dat.ID); + view->SetVisible((view_dat.Flags & kSvgViewportVisible) != 0); + view->SetRect(RectWH(view_dat.Left, view_dat.Top, view_dat.Width, view_dat.Height)); + view->SetZOrder(view_dat.ZOrder); + // Restore camera link + int cam_index = view_dat.CamID; + if (cam_index < 0) continue; + auto cam = play.GetRoomCamera(cam_index); + view->LinkCamera(cam); + cam->LinkToViewport(view); + } + play.InvalidateViewportZOrder(); +} + +// Final processing after successfully restoring from save +HSaveError DoAfterRestore(const PreservedParams &pp, const RestoredData &r_data) +{ + // Use a yellow dialog highlight for older game versions + // CHECKME: it is dubious that this should be right here + if(loaded_game_file_version < kGameVersion_331) + play.dialog_options_highlight_color = DIALOG_OPTIONS_HIGHLIGHT_COLOR_DEFAULT; + + // Preserve whether the music vox is available + play.separate_music_lib = pp.MusicVOX; + // If they had the vox when they saved it, but they don't now + if ((pp.SpeechVOX < 0) && (play.want_speech >= 0)) + play.want_speech = (-play.want_speech) - 1; + // If they didn't have the vox before, but now they do + else if ((pp.SpeechVOX >= 0) && (play.want_speech < 0)) + play.want_speech = (-play.want_speech) - 1; + + // recache queued clips + for (int i = 0; i < play.new_music_queue_size; ++i) + { + play.new_music_queue[i].cachedClip = nullptr; + } + + // restore these to the ones retrieved from the save game + const size_t dynsurf_num = Math::Min((size_t)MAX_DYNAMIC_SURFACES, r_data.DynamicSurfaces.size()); + for (size_t i = 0; i < dynsurf_num; ++i) + { + dynamicallyCreatedSurfaces[i] = r_data.DynamicSurfaces[i]; + } + + for (int i = 0; i < game.numgui; ++i) + export_gui_controls(i); + update_gui_zorder(); + + if (create_global_script()) + { + return new SavegameError(kSvgErr_GameObjectInitFailed, + String::FromFormat("Unable to recreate global script: %s", ccErrorString.GetCStr())); + } + + // read the global data into the newly created script + if (r_data.GlobalScript.Data.get()) + memcpy(gameinst->globaldata, r_data.GlobalScript.Data.get(), + Math::Min((size_t)gameinst->globaldatasize, r_data.GlobalScript.Len)); + + // restore the script module data + for (int i = 0; i < numScriptModules; ++i) + { + if (r_data.ScriptModules[i].Data.get()) + memcpy(moduleInst[i]->globaldata, r_data.ScriptModules[i].Data.get(), + Math::Min((size_t)moduleInst[i]->globaldatasize, r_data.ScriptModules[i].Len)); + } + + setup_player_character(game.playercharacter); + + // Save some parameters to restore them after room load + int gstimer=play.gscript_timer; + int oldx1 = play.mboundx1, oldx2 = play.mboundx2; + int oldy1 = play.mboundy1, oldy2 = play.mboundy2; + + // disable the queue momentarily + int queuedMusicSize = play.music_queue_size; + play.music_queue_size = 0; + + update_polled_stuff_if_runtime(); + + // load the room the game was saved in + if (displayed_room >= 0) + load_new_room(displayed_room, nullptr); + + update_polled_stuff_if_runtime(); + + play.gscript_timer=gstimer; + // restore the correct room volume (they might have modified + // it with SetMusicVolume) + thisroom.Options.MusicVolume = r_data.RoomVolume; + + Mouse::SetMoveLimit(Rect(oldx1, oldy1, oldx2, oldy2)); + + set_cursor_mode(r_data.CursorMode); + set_mouse_cursor(r_data.CursorID); + if (r_data.CursorMode == MODE_USE) + SetActiveInventory(playerchar->activeinv); + // ensure that the current cursor is locked + spriteset.Precache(game.mcurs[r_data.CursorID].pic); + + set_window_title(play.game_name); + + update_polled_stuff_if_runtime(); + + if (displayed_room >= 0) + { + for (int i = 0; i < MAX_ROOM_BGFRAMES; ++i) + { + if (r_data.RoomBkgScene[i]) + { + thisroom.BgFrames[i].Graphic = r_data.RoomBkgScene[i]; + } + } + + in_new_room=3; // don't run "enters screen" events + // now that room has loaded, copy saved light levels in + for (size_t i = 0; i < MAX_ROOM_REGIONS; ++i) + { + thisroom.Regions[i].Light = r_data.RoomLightLevels[i]; + thisroom.Regions[i].Tint = r_data.RoomTintLevels[i]; + } + generate_light_table(); + + for (size_t i = 0; i < MAX_WALK_AREAS + 1; ++i) + { + thisroom.WalkAreas[i].ScalingFar = r_data.RoomZoomLevels1[i]; + thisroom.WalkAreas[i].ScalingNear = r_data.RoomZoomLevels2[i]; + } + + on_background_frame_change(); + } + + gui_disabled_style = convert_gui_disabled_style(game.options[OPT_DISABLEOFF]); + + // restore the queue now that the music is playing + play.music_queue_size = queuedMusicSize; + + if (play.digital_master_volume >= 0) + System_SetVolume(play.digital_master_volume); + + // Run audio clips on channels + // these two crossfading parameters have to be temporarily reset + const int cf_in_chan = play.crossfading_in_channel; + const int cf_out_chan = play.crossfading_out_channel; + play.crossfading_in_channel = 0; + play.crossfading_out_channel = 0; + + { + AudioChannelsLock lock; + // NOTE: channels are array of MAX_SOUND_CHANNELS+1 size + for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) + { + const RestoredData::ChannelInfo &chan_info = r_data.AudioChans[i]; + if (chan_info.ClipID < 0) + continue; + if ((size_t)chan_info.ClipID >= game.audioClips.size()) + { + return new SavegameError(kSvgErr_GameObjectInitFailed, + String::FromFormat("Invalid audio clip index: %d (clip count: %u).", chan_info.ClipID, game.audioClips.size())); + } + play_audio_clip_on_channel(i, &game.audioClips[chan_info.ClipID], + chan_info.Priority, chan_info.Repeat, chan_info.Pos); + + auto* ch = lock.GetChannel(i); + if (ch != nullptr) + { + ch->set_volume_direct(chan_info.VolAsPercent, chan_info.Vol); + ch->set_speed(chan_info.Speed); + ch->set_panning(chan_info.Pan); + ch->panningAsPercentage = chan_info.PanAsPercent; + ch->xSource = chan_info.XSource; + ch->ySource = chan_info.YSource; + ch->maximumPossibleDistanceAway = chan_info.MaxDist; + } + } + if ((cf_in_chan > 0) && (lock.GetChannel(cf_in_chan) != nullptr)) + play.crossfading_in_channel = cf_in_chan; + if ((cf_out_chan > 0) && (lock.GetChannel(cf_out_chan) != nullptr)) + play.crossfading_out_channel = cf_out_chan; + + // If there were synced audio tracks, the time taken to load in the + // different channels will have thrown them out of sync, so re-time it + // NOTE: channels are array of MAX_SOUND_CHANNELS+1 size + for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) + { + auto* ch = lock.GetChannelIfPlaying(i); + int pos = r_data.AudioChans[i].Pos; + if ((pos > 0) && (ch != nullptr)) + { + ch->seek(pos); + } + } + } // -- AudioChannelsLock + + // TODO: investigate loop range + for (int i = 1; i < MAX_SOUND_CHANNELS; ++i) + { + if (r_data.DoAmbient[i]) + PlayAmbientSound(i, r_data.DoAmbient[i], ambient[i].vol, ambient[i].x, ambient[i].y); + } + update_directional_sound_vol(); + + for (int i = 0; i < game.numgui; ++i) + { + guibg[i] = BitmapHelper::CreateBitmap(guis[i].Width, guis[i].Height, game.GetColorDepth()); + guibg[i] = ReplaceBitmapWithSupportedFormat(guibg[i]); + } + + recreate_overlay_ddbs(); + + guis_need_update = 1; + + RestoreViewportsAndCameras(r_data); + + play.ClearIgnoreInput(); // don't keep ignored input after save restore + update_polled_stuff_if_runtime(); + + pl_run_plugin_hooks(AGSE_POSTRESTOREGAME, 0); + + if (displayed_room < 0) + { + // the restart point, no room was loaded + load_new_room(playerchar->room, playerchar); + playerchar->prevroom = -1; + + first_room_initialization(); + } + + if ((play.music_queue_size > 0) && (cachedQueuedMusic == nullptr)) + { + cachedQueuedMusic = load_music_from_disk(play.music_queue[0], 0); + } + + // Test if the old-style audio had playing music and it was properly loaded + if (current_music_type > 0) + { + AudioChannelsLock lock; + + if ((crossFading > 0 && !lock.GetChannelIfPlaying(crossFading)) || + (crossFading <= 0 && !lock.GetChannelIfPlaying(SCHAN_MUSIC))) + { + current_music_type = 0; // playback failed, reset flag + } + } + + set_game_speed(r_data.FPS); + + return HSaveError::None(); +} + +HSaveError RestoreGameState(PStream in, SavegameVersion svg_version) +{ + PreservedParams pp; + RestoredData r_data; + DoBeforeRestore(pp); + HSaveError err; + if (svg_version >= kSvgVersion_Components) + err = SavegameComponents::ReadAll(in, svg_version, pp, r_data); + else + err = restore_game_data(in.get(), svg_version, pp, r_data); + if (!err) + return err; + return DoAfterRestore(pp, r_data); +} + + +void WriteSaveImage(Stream *out, const Bitmap *screenshot) +{ + // store the screenshot at the start to make it easily accesible + out->WriteInt32((screenshot == nullptr) ? 0 : 1); + + if (screenshot) + serialize_bitmap(screenshot, out); +} + +void WriteDescription(Stream *out, const String &user_text, const Bitmap *user_image) +{ + // Data format version + out->WriteInt32(kSvgVersion_Current); + // Enviroment information + StrUtil::WriteString("Adventure Game Studio run-time engine", out); + StrUtil::WriteString(EngineVersion.LongString, out); + StrUtil::WriteString(game.guid, out); + StrUtil::WriteString(game.gamename, out); + StrUtil::WriteString(ResPaths.GamePak.Name, out); + out->WriteInt32(loaded_game_file_version); + out->WriteInt32(game.GetColorDepth()); + // User description + StrUtil::WriteString(user_text, out); + WriteSaveImage(out, user_image); +} + +PStream StartSavegame(const String &filename, const String &user_text, const Bitmap *user_image) +{ + Stream *out = Common::File::CreateFile(filename); + if (!out) + return PStream(); + + // Initialize and write Vista header + RICH_GAME_MEDIA_HEADER vistaHeader; + memset(&vistaHeader, 0, sizeof(RICH_GAME_MEDIA_HEADER)); + memcpy(&vistaHeader.dwMagicNumber, RM_MAGICNUMBER, sizeof(int)); + vistaHeader.dwHeaderVersion = 1; + vistaHeader.dwHeaderSize = sizeof(RICH_GAME_MEDIA_HEADER); + vistaHeader.dwThumbnailOffsetHigherDword = 0; + vistaHeader.dwThumbnailOffsetLowerDword = 0; + vistaHeader.dwThumbnailSize = 0; + convert_guid_from_text_to_binary(game.guid, &vistaHeader.guidGameId[0]); + uconvert(game.gamename, U_ASCII, (char*)&vistaHeader.szGameName[0], U_UNICODE, RM_MAXLENGTH); + uconvert(user_text, U_ASCII, (char*)&vistaHeader.szSaveName[0], U_UNICODE, RM_MAXLENGTH); + vistaHeader.szLevelName[0] = 0; + vistaHeader.szComments[0] = 0; + // MS Windows Vista rich media header + vistaHeader.WriteToFile(out); + + // Savegame signature + out->Write(SavegameSource::Signature.GetCStr(), SavegameSource::Signature.GetLength()); + + // CHECKME: what is this plugin hook suppose to mean, and if it is called here correctly + pl_run_plugin_hooks(AGSE_PRESAVEGAME, 0); + + // Write descrition block + WriteDescription(out, user_text, user_image); + return PStream(out); +} + +void DoBeforeSave() +{ + if (play.cur_music_number >= 0) + { + if (IsMusicPlaying() == 0) + play.cur_music_number = -1; + } + + if (displayed_room >= 0) + { + // update the current room script's data segment copy + if (roominst) + save_room_data_segment(); + + // Update the saved interaction variable values + for (size_t i = 0; i < thisroom.LocalVariables.size() && i < (size_t)MAX_GLOBAL_VARIABLES; ++i) + croom->interactionVariableValues[i] = thisroom.LocalVariables[i].Value; + } +} + +void SaveGameState(PStream out) +{ + DoBeforeSave(); + SavegameComponents::WriteAllCommon(out); +} + +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/game/savegame.h b/engines/ags/engine/game/savegame.h new file mode 100644 index 00000000000..2feacc41a6f --- /dev/null +++ b/engines/ags/engine/game/savegame.h @@ -0,0 +1,169 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_GAME__SAVEGAME_H +#define __AGS_EE_GAME__SAVEGAME_H + +#include +#include "ac/game_version.h" +#include "util/error.h" +#include "util/version.h" + + +namespace AGS +{ + +namespace Common { class Bitmap; class Stream; } + +namespace Engine +{ + +using Common::Bitmap; +using Common::ErrorHandle; +using Common::TypedCodeError; +using Common::Stream; +using Common::String; +using Common::Version; + +typedef std::shared_ptr PStream; + +//----------------------------------------------------------------------------- +// Savegame version history +// +// 8 last old style saved game format (of AGS 3.2.1) +// 9 first new style (self-descriptive block-based) format version +//----------------------------------------------------------------------------- +enum SavegameVersion +{ + kSvgVersion_Undefined = 0, + kSvgVersion_321 = 8, + kSvgVersion_Components= 9, + kSvgVersion_Cmp_64bit = 10, + kSvgVersion_350_final = 11, + kSvgVersion_350_final2= 12, + kSvgVersion_Current = kSvgVersion_350_final2, + kSvgVersion_LowestSupported = kSvgVersion_321 // change if support dropped +}; + +// Error codes for save restoration routine +enum SavegameErrorType +{ + kSvgErr_NoError, + kSvgErr_FileOpenFailed, + kSvgErr_SignatureFailed, + kSvgErr_FormatVersionNotSupported, + kSvgErr_IncompatibleEngine, + kSvgErr_GameGuidMismatch, + kSvgErr_ComponentListOpeningTagFormat, + kSvgErr_ComponentListClosingTagMissing, + kSvgErr_ComponentOpeningTagFormat, + kSvgErr_ComponentClosingTagFormat, + kSvgErr_ComponentSizeMismatch, + kSvgErr_UnsupportedComponent, + kSvgErr_ComponentSerialization, + kSvgErr_ComponentUnserialization, + kSvgErr_InconsistentFormat, + kSvgErr_UnsupportedComponentVersion, + kSvgErr_GameContentAssertion, + kSvgErr_InconsistentData, + kSvgErr_InconsistentPlugin, + kSvgErr_DifferentColorDepth, + kSvgErr_GameObjectInitFailed, + kNumSavegameError +}; + +String GetSavegameErrorText(SavegameErrorType err); + +typedef TypedCodeError SavegameError; +typedef ErrorHandle HSaveError; +typedef std::unique_ptr UStream; +typedef std::unique_ptr UBitmap; + +// SavegameSource defines a successfully opened savegame stream +struct SavegameSource +{ + // Signature of the current savegame format + static const String Signature; + // Signature of the legacy savegame format + static const String LegacySignature; + + // Name of the savefile + String Filename; + // Savegame format version + SavegameVersion Version; + // A ponter to the opened stream + PStream InputStream; + + SavegameSource(); +}; + +// Supported elements of savegame description; +// these may be used as flags to define valid fields +enum SavegameDescElem +{ + kSvgDesc_None = 0, + kSvgDesc_EnvInfo = 0x0001, + kSvgDesc_UserText = 0x0002, + kSvgDesc_UserImage = 0x0004, + kSvgDesc_All = kSvgDesc_EnvInfo | kSvgDesc_UserText | kSvgDesc_UserImage +}; + +// SavegameDescription describes savegame with information about the enviroment +// it was created in, and custom data provided by user +struct SavegameDescription +{ + // Name of the engine that saved the game + String EngineName; + // Version of the engine that saved the game + Version EngineVersion; + // Guid of the game which made this save + String GameGuid; + // Title of the game which made this save + String GameTitle; + // Name of the main data file used; this is needed to properly + // load saves made by "minigames" + String MainDataFilename; + // Game's main data version; should be checked early to know + // if the save was made for the supported game format + GameDataVersion MainDataVersion; + // Native color depth of the game; this is required to + // properly restore dynamic graphics from the save + int ColorDepth; + + String UserText; + UBitmap UserImage; + + SavegameDescription(); +}; + + +// Opens savegame for reading; optionally reads description, if any is provided +HSaveError OpenSavegame(const String &filename, SavegameSource &src, + SavegameDescription &desc, SavegameDescElem elems = kSvgDesc_All); +// Opens savegame and reads the savegame description +HSaveError OpenSavegame(const String &filename, SavegameDescription &desc, SavegameDescElem elems = kSvgDesc_All); + +// Reads the game data from the save stream and reinitializes game state +HSaveError RestoreGameState(PStream in, SavegameVersion svg_version); + +// Opens savegame for writing and puts in savegame description +PStream StartSavegame(const String &filename, const String &user_text, const Bitmap *user_image); + +// Prepares game for saving state and writes game data into the save stream +void SaveGameState(PStream out); + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GAME__SAVEGAME_H diff --git a/engines/ags/engine/game/savegame_components.cpp b/engines/ags/engine/game/savegame_components.cpp new file mode 100644 index 00000000000..d309ed0ed6e --- /dev/null +++ b/engines/ags/engine/game/savegame_components.cpp @@ -0,0 +1,1392 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include + +#include "ac/audiocliptype.h" +#include "ac/character.h" +#include "ac/common.h" +#include "ac/dialogtopic.h" +#include "ac/draw.h" +#include "ac/dynamicsprite.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/gui.h" +#include "ac/mouse.h" +#include "ac/movelist.h" +#include "ac/overlay.h" +#include "ac/roomstatus.h" +#include "ac/screenoverlay.h" +#include "ac/spritecache.h" +#include "ac/view.h" +#include "ac/system.h" +#include "ac/dynobj/cc_serializer.h" +#include "debug/out.h" +#include "game/savegame_components.h" +#include "game/savegame_internal.h" +#include "gfx/bitmap.h" +#include "gui/animatingguibutton.h" +#include "gui/guibutton.h" +#include "gui/guiinv.h" +#include "gui/guilabel.h" +#include "gui/guilistbox.h" +#include "gui/guimain.h" +#include "gui/guislider.h" +#include "gui/guitextbox.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "script/cc_error.h" +#include "script/script.h" +#include "util/filestream.h" // TODO: needed only because plugins expect file handle +#include "media/audio/audio_system.h" + +using namespace Common; + +extern GameSetupStruct game; +extern color palette[256]; +extern DialogTopic *dialog; +extern AnimatingGUIButton animbuts[MAX_ANIMATING_BUTTONS]; +extern int numAnimButs; +extern ViewStruct *views; +extern Bitmap *dynamicallyCreatedSurfaces[MAX_DYNAMIC_SURFACES]; +extern RoomStruct thisroom; +extern RoomStatus troom; +extern Bitmap *raw_saved_screen; +extern MoveList *mls; + + +namespace AGS +{ +namespace Engine +{ + +namespace SavegameComponents +{ + +const String ComponentListTag = "Components"; + +void WriteFormatTag(PStream out, const String &tag, bool open = true) +{ + String full_tag = String::FromFormat(open ? "<%s>" : "", tag.GetCStr()); + out->Write(full_tag.GetCStr(), full_tag.GetLength()); +} + +bool ReadFormatTag(PStream in, String &tag, bool open = true) +{ + if (in->ReadByte() != '<') + return false; + if (!open && in->ReadByte() != '/') + return false; + tag.Empty(); + while (!in->EOS()) + { + char c = in->ReadByte(); + if (c == '>') + return true; + tag.AppendChar(c); + } + return false; // reached EOS before closing symbol +} + +bool AssertFormatTag(PStream in, const String &tag, bool open = true) +{ + String read_tag; + if (!ReadFormatTag(in, read_tag, open)) + return false; + return read_tag.Compare(tag) == 0; +} + +bool AssertFormatTagStrict(HSaveError &err, PStream in, const String &tag, bool open = true) +{ + + String read_tag; + if (!ReadFormatTag(in, read_tag, open) || read_tag.Compare(tag) != 0) + { + err = new SavegameError(kSvgErr_InconsistentFormat, + String::FromFormat("Mismatching tag: %s.", tag.GetCStr())); + return false; + } + return true; +} + +inline bool AssertCompatLimit(HSaveError &err, int count, int max_count, const char *content_name) +{ + if (count > max_count) + { + err = new SavegameError(kSvgErr_IncompatibleEngine, + String::FromFormat("Incompatible number of %s (count: %d, max: %d).", + content_name, count, max_count)); + return false; + } + return true; +} + +inline bool AssertCompatRange(HSaveError &err, int value, int min_value, int max_value, const char *content_name) +{ + if (value < min_value || value > max_value) + { + err = new SavegameError(kSvgErr_IncompatibleEngine, + String::FromFormat("Restore game error: incompatible %s (id: %d, range: %d - %d).", + content_name, value, min_value, max_value)); + return false; + } + return true; +} + +inline bool AssertGameContent(HSaveError &err, int new_val, int original_val, const char *content_name) +{ + if (new_val != original_val) + { + err = new SavegameError(kSvgErr_GameContentAssertion, + String::FromFormat("Mismatching number of %s (game: %d, save: %d).", + content_name, original_val, new_val)); + return false; + } + return true; +} + +inline bool AssertGameObjectContent(HSaveError &err, int new_val, int original_val, const char *content_name, + const char *obj_type, int obj_id) +{ + if (new_val != original_val) + { + err = new SavegameError(kSvgErr_GameContentAssertion, + String::FromFormat("Mismatching number of %s, %s #%d (game: %d, save: %d).", + content_name, obj_type, obj_id, original_val, new_val)); + return false; + } + return true; +} + +inline bool AssertGameObjectContent2(HSaveError &err, int new_val, int original_val, const char *content_name, + const char *obj1_type, int obj1_id, const char *obj2_type, int obj2_id) +{ + if (new_val != original_val) + { + err = new SavegameError(kSvgErr_GameContentAssertion, + String::FromFormat("Mismatching number of %s, %s #%d, %s #%d (game: %d, save: %d).", + content_name, obj1_type, obj1_id, obj2_type, obj2_id, original_val, new_val)); + return false; + } + return true; +} + + +void WriteCameraState(const Camera &cam, Stream *out) +{ + int flags = 0; + if (cam.IsLocked()) flags |= kSvgCamPosLocked; + out->WriteInt32(flags); + const Rect &rc = cam.GetRect(); + out->WriteInt32(rc.Left); + out->WriteInt32(rc.Top); + out->WriteInt32(rc.GetWidth()); + out->WriteInt32(rc.GetHeight()); +} + +void WriteViewportState(const Viewport &view, Stream *out) +{ + int flags = 0; + if (view.IsVisible()) flags |= kSvgViewportVisible; + out->WriteInt32(flags); + const Rect &rc = view.GetRect(); + out->WriteInt32(rc.Left); + out->WriteInt32(rc.Top); + out->WriteInt32(rc.GetWidth()); + out->WriteInt32(rc.GetHeight()); + out->WriteInt32(view.GetZOrder()); + auto cam = view.GetCamera(); + if (cam) + out->WriteInt32(cam->GetID()); + else + out->WriteInt32(-1); +} + +HSaveError WriteGameState(PStream out) +{ + // Game base + game.WriteForSavegame(out); + // Game palette + // TODO: probably no need to save this for hi/true-res game + out->WriteArray(palette, sizeof(color), 256); + + if (loaded_game_file_version <= kGameVersion_272) + { + // Global variables + out->WriteInt32(numGlobalVars); + for (int i = 0; i < numGlobalVars; ++i) + globalvars[i].Write(out.get()); + } + + // Game state + play.WriteForSavegame(out.get()); + // Other dynamic values + out->WriteInt32(frames_per_second); + out->WriteInt32(loopcounter); + out->WriteInt32(ifacepopped); + out->WriteInt32(game_paused); + // Mouse cursor + out->WriteInt32(cur_mode); + out->WriteInt32(cur_cursor); + out->WriteInt32(mouse_on_iface); + + // Viewports and cameras + int viewcam_flags = 0; + if (play.IsAutoRoomViewport()) + viewcam_flags |= kSvgGameAutoRoomView; + out->WriteInt32(viewcam_flags); + out->WriteInt32(play.GetRoomCameraCount()); + for (int i = 0; i < play.GetRoomCameraCount(); ++i) + WriteCameraState(*play.GetRoomCamera(i), out.get()); + out->WriteInt32(play.GetRoomViewportCount()); + for (int i = 0; i < play.GetRoomViewportCount(); ++i) + WriteViewportState(*play.GetRoomViewport(i), out.get()); + + return HSaveError::None(); +} + +void ReadLegacyCameraState(Stream *in, RestoredData &r_data) +{ + // Precreate viewport and camera and save data in temp structs + int camx = in->ReadInt32(); + int camy = in->ReadInt32(); + play.CreateRoomCamera(); + play.CreateRoomViewport(); + const auto &main_view = play.GetMainViewport(); + RestoredData::CameraData cam_dat; + cam_dat.ID = 0; + cam_dat.Left = camx; + cam_dat.Top = camy; + cam_dat.Width = main_view.GetWidth(); + cam_dat.Height = main_view.GetHeight(); + r_data.Cameras.push_back(cam_dat); + RestoredData::ViewportData view_dat; + view_dat.ID = 0; + view_dat.Width = main_view.GetWidth(); + view_dat.Height = main_view.GetHeight(); + view_dat.Flags = kSvgViewportVisible; + view_dat.CamID = 0; + r_data.Viewports.push_back(view_dat); +} + +void ReadCameraState(RestoredData &r_data, Stream *in) +{ + RestoredData::CameraData cam; + cam.ID = r_data.Cameras.size(); + cam.Flags = in->ReadInt32(); + cam.Left = in->ReadInt32(); + cam.Top = in->ReadInt32(); + cam.Width = in->ReadInt32(); + cam.Height = in->ReadInt32(); + r_data.Cameras.push_back(cam); +} + +void ReadViewportState(RestoredData &r_data, Stream *in) +{ + RestoredData::ViewportData view; + view.ID = r_data.Viewports.size(); + view.Flags = in->ReadInt32(); + view.Left = in->ReadInt32(); + view.Top = in->ReadInt32(); + view.Width = in->ReadInt32(); + view.Height = in->ReadInt32(); + view.ZOrder = in->ReadInt32(); + view.CamID = in->ReadInt32(); + r_data.Viewports.push_back(view); +} + +HSaveError ReadGameState(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + GameStateSvgVersion svg_ver = (GameStateSvgVersion)cmp_ver; + // Game base + game.ReadFromSavegame(in); + // Game palette + in->ReadArray(palette, sizeof(color), 256); + + if (loaded_game_file_version <= kGameVersion_272) + { + // Legacy interaction global variables + if (!AssertGameContent(err, in->ReadInt32(), numGlobalVars, "Global Variables")) + return err; + for (int i = 0; i < numGlobalVars; ++i) + globalvars[i].Read(in.get()); + } + + // Game state + play.ReadFromSavegame(in.get(), svg_ver, r_data); + + // Other dynamic values + r_data.FPS = in->ReadInt32(); + set_loop_counter(in->ReadInt32()); + ifacepopped = in->ReadInt32(); + game_paused = in->ReadInt32(); + // Mouse cursor state + r_data.CursorMode = in->ReadInt32(); + r_data.CursorID = in->ReadInt32(); + mouse_on_iface = in->ReadInt32(); + + // Viewports and cameras + if (svg_ver < kGSSvgVersion_3510) + { + ReadLegacyCameraState(in.get(), r_data); + r_data.Cameras[0].Flags = r_data.Camera0_Flags; + } + else + { + int viewcam_flags = in->ReadInt32(); + play.SetAutoRoomViewport((viewcam_flags & kSvgGameAutoRoomView) != 0); + // TODO: we create viewport and camera objects here because they are + // required for the managed pool deserialization, but read actual + // data into temp structs because we need to apply it after active + // room is loaded. + // See comments to RestoredData struct for further details. + int cam_count = in->ReadInt32(); + for (int i = 0; i < cam_count; ++i) + { + play.CreateRoomCamera(); + ReadCameraState(r_data, in.get()); + } + int view_count = in->ReadInt32(); + for (int i = 0; i < view_count; ++i) + { + play.CreateRoomViewport(); + ReadViewportState(r_data, in.get()); + } + } + return err; +} + +HSaveError WriteAudio(PStream out) +{ + AudioChannelsLock lock; + + // Game content assertion + out->WriteInt32(game.audioClipTypes.size()); + out->WriteInt32(game.audioClips.size()); // [ivan-mogilko] not necessary, kept only to avoid changing save format + // Audio types + for (size_t i = 0; i < game.audioClipTypes.size(); ++i) + { + game.audioClipTypes[i].WriteToSavegame(out.get()); + out->WriteInt32(play.default_audio_type_volumes[i]); + } + + // Audio clips and crossfade + for (int i = 0; i <= MAX_SOUND_CHANNELS; i++) + { + auto* ch = lock.GetChannelIfPlaying(i); + if ((ch != nullptr) && (ch->sourceClip != nullptr)) + { + out->WriteInt32(((ScriptAudioClip*)ch->sourceClip)->id); + out->WriteInt32(ch->get_pos()); + out->WriteInt32(ch->priority); + out->WriteInt32(ch->repeat ? 1 : 0); + out->WriteInt32(ch->vol); + out->WriteInt32(ch->panning); + out->WriteInt32(ch->volAsPercentage); + out->WriteInt32(ch->panningAsPercentage); + out->WriteInt32(ch->get_speed()); + // since version 1 + out->WriteInt32(ch->xSource); + out->WriteInt32(ch->ySource); + out->WriteInt32(ch->maximumPossibleDistanceAway); + } + else + { + out->WriteInt32(-1); + } + } + out->WriteInt32(crossFading); + out->WriteInt32(crossFadeVolumePerStep); + out->WriteInt32(crossFadeStep); + out->WriteInt32(crossFadeVolumeAtStart); + // CHECKME: why this needs to be saved? + out->WriteInt32(current_music_type); + + // Ambient sound + for (int i = 0; i < MAX_SOUND_CHANNELS; ++i) + ambient[i].WriteToFile(out.get()); + return HSaveError::None(); +} + +HSaveError ReadAudio(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + // Game content assertion + if (!AssertGameContent(err, in->ReadInt32(), game.audioClipTypes.size(), "Audio Clip Types")) + return err; + in->ReadInt32(); // audio clip count + /* [ivan-mogilko] looks like it's not necessary to assert, as there's no data serialized for clips + if (!AssertGameContent(err, in->ReadInt32(), game.audioClips.size(), "Audio Clips")) + return err;*/ + + // Audio types + for (size_t i = 0; i < game.audioClipTypes.size(); ++i) + { + game.audioClipTypes[i].ReadFromSavegame(in.get()); + play.default_audio_type_volumes[i] = in->ReadInt32(); + } + + // Audio clips and crossfade + for (int i = 0; i <= MAX_SOUND_CHANNELS; ++i) + { + RestoredData::ChannelInfo &chan_info = r_data.AudioChans[i]; + chan_info.Pos = 0; + chan_info.ClipID = in->ReadInt32(); + if (chan_info.ClipID >= 0) + { + chan_info.Pos = in->ReadInt32(); + if (chan_info.Pos < 0) + chan_info.Pos = 0; + chan_info.Priority = in->ReadInt32(); + chan_info.Repeat = in->ReadInt32(); + chan_info.Vol = in->ReadInt32(); + chan_info.Pan = in->ReadInt32(); + chan_info.VolAsPercent = in->ReadInt32(); + chan_info.PanAsPercent = in->ReadInt32(); + chan_info.Speed = 1000; + chan_info.Speed = in->ReadInt32(); + if (cmp_ver >= 1) + { + chan_info.XSource = in->ReadInt32(); + chan_info.YSource = in->ReadInt32(); + chan_info.MaxDist = in->ReadInt32(); + } + } + } + crossFading = in->ReadInt32(); + crossFadeVolumePerStep = in->ReadInt32(); + crossFadeStep = in->ReadInt32(); + crossFadeVolumeAtStart = in->ReadInt32(); + // preserve legacy music type setting + current_music_type = in->ReadInt32(); + + // Ambient sound + for (int i = 0; i < MAX_SOUND_CHANNELS; ++i) + ambient[i].ReadFromFile(in.get()); + for (int i = 1; i < MAX_SOUND_CHANNELS; ++i) + { + if (ambient[i].channel == 0) + { + r_data.DoAmbient[i] = 0; + } + else + { + r_data.DoAmbient[i] = ambient[i].num; + ambient[i].channel = 0; + } + } + return err; +} + +void WriteTimesRun272(const Interaction &intr, Stream *out) +{ + for (size_t i = 0; i < intr.Events.size(); ++i) + out->WriteInt32(intr.Events[i].TimesRun); +} + +void WriteInteraction272(const Interaction &intr, Stream *out) +{ + const size_t evt_count = intr.Events.size(); + out->WriteInt32(evt_count); + for (size_t i = 0; i < evt_count; ++i) + out->WriteInt32(intr.Events[i].Type); + WriteTimesRun272(intr, out); +} + +void ReadTimesRun272(Interaction &intr, Stream *in) +{ + for (size_t i = 0; i < intr.Events.size(); ++i) + intr.Events[i].TimesRun = in->ReadInt32(); +} + +HSaveError ReadInteraction272(Interaction &intr, Stream *in) +{ + HSaveError err; + const size_t evt_count = in->ReadInt32(); + if (!AssertCompatLimit(err, evt_count, MAX_NEWINTERACTION_EVENTS, "interactions")) + return err; + intr.Events.resize(evt_count); + for (size_t i = 0; i < evt_count; ++i) + intr.Events[i].Type = in->ReadInt32(); + ReadTimesRun272(intr, in); + return err; +} + +HSaveError WriteCharacters(PStream out) +{ + out->WriteInt32(game.numcharacters); + for (int i = 0; i < game.numcharacters; ++i) + { + game.chars[i].WriteToFile(out.get()); + charextra[i].WriteToFile(out.get()); + Properties::WriteValues(play.charProps[i], out.get()); + if (loaded_game_file_version <= kGameVersion_272) + WriteTimesRun272(*game.intrChar[i], out.get()); + // character movement path cache + mls[CHMLSOFFS + i].WriteToFile(out.get()); + } + return HSaveError::None(); +} + +HSaveError ReadCharacters(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + if (!AssertGameContent(err, in->ReadInt32(), game.numcharacters, "Characters")) + return err; + for (int i = 0; i < game.numcharacters; ++i) + { + game.chars[i].ReadFromFile(in.get()); + charextra[i].ReadFromFile(in.get()); + Properties::ReadValues(play.charProps[i], in.get()); + if (loaded_game_file_version <= kGameVersion_272) + ReadTimesRun272(*game.intrChar[i], in.get()); + // character movement path cache + err = mls[CHMLSOFFS + i].ReadFromFile(in.get(), cmp_ver > 0 ? 1 : 0); + if (!err) + return err; + } + return err; +} + +HSaveError WriteDialogs(PStream out) +{ + out->WriteInt32(game.numdialog); + for (int i = 0; i < game.numdialog; ++i) + { + dialog[i].WriteToSavegame(out.get()); + } + return HSaveError::None(); +} + +HSaveError ReadDialogs(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + if (!AssertGameContent(err, in->ReadInt32(), game.numdialog, "Dialogs")) + return err; + for (int i = 0; i < game.numdialog; ++i) + { + dialog[i].ReadFromSavegame(in.get()); + } + return err; +} + +HSaveError WriteGUI(PStream out) +{ + // GUI state + WriteFormatTag(out, "GUIs"); + out->WriteInt32(game.numgui); + for (int i = 0; i < game.numgui; ++i) + guis[i].WriteToSavegame(out.get()); + + WriteFormatTag(out, "GUIButtons"); + out->WriteInt32(numguibuts); + for (int i = 0; i < numguibuts; ++i) + guibuts[i].WriteToSavegame(out.get()); + + WriteFormatTag(out, "GUILabels"); + out->WriteInt32(numguilabels); + for (int i = 0; i < numguilabels; ++i) + guilabels[i].WriteToSavegame(out.get()); + + WriteFormatTag(out, "GUIInvWindows"); + out->WriteInt32(numguiinv); + for (int i = 0; i < numguiinv; ++i) + guiinv[i].WriteToSavegame(out.get()); + + WriteFormatTag(out, "GUISliders"); + out->WriteInt32(numguislider); + for (int i = 0; i < numguislider; ++i) + guislider[i].WriteToSavegame(out.get()); + + WriteFormatTag(out, "GUITextBoxes"); + out->WriteInt32(numguitext); + for (int i = 0; i < numguitext; ++i) + guitext[i].WriteToSavegame(out.get()); + + WriteFormatTag(out, "GUIListBoxes"); + out->WriteInt32(numguilist); + for (int i = 0; i < numguilist; ++i) + guilist[i].WriteToSavegame(out.get()); + + // Animated buttons + WriteFormatTag(out, "AnimatedButtons"); + out->WriteInt32(numAnimButs); + for (int i = 0; i < numAnimButs; ++i) + animbuts[i].WriteToFile(out.get()); + return HSaveError::None(); +} + +HSaveError ReadGUI(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + const GuiSvgVersion svg_ver = (GuiSvgVersion)cmp_ver; + // GUI state + if (!AssertFormatTagStrict(err, in, "GUIs")) + return err; + if (!AssertGameContent(err, in->ReadInt32(), game.numgui, "GUIs")) + return err; + for (int i = 0; i < game.numgui; ++i) + guis[i].ReadFromSavegame(in.get(), svg_ver); + + if (!AssertFormatTagStrict(err, in, "GUIButtons")) + return err; + if (!AssertGameContent(err, in->ReadInt32(), numguibuts, "GUI Buttons")) + return err; + for (int i = 0; i < numguibuts; ++i) + guibuts[i].ReadFromSavegame(in.get(), svg_ver); + + if (!AssertFormatTagStrict(err, in, "GUILabels")) + return err; + if (!AssertGameContent(err, in->ReadInt32(), numguilabels, "GUI Labels")) + return err; + for (int i = 0; i < numguilabels; ++i) + guilabels[i].ReadFromSavegame(in.get(), svg_ver); + + if (!AssertFormatTagStrict(err, in, "GUIInvWindows")) + return err; + if (!AssertGameContent(err, in->ReadInt32(), numguiinv, "GUI InvWindows")) + return err; + for (int i = 0; i < numguiinv; ++i) + guiinv[i].ReadFromSavegame(in.get(), svg_ver); + + if (!AssertFormatTagStrict(err, in, "GUISliders")) + return err; + if (!AssertGameContent(err, in->ReadInt32(), numguislider, "GUI Sliders")) + return err; + for (int i = 0; i < numguislider; ++i) + guislider[i].ReadFromSavegame(in.get(), svg_ver); + + if (!AssertFormatTagStrict(err, in, "GUITextBoxes")) + return err; + if (!AssertGameContent(err, in->ReadInt32(), numguitext, "GUI TextBoxes")) + return err; + for (int i = 0; i < numguitext; ++i) + guitext[i].ReadFromSavegame(in.get(), svg_ver); + + if (!AssertFormatTagStrict(err, in, "GUIListBoxes")) + return err; + if (!AssertGameContent(err, in->ReadInt32(), numguilist, "GUI ListBoxes")) + return err; + for (int i = 0; i < numguilist; ++i) + guilist[i].ReadFromSavegame(in.get(), svg_ver); + + // Animated buttons + if (!AssertFormatTagStrict(err, in, "AnimatedButtons")) + return err; + int anim_count = in->ReadInt32(); + if (!AssertCompatLimit(err, anim_count, MAX_ANIMATING_BUTTONS, "animated buttons")) + return err; + numAnimButs = anim_count; + for (int i = 0; i < numAnimButs; ++i) + animbuts[i].ReadFromFile(in.get()); + return err; +} + +HSaveError WriteInventory(PStream out) +{ + out->WriteInt32(game.numinvitems); + for (int i = 0; i < game.numinvitems; ++i) + { + game.invinfo[i].WriteToSavegame(out.get()); + Properties::WriteValues(play.invProps[i], out.get()); + if (loaded_game_file_version <= kGameVersion_272) + WriteTimesRun272(*game.intrInv[i], out.get()); + } + return HSaveError::None(); +} + +HSaveError ReadInventory(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + if (!AssertGameContent(err, in->ReadInt32(), game.numinvitems, "Inventory Items")) + return err; + for (int i = 0; i < game.numinvitems; ++i) + { + game.invinfo[i].ReadFromSavegame(in.get()); + Properties::ReadValues(play.invProps[i], in.get()); + if (loaded_game_file_version <= kGameVersion_272) + ReadTimesRun272(*game.intrInv[i], in.get()); + } + return err; +} + +HSaveError WriteMouseCursors(PStream out) +{ + out->WriteInt32(game.numcursors); + for (int i = 0; i < game.numcursors; ++i) + { + game.mcurs[i].WriteToSavegame(out.get()); + } + return HSaveError::None(); +} + +HSaveError ReadMouseCursors(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + if (!AssertGameContent(err, in->ReadInt32(), game.numcursors, "Mouse Cursors")) + return err; + for (int i = 0; i < game.numcursors; ++i) + { + game.mcurs[i].ReadFromSavegame(in.get()); + } + return err; +} + +HSaveError WriteViews(PStream out) +{ + out->WriteInt32(game.numviews); + for (int view = 0; view < game.numviews; ++view) + { + out->WriteInt32(views[view].numLoops); + for (int loop = 0; loop < views[view].numLoops; ++loop) + { + out->WriteInt32(views[view].loops[loop].numFrames); + for (int frame = 0; frame < views[view].loops[loop].numFrames; ++frame) + { + out->WriteInt32(views[view].loops[loop].frames[frame].sound); + out->WriteInt32(views[view].loops[loop].frames[frame].pic); + } + } + } + return HSaveError::None(); +} + +HSaveError ReadViews(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + if (!AssertGameContent(err, in->ReadInt32(), game.numviews, "Views")) + return err; + for (int view = 0; view < game.numviews; ++view) + { + if (!AssertGameObjectContent(err, in->ReadInt32(), views[view].numLoops, + "Loops", "View", view)) + return err; + for (int loop = 0; loop < views[view].numLoops; ++loop) + { + if (!AssertGameObjectContent2(err, in->ReadInt32(), views[view].loops[loop].numFrames, + "Frame", "View", view, "Loop", loop)) + return err; + for (int frame = 0; frame < views[view].loops[loop].numFrames; ++frame) + { + views[view].loops[loop].frames[frame].sound = in->ReadInt32(); + views[view].loops[loop].frames[frame].pic = in->ReadInt32(); + } + } + } + return err; +} + +HSaveError WriteDynamicSprites(PStream out) +{ + const soff_t ref_pos = out->GetPosition(); + out->WriteInt32(0); // number of dynamic sprites + out->WriteInt32(0); // top index + int count = 0; + int top_index = 1; + for (int i = 1; i < spriteset.GetSpriteSlotCount(); ++i) + { + if (game.SpriteInfos[i].Flags & SPF_DYNAMICALLOC) + { + count++; + top_index = i; + out->WriteInt32(i); + out->WriteInt32(game.SpriteInfos[i].Flags); + serialize_bitmap(spriteset[i], out.get()); + } + } + const soff_t end_pos = out->GetPosition(); + out->Seek(ref_pos, kSeekBegin); + out->WriteInt32(count); + out->WriteInt32(top_index); + out->Seek(end_pos, kSeekBegin); + return HSaveError::None(); +} + +HSaveError ReadDynamicSprites(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + const int spr_count = in->ReadInt32(); + // ensure the sprite set is at least large enough + // to accomodate top dynamic sprite index + const int top_index = in->ReadInt32(); + spriteset.EnlargeTo(top_index); + for (int i = 0; i < spr_count; ++i) + { + int id = in->ReadInt32(); + int flags = in->ReadInt32(); + add_dynamic_sprite(id, read_serialized_bitmap(in.get())); + game.SpriteInfos[id].Flags = flags; + } + return err; +} + +HSaveError WriteOverlays(PStream out) +{ + out->WriteInt32(screenover.size()); + for (const auto &over : screenover) + { + over.WriteToFile(out.get()); + serialize_bitmap(over.pic, out.get()); + } + return HSaveError::None(); +} + +HSaveError ReadOverlays(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + size_t over_count = in->ReadInt32(); + for (size_t i = 0; i < over_count; ++i) + { + ScreenOverlay over; + over.ReadFromFile(in.get(), cmp_ver); + if (over.hasSerializedBitmap) + over.pic = read_serialized_bitmap(in.get()); + screenover.push_back(over); + } + return err; +} + +HSaveError WriteDynamicSurfaces(PStream out) +{ + out->WriteInt32(MAX_DYNAMIC_SURFACES); + for (int i = 0; i < MAX_DYNAMIC_SURFACES; ++i) + { + if (dynamicallyCreatedSurfaces[i] == nullptr) + { + out->WriteInt8(0); + } + else + { + out->WriteInt8(1); + serialize_bitmap(dynamicallyCreatedSurfaces[i], out.get()); + } + } + return HSaveError::None(); +} + +HSaveError ReadDynamicSurfaces(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + if (!AssertCompatLimit(err, in->ReadInt32(), MAX_DYNAMIC_SURFACES, "Drawing Surfaces")) + return err; + // Load the surfaces into a temporary array since ccUnserialiseObjects will destroy them otherwise + r_data.DynamicSurfaces.resize(MAX_DYNAMIC_SURFACES); + for (int i = 0; i < MAX_DYNAMIC_SURFACES; ++i) + { + if (in->ReadInt8() == 0) + r_data.DynamicSurfaces[i] = nullptr; + else + r_data.DynamicSurfaces[i] = read_serialized_bitmap(in.get()); + } + return err; +} + +HSaveError WriteScriptModules(PStream out) +{ + // write the data segment of the global script + int data_len = gameinst->globaldatasize; + out->WriteInt32(data_len); + if (data_len > 0) + out->Write(gameinst->globaldata, data_len); + // write the script modules data segments + out->WriteInt32(numScriptModules); + for (int i = 0; i < numScriptModules; ++i) + { + data_len = moduleInst[i]->globaldatasize; + out->WriteInt32(data_len); + if (data_len > 0) + out->Write(moduleInst[i]->globaldata, data_len); + } + return HSaveError::None(); +} + +HSaveError ReadScriptModules(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + // read the global script data segment + int data_len = in->ReadInt32(); + if (!AssertGameContent(err, data_len, pp.GlScDataSize, "global script data")) + return err; + r_data.GlobalScript.Len = data_len; + r_data.GlobalScript.Data.reset(new char[data_len]); + in->Read(r_data.GlobalScript.Data.get(), data_len); + + if (!AssertGameContent(err, in->ReadInt32(), numScriptModules, "Script Modules")) + return err; + r_data.ScriptModules.resize(numScriptModules); + for (int i = 0; i < numScriptModules; ++i) + { + data_len = in->ReadInt32(); + if (!AssertGameObjectContent(err, data_len, pp.ScMdDataSize[i], "script module data", "module", i)) + return err; + r_data.ScriptModules[i].Len = data_len; + r_data.ScriptModules[i].Data.reset(new char[data_len]); + in->Read(r_data.ScriptModules[i].Data.get(), data_len); + } + return err; +} + +HSaveError WriteRoomStates(PStream out) +{ + // write the room state for all the rooms the player has been in + out->WriteInt32(MAX_ROOMS); + for (int i = 0; i < MAX_ROOMS; ++i) + { + if (isRoomStatusValid(i)) + { + RoomStatus *roomstat = getRoomStatus(i); + if (roomstat->beenhere) + { + out->WriteInt32(i); + WriteFormatTag(out, "RoomState", true); + roomstat->WriteToSavegame(out.get()); + WriteFormatTag(out, "RoomState", false); + } + else + out->WriteInt32(-1); + } + else + out->WriteInt32(-1); + } + return HSaveError::None(); +} + +HSaveError ReadRoomStates(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + int roomstat_count = in->ReadInt32(); + for (; roomstat_count > 0; --roomstat_count) + { + int id = in->ReadInt32(); + // If id == -1, then the player has not been there yet (or room state was reset) + if (id != -1) + { + if (!AssertCompatRange(err, id, 0, MAX_ROOMS - 1, "room index")) + return err; + if (!AssertFormatTagStrict(err, in, "RoomState", true)) + return err; + RoomStatus *roomstat = getRoomStatus(id); + roomstat->ReadFromSavegame(in.get()); + if (!AssertFormatTagStrict(err, in, "RoomState", false)) + return err; + } + } + return HSaveError::None(); +} + +HSaveError WriteThisRoom(PStream out) +{ + out->WriteInt32(displayed_room); + if (displayed_room < 0) + return HSaveError::None(); + + // modified room backgrounds + for (int i = 0; i < MAX_ROOM_BGFRAMES; ++i) + { + out->WriteBool(play.raw_modified[i] != 0); + if (play.raw_modified[i]) + serialize_bitmap(thisroom.BgFrames[i].Graphic.get(), out.get()); + } + out->WriteBool(raw_saved_screen != nullptr); + if (raw_saved_screen) + serialize_bitmap(raw_saved_screen, out.get()); + + // room region state + for (int i = 0; i < MAX_ROOM_REGIONS; ++i) + { + out->WriteInt32(thisroom.Regions[i].Light); + out->WriteInt32(thisroom.Regions[i].Tint); + } + for (int i = 0; i < MAX_WALK_AREAS + 1; ++i) + { + out->WriteInt32(thisroom.WalkAreas[i].ScalingFar); + out->WriteInt32(thisroom.WalkAreas[i].ScalingNear); + } + + // room object movement paths cache + out->WriteInt32(thisroom.ObjectCount + 1); + for (size_t i = 0; i < thisroom.ObjectCount + 1; ++i) + { + mls[i].WriteToFile(out.get()); + } + + // room music volume + out->WriteInt32(thisroom.Options.MusicVolume); + + // persistent room's indicator + const bool persist = displayed_room < MAX_ROOMS; + out->WriteBool(persist); + // write the current troom state, in case they save in temporary room + if (!persist) + troom.WriteToSavegame(out.get()); + return HSaveError::None(); +} + +HSaveError ReadThisRoom(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + HSaveError err; + displayed_room = in->ReadInt32(); + if (displayed_room < 0) + return err; + + // modified room backgrounds + for (int i = 0; i < MAX_ROOM_BGFRAMES; ++i) + { + play.raw_modified[i] = in->ReadBool(); + if (play.raw_modified[i]) + r_data.RoomBkgScene[i].reset(read_serialized_bitmap(in.get())); + else + r_data.RoomBkgScene[i] = nullptr; + } + if (in->ReadBool()) + raw_saved_screen = read_serialized_bitmap(in.get()); + + // room region state + for (int i = 0; i < MAX_ROOM_REGIONS; ++i) + { + r_data.RoomLightLevels[i] = in->ReadInt32(); + r_data.RoomTintLevels[i] = in->ReadInt32(); + } + for (int i = 0; i < MAX_WALK_AREAS + 1; ++i) + { + r_data.RoomZoomLevels1[i] = in->ReadInt32(); + r_data.RoomZoomLevels2[i] = in->ReadInt32(); + } + + // room object movement paths cache + int objmls_count = in->ReadInt32(); + if (!AssertCompatLimit(err, objmls_count, CHMLSOFFS, "room object move lists")) + return err; + for (int i = 0; i < objmls_count; ++i) + { + err = mls[i].ReadFromFile(in.get(), cmp_ver > 0 ? 1 : 0); + if (!err) + return err; + } + + // save the new room music vol for later use + r_data.RoomVolume = (RoomVolumeMod)in->ReadInt32(); + + // read the current troom state, in case they saved in temporary room + if (!in->ReadBool()) + troom.ReadFromSavegame(in.get()); + + return HSaveError::None(); +} + +HSaveError WriteManagedPool(PStream out) +{ + ccSerializeAllObjects(out.get()); + return HSaveError::None(); +} + +HSaveError ReadManagedPool(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + if (ccUnserializeAllObjects(in.get(), &ccUnserializer)) + { + return new SavegameError(kSvgErr_GameObjectInitFailed, + String::FromFormat("Managed pool deserialization failed: %s", ccErrorString.GetCStr())); + } + return HSaveError::None(); +} + +HSaveError WritePluginData(PStream out) +{ + auto pluginFileHandle = AGSE_SAVEGAME; + pl_set_file_handle(pluginFileHandle, out.get()); + pl_run_plugin_hooks(AGSE_SAVEGAME, pluginFileHandle); + pl_clear_file_handle(); + return HSaveError::None(); +} + +HSaveError ReadPluginData(PStream in, int32_t cmp_ver, const PreservedParams &pp, RestoredData &r_data) +{ + auto pluginFileHandle = AGSE_RESTOREGAME; + pl_set_file_handle(pluginFileHandle, in.get()); + pl_run_plugin_hooks(AGSE_RESTOREGAME, pluginFileHandle); + pl_clear_file_handle(); + return HSaveError::None(); +} + + +// Description of a supported game state serialization component +struct ComponentHandler +{ + String Name; // internal component's ID + int32_t Version; // current version to write and the highest supported version + int32_t LowestVersion; // lowest supported version that the engine can read + HSaveError (*Serialize) (PStream); + HSaveError (*Unserialize)(PStream, int32_t cmp_ver, const PreservedParams&, RestoredData&); +}; + +// Array of supported components +ComponentHandler ComponentHandlers[] = +{ + { + "Game State", + kGSSvgVersion_3510, + kGSSvgVersion_Initial, + WriteGameState, + ReadGameState + }, + { + "Audio", + 1, + 0, + WriteAudio, + ReadAudio + }, + { + "Characters", + 1, + 0, + WriteCharacters, + ReadCharacters + }, + { + "Dialogs", + 0, + 0, + WriteDialogs, + ReadDialogs + }, + { + "GUI", + kGuiSvgVersion_350, + kGuiSvgVersion_Initial, + WriteGUI, + ReadGUI + }, + { + "Inventory Items", + 0, + 0, + WriteInventory, + ReadInventory + }, + { + "Mouse Cursors", + 0, + 0, + WriteMouseCursors, + ReadMouseCursors + }, + { + "Views", + 0, + 0, + WriteViews, + ReadViews + }, + { + "Dynamic Sprites", + 0, + 0, + WriteDynamicSprites, + ReadDynamicSprites + }, + { + "Overlays", + 1, + 0, + WriteOverlays, + ReadOverlays + }, + { + "Drawing Surfaces", + 0, + 0, + WriteDynamicSurfaces, + ReadDynamicSurfaces + }, + { + "Script Modules", + 0, + 0, + WriteScriptModules, + ReadScriptModules + }, + { + "Room States", + 0, + 0, + WriteRoomStates, + ReadRoomStates + }, + { + "Loaded Room State", + 1, + 0, + WriteThisRoom, + ReadThisRoom + }, + { + "Managed Pool", + 1, + 0, + WriteManagedPool, + ReadManagedPool + }, + { + "Plugin Data", + 0, + 0, + WritePluginData, + ReadPluginData + }, + { nullptr, 0, 0, nullptr, nullptr } // end of array +}; + + +typedef std::map HandlersMap; +void GenerateHandlersMap(HandlersMap &map) +{ + map.clear(); + for (int i = 0; !ComponentHandlers[i].Name.IsEmpty(); ++i) + map[ComponentHandlers[i].Name] = ComponentHandlers[i]; +} + +// A helper struct to pass to (de)serialization handlers +struct SvgCmpReadHelper +{ + SavegameVersion Version; // general savegame version + const PreservedParams &PP; // previous game state kept for reference + RestoredData &RData; // temporary storage for loaded data, that + // will be applied after loading is done + // The map of serialization handlers, one per supported component type ID + HandlersMap Handlers; + + SvgCmpReadHelper(SavegameVersion svg_version, const PreservedParams &pp, RestoredData &r_data) + : Version(svg_version) + , PP(pp) + , RData(r_data) + { + } +}; + +// The basic information about deserialized component, used for debugging purposes +struct ComponentInfo +{ + String Name; // internal component's ID + int32_t Version; // data format version + soff_t Offset; // offset at which an opening tag is located + soff_t DataOffset; // offset at which component data begins + soff_t DataSize; // expected size of component data + + ComponentInfo() : Version(-1), Offset(0), DataOffset(0), DataSize(0) {} +}; + +HSaveError ReadComponent(PStream in, SvgCmpReadHelper &hlp, ComponentInfo &info) +{ + info = ComponentInfo(); // reset in case of early error + info.Offset = in->GetPosition(); + if (!ReadFormatTag(in, info.Name, true)) + return new SavegameError(kSvgErr_ComponentOpeningTagFormat); + info.Version = in->ReadInt32(); + info.DataSize = hlp.Version >= kSvgVersion_Cmp_64bit ? in->ReadInt64() : in->ReadInt32(); + info.DataOffset = in->GetPosition(); + + const ComponentHandler *handler = nullptr; + std::map::const_iterator it_hdr = hlp.Handlers.find(info.Name); + if (it_hdr != hlp.Handlers.end()) + handler = &it_hdr->second; + + if (!handler || !handler->Unserialize) + return new SavegameError(kSvgErr_UnsupportedComponent); + if (info.Version > handler->Version || info.Version < handler->LowestVersion) + return new SavegameError(kSvgErr_UnsupportedComponentVersion, String::FromFormat("Saved version: %d, supported: %d - %d", info.Version, handler->LowestVersion, handler->Version)); + HSaveError err = handler->Unserialize(in, info.Version, hlp.PP, hlp.RData); + if (!err) + return err; + if (in->GetPosition() - info.DataOffset != info.DataSize) + return new SavegameError(kSvgErr_ComponentSizeMismatch, String::FromFormat("Expected: %lld, actual: %lld", info.DataSize, in->GetPosition() - info.DataOffset)); + if (!AssertFormatTag(in, info.Name, false)) + return new SavegameError(kSvgErr_ComponentClosingTagFormat); + return HSaveError::None(); +} + +HSaveError ReadAll(PStream in, SavegameVersion svg_version, const PreservedParams &pp, RestoredData &r_data) +{ + // Prepare a helper struct we will be passing to the block reading proc + SvgCmpReadHelper hlp(svg_version, pp, r_data); + GenerateHandlersMap(hlp.Handlers); + + size_t idx = 0; + if (!AssertFormatTag(in, ComponentListTag, true)) + return new SavegameError(kSvgErr_ComponentListOpeningTagFormat); + do + { + // Look out for the end of the component list: + // this is the only way how this function ends with success + soff_t off = in->GetPosition(); + if (AssertFormatTag(in, ComponentListTag, false)) + return HSaveError::None(); + // If the list's end was not detected, then seek back and continue reading + in->Seek(off, kSeekBegin); + + ComponentInfo info; + HSaveError err = ReadComponent(in, hlp, info); + if (!err) + { + return new SavegameError(kSvgErr_ComponentUnserialization, + String::FromFormat("(#%d) %s, version %i, at offset %u.", + idx, info.Name.IsEmpty() ? "unknown" : info.Name.GetCStr(), info.Version, info.Offset), + err); + } + update_polled_stuff_if_runtime(); + idx++; + } + while (!in->EOS()); + return new SavegameError(kSvgErr_ComponentListClosingTagMissing); +} + +HSaveError WriteComponent(PStream out, ComponentHandler &hdlr) +{ + WriteFormatTag(out, hdlr.Name, true); + out->WriteInt32(hdlr.Version); + soff_t ref_pos = out->GetPosition(); + out->WriteInt64(0); // placeholder for the component size + HSaveError err = hdlr.Serialize(out); + soff_t end_pos = out->GetPosition(); + out->Seek(ref_pos, kSeekBegin); + out->WriteInt64(end_pos - ref_pos - sizeof(int64_t)); // size of serialized component data + out->Seek(end_pos, kSeekBegin); + if (err) + WriteFormatTag(out, hdlr.Name, false); + return err; +} + +HSaveError WriteAllCommon(PStream out) +{ + WriteFormatTag(out, ComponentListTag, true); + for (int type = 0; !ComponentHandlers[type].Name.IsEmpty(); ++type) + { + HSaveError err = WriteComponent(out, ComponentHandlers[type]); + if (!err) + { + return new SavegameError(kSvgErr_ComponentSerialization, + String::FromFormat("Component: (#%d) %s", type, ComponentHandlers[type].Name.GetCStr()), + err); + } + update_polled_stuff_if_runtime(); + } + WriteFormatTag(out, ComponentListTag, false); + return HSaveError::None(); +} + +} // namespace SavegameBlocks +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/game/savegame_components.h b/engines/ags/engine/game/savegame_components.h new file mode 100644 index 00000000000..7f44872bdd8 --- /dev/null +++ b/engines/ags/engine/game/savegame_components.h @@ -0,0 +1,56 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_GAME__SAVEGAMECOMPONENTS_H +#define __AGS_EE_GAME__SAVEGAMECOMPONENTS_H + +#include "game/savegame.h" +#include "util/stream.h" + +namespace AGS +{ + +namespace Common { struct Interaction; } + +namespace Engine +{ + +using Common::Stream; +typedef std::shared_ptr PStream; + +struct PreservedParams; +struct RestoredData; + +namespace SavegameComponents +{ + // Reads all available components from the stream + HSaveError ReadAll(PStream in, SavegameVersion svg_version, const PreservedParams &pp, RestoredData &r_data); + // Writes a full list of common components to the stream + HSaveError WriteAllCommon(PStream out); + + // Utility functions for reading and writing legacy interactions, + // or their "times run" counters separately. + void ReadTimesRun272(Interaction &intr, Stream *in); + HSaveError ReadInteraction272(Interaction &intr, Stream *in); + void WriteTimesRun272(const Interaction &intr, Stream *out); + void WriteInteraction272(const Interaction &intr, Stream *out); + + // Precreates primary camera and viewport and reads legacy camera data + void ReadLegacyCameraState(Stream *in, RestoredData &r_data); +} + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GAME__SAVEGAMECOMPONENTS_H diff --git a/engines/ags/engine/game/savegame_internal.h b/engines/ags/engine/game/savegame_internal.h new file mode 100644 index 00000000000..8a0d08b45fe --- /dev/null +++ b/engines/ags/engine/game/savegame_internal.h @@ -0,0 +1,144 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_GAME__SAVEGAMEINTERNAL_H +#define __AGS_EE_GAME__SAVEGAMEINTERNAL_H + +#include +#include +#include "ac/common_defines.h" +#include "gfx/bitmap.h" +#include "media/audio/audiodefines.h" + + +namespace AGS +{ +namespace Engine +{ + +using AGS::Common::Bitmap; + +typedef std::shared_ptr PBitmap; + +// PreservedParams keeps old values of particular gameplay +// parameters that are saved before the save restoration +// and either applied or compared to new values after +// loading save data +struct PreservedParams +{ + // Whether speech and audio packages available + int SpeechVOX; + int MusicVOX; + // Script global data sizes + int GlScDataSize; + std::vector ScMdDataSize; + + PreservedParams(); +}; + +enum GameViewCamFlags +{ + kSvgGameAutoRoomView = 0x01 +}; + +enum CameraSaveFlags +{ + kSvgCamPosLocked = 0x01 +}; + +enum ViewportSaveFlags +{ + kSvgViewportVisible = 0x01 +}; + +// RestoredData keeps certain temporary data to help with +// the restoration process +struct RestoredData +{ + int FPS; + // Unserialized bitmaps for dynamic surfaces + std::vector DynamicSurfaces; + // Scripts global data + struct ScriptData + { + std::shared_ptr Data; + size_t Len; + + ScriptData(); + }; + ScriptData GlobalScript; + std::vector ScriptModules; + // Room data (has to be be preserved until room is loaded) + PBitmap RoomBkgScene[MAX_ROOM_BGFRAMES]; + short RoomLightLevels[MAX_ROOM_REGIONS]; + int RoomTintLevels[MAX_ROOM_REGIONS]; + short RoomZoomLevels1[MAX_WALK_AREAS + 1]; + short RoomZoomLevels2[MAX_WALK_AREAS + 1]; + RoomVolumeMod RoomVolume; + // Mouse cursor parameters + int CursorID; + int CursorMode; + // General audio + struct ChannelInfo + { + int ClipID = 0; + int Pos = 0; + int Priority = 0; + int Repeat = 0; + int Vol = 0; + int VolAsPercent = 0; + int Pan = 0; + int PanAsPercent = 0; + int Speed = 0; + // since version 1 + int XSource = -1; + int YSource = -1; + int MaxDist = 0; + }; + ChannelInfo AudioChans[MAX_SOUND_CHANNELS + 1]; + // Ambient sounds + int DoAmbient[MAX_SOUND_CHANNELS]; + // Viewport and camera data, has to be preserved and applied only after + // room gets loaded, because we must clamp these to room parameters. + struct ViewportData + { + int ID = -1; + int Flags = 0; + int Left = 0; + int Top = 0; + int Width = 0; + int Height = 0; + int ZOrder = 0; + int CamID = -1; + }; + struct CameraData + { + int ID = -1; + int Flags = 0; + int Left = 0; + int Top = 0; + int Width = 0; + int Height = 0; + }; + std::vector Viewports; + std::vector Cameras; + int32_t Camera0_Flags = 0; // flags for primary camera, when data is read in legacy order + + RestoredData(); +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GAME__SAVEGAMEINTERNAL_H diff --git a/engines/ags/engine/game/viewport.cpp b/engines/ags/engine/game/viewport.cpp new file mode 100644 index 00000000000..eea1d980f16 --- /dev/null +++ b/engines/ags/engine/game/viewport.cpp @@ -0,0 +1,224 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include "ac/draw.h" +#include "ac/gamestate.h" +#include "debug/debug_log.h" +#include "game/roomstruct.h" +#include "game/viewport.h" + +using namespace AGS::Common; + +extern RoomStruct thisroom; + +void Camera::SetID(int id) +{ + _id = id; +} + +// Returns Room camera position and size inside the room (in room coordinates) +const Rect &Camera::GetRect() const +{ + return _position; +} + +// Sets explicit room camera's orthographic size +void Camera::SetSize(const Size cam_size) +{ + // TODO: currently we don't support having camera larger than room background + // (or rather - looking outside of the room background); look into this later + const Size real_room_sz = Size(data_to_game_coord(thisroom.Width), data_to_game_coord(thisroom.Height)); + Size real_size = Size::Clamp(cam_size, Size(1, 1), real_room_sz); + + _position.SetWidth(real_size.Width); + _position.SetHeight(real_size.Height); + for (auto vp = _viewportRefs.begin(); vp != _viewportRefs.end(); ++vp) + { + auto locked_vp = vp->lock(); + if (locked_vp) + locked_vp->AdjustTransformation(); + } + _hasChangedSize = true; +} + +// Puts room camera to the new location in the room +void Camera::SetAt(int x, int y) +{ + int cw = _position.GetWidth(); + int ch = _position.GetHeight(); + int room_width = data_to_game_coord(thisroom.Width); + int room_height = data_to_game_coord(thisroom.Height); + x = Math::Clamp(x, 0, room_width - cw); + y = Math::Clamp(y, 0, room_height - ch); + _position.MoveTo(Point(x, y)); + _hasChangedPosition = true; +} + +// Tells if camera is currently locked at custom position +bool Camera::IsLocked() const +{ + return _locked; +} + +// Locks room camera at its current position +void Camera::Lock() +{ + debug_script_log("Room camera locked"); + _locked = true; +} + +// Similar to SetAt, but also locks camera preventing it from following player character +void Camera::LockAt(int x, int y) +{ + debug_script_log("Room camera locked to %d,%d", x, y); + SetAt(x, y); + _locked = true; +} + +// Releases camera lock, letting it follow player character +void Camera::Release() +{ + _locked = false; + debug_script_log("Room camera released back to engine control"); +} + +// Link this camera to a new viewport; this does not unlink any linked ones +void Camera::LinkToViewport(ViewportRef viewport) +{ + auto new_locked = viewport.lock(); + if (!new_locked) + return; + for (auto vp = _viewportRefs.begin(); vp != _viewportRefs.end(); ++vp) + { + auto old_locked = vp->lock(); + if (old_locked->GetID() == new_locked->GetID()) + return; + } + _viewportRefs.push_back(viewport); +} + +// Unlinks this camera from a given viewport; does nothing if link did not exist +void Camera::UnlinkFromViewport(int id) +{ + for (auto vp = _viewportRefs.begin(); vp != _viewportRefs.end(); ++vp) + { + auto locked = vp->lock(); + if (locked && locked->GetID() == id) + { + _viewportRefs.erase(vp); + return; + } + } +} + +const std::vector &Camera::GetLinkedViewports() const +{ + return _viewportRefs; +} + +void Viewport::SetID(int id) +{ + _id = id; +} + +void Viewport::SetRect(const Rect &rc) +{ + // TODO: consider allowing size 0,0, in which case viewport is considered not visible + Size fix_size = rc.GetSize().IsNull() ? Size(1, 1) : rc.GetSize(); + _position = RectWH(rc.Left, rc.Top, fix_size.Width, fix_size.Height); + AdjustTransformation(); + _hasChangedPosition = true; + _hasChangedSize = true; +} + +void Viewport::SetSize(const Size sz) +{ + // TODO: consider allowing size 0,0, in which case viewport is considered not visible + Size fix_size = sz.IsNull() ? Size(1, 1) : sz; + _position = RectWH(_position.Left, _position.Top, fix_size.Width, fix_size.Height); + AdjustTransformation(); + _hasChangedSize = true; +} + +void Viewport::SetAt(int x, int y) +{ + _position.MoveTo(Point(x, y)); + AdjustTransformation(); + _hasChangedPosition = true; +} + +void Viewport::SetVisible(bool on) +{ + _visible = on; + _hasChangedVisible = true; +} + +void Viewport::SetZOrder(int zorder) +{ + _zorder = zorder; + _hasChangedVisible = true; +} + +void Viewport::AdjustTransformation() +{ + auto locked_cam = _camera.lock(); + if (locked_cam) + _transform.Init(locked_cam->GetRect().GetSize(), _position); +} + +PCamera Viewport::GetCamera() const +{ + return _camera.lock(); +} + +void Viewport::LinkCamera(PCamera cam) +{ + _camera = cam; + AdjustTransformation(); +} + +VpPoint Viewport::RoomToScreen(int roomx, int roomy, bool clip) const +{ + auto cam = _camera.lock(); + if (!cam) + return std::make_pair(Point(), -1); + const Rect &camr = cam->GetRect(); + Point screen_pt = _transform.Scale(Point(roomx - camr.Left, roomy - camr.Top)); + if (clip && !_position.IsInside(screen_pt)) + return std::make_pair(Point(), -1); + return std::make_pair(screen_pt, _id); +} + +VpPoint Viewport::ScreenToRoom(int scrx, int scry, bool clip, bool convert_cam_to_data) const +{ + Point screen_pt(scrx, scry); + if (clip && !_position.IsInside(screen_pt)) + return std::make_pair(Point(), -1); + auto cam = _camera.lock(); + if (!cam) + return std::make_pair(Point(), -1); + + const Rect &camr = cam->GetRect(); + Point p = _transform.UnScale(screen_pt); + if (convert_cam_to_data) + { + p.X += game_to_data_coord(camr.Left); + p.Y += game_to_data_coord(camr.Top); + } + else + { + p.X += camr.Left; + p.Y += camr.Top; + } + return std::make_pair(p, _id); +} diff --git a/engines/ags/engine/game/viewport.h b/engines/ags/engine/game/viewport.h new file mode 100644 index 00000000000..4fcc6abc738 --- /dev/null +++ b/engines/ags/engine/game/viewport.h @@ -0,0 +1,192 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Definition for the game viewports and cameras. +// +//============================================================================= +#ifndef __AGS_EE_AC__VIEWPORT_H +#define __AGS_EE_AC__VIEWPORT_H + +#include +#include +#include "util/geometry.h" +#include "util/scaling.h" + +class Camera; +class Viewport; + +typedef std::shared_ptr PCamera; +typedef std::shared_ptr PViewport; +typedef std::weak_ptr CameraRef; +typedef std::weak_ptr ViewportRef; + +// TODO: move to utility header +// From https://stackoverflow.com/questions/45507041/how-to-check-if-weak-ptr-is-empty-non-assigned +// Tests that weak_ptr is empty (was not initialized with valid reference) +template +bool is_uninitialized(std::weak_ptr const& weak) { + using wt = std::weak_ptr; + return !weak.owner_before(wt{}) && !wt{}.owner_before(weak); +} + + +// Camera defines a "looking eye" inside the room, its position and size. +// It does not render anywhere on its own, instead it is linked to a viewport +// and latter draws what that camera sees. +// One camera may be linked to multiple viewports. +// Camera does not move on its own, this is performed by separate behavior +// algorithm. But it provides "lock" property that tells if its position is +// currently owned by user script. +class Camera +{ +public: + // Gets camera ID (serves as an index) + inline int GetID() const { return _id; } + // Sets new camera ID + void SetID(int id); + // Returns Room camera position and size inside the room (in room coordinates) + const Rect &GetRect() const; + // Sets explicit room camera's orthographic size + void SetSize(const Size sz); + // Puts room camera to the new location in the room + void SetAt(int x, int y); + // Tells if camera is currently locked at custom position + bool IsLocked() const; + // Locks room camera at its current position + void Lock(); + // Similar to SetAt, but also locks camera preventing it from following player character + void LockAt(int x, int y); + // Releases camera lock, letting it follow player character + void Release(); + + // Link this camera to a new viewport; this does not unlink any linked ones + void LinkToViewport(ViewportRef viewport); + // Unlinks this camera from a given viewport; does nothing if link did not exist + void UnlinkFromViewport(int id); + // Get the array of linked viewport references + const std::vector &GetLinkedViewports() const; + + // Tell if this camera has changed recently + inline bool HasChangedPosition() const { return _hasChangedPosition; } + inline bool HasChangedSize() const { return _hasChangedSize; } + // Clears the changed flags + void ClearChangedFlags() + { + _hasChangedPosition = false; + _hasChangedSize = false; + } + +private: + int _id = -1; + // Actual position and orthographic size + Rect _position; + // Locked or following player automatically + bool _locked = false; + // Linked viewport refs, used to notify viewports of camera changes + std::vector _viewportRefs; + // Flags that tell whether this camera's position on screen has changed recently + bool _hasChangedPosition = false; + bool _hasChangedSize = false; +}; + + +// A result of coordinate conversion between screen and the room, +// tells which viewport was used to pass the "touch" through. +typedef std::pair VpPoint; + + +// Viewport class defines a rectangular area on game screen where the contents +// of a Camera are rendered. +// Viewport may have one linked camera at a time. +class Viewport +{ +public: + // Gets viewport ID (serves as an index) + inline int GetID() const { return _id; } + // Sets new viewport ID + void SetID(int id); + // Returns viewport's position on screen + inline const Rect &GetRect() const { return _position; } + // Returns viewport's room-to-screen transformation + inline const AGS::Engine::PlaneScaling &GetTransform() const { return _transform; } + // Set viewport's rectangle on screen + void SetRect(const Rect &rc); + // Sets viewport size + void SetSize(const Size sz); + // Sets viewport's position on screen + void SetAt(int x, int y); + + // Tells whether viewport content is rendered on screen + bool IsVisible() const { return _visible; } + // Changes viewport visibility + void SetVisible(bool on); + // Gets the order viewport is displayed on screen + int GetZOrder() const { return _zorder; } + // Sets the viewport's z-order on screen + void SetZOrder(int zorder); + + // Calculates room-to-viewport coordinate conversion. + void AdjustTransformation(); + // Returns linked camera + PCamera GetCamera() const; + // Links new camera to this viewport, overriding existing link; + // pass nullptr to leave viewport without a camera link + void LinkCamera(PCamera cam); + + // TODO: provide a Transform object here that does these conversions instead + // Converts room coordinates to the game screen coordinates through this viewport; + // if clipping is on, the function will fail for room coordinates outside of camera + VpPoint RoomToScreen(int roomx, int roomy, bool clip = false) const; + // Converts game screen coordinates to the room coordinates through this viewport; + // if clipping is on, the function will fail for screen coordinates outside of viewport; + // convert_cam_to_data parameter converts camera "game" coordinates to "data" units (legacy mode) + VpPoint ScreenToRoom(int scrx, int scry, bool clip = false, bool convert_cam_to_data = false) const; + + // Following functions tell if this viewport has changed recently + inline bool HasChangedPosition() const { return _hasChangedPosition; } + inline bool HasChangedSize() const { return _hasChangedSize; } + inline bool HasChangedVisible() const { return _hasChangedVisible; } + inline void SetChangedVisible() { _hasChangedVisible = true; } + // Clears the changed flags + inline void ClearChangedFlags() + { + _hasChangedPosition = false; + _hasChangedSize = false; + _hasChangedVisible = false; + } + +private: + // Parameterized implementation of screen-to-room coordinate conversion + VpPoint ScreenToRoomImpl(int scrx, int scry, bool clip, bool convert_cam_to_data); + + int _id = -1; + // Position of the viewport on screen + Rect _position; + // TODO: Camera reference (when supporting multiple cameras) + // Coordinate tranform between camera and viewport + // TODO: need to add rotate conversion to let script API support that; + // (maybe use full 3D matrix for that) + AGS::Engine::PlaneScaling _transform; + // Linked camera reference + CameraRef _camera; + bool _visible = true; + int _zorder = 0; + // Flags that tell whether this viewport's position on screen has changed recently + bool _hasChangedPosition = false; + bool _hasChangedOffscreen = false; + bool _hasChangedSize = false; + bool _hasChangedVisible = false; +}; + +#endif // __AGS_EE_AC__VIEWPORT_H diff --git a/engines/ags/engine/gfx/ali3dexception.h b/engines/ags/engine/gfx/ali3dexception.h new file mode 100644 index 00000000000..8c9580330da --- /dev/null +++ b/engines/ags/engine/gfx/ali3dexception.h @@ -0,0 +1,49 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Graphics driver exception class +// +//============================================================================= + +#ifndef __AGS_EE_GFX__ALI3DEXCEPTION_H +#define __AGS_EE_GFX__ALI3DEXCEPTION_H + +namespace AGS +{ +namespace Engine +{ + +class Ali3DException +{ +public: + Ali3DException(const char *message) + { + _message = message; + } + + const char *_message; +}; + +class Ali3DFullscreenLostException : public Ali3DException +{ +public: + Ali3DFullscreenLostException() : Ali3DException("User has switched away from application") + { + } +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__ALI3DEXCEPTION_H diff --git a/engines/ags/engine/gfx/ali3dogl.cpp b/engines/ags/engine/gfx/ali3dogl.cpp new file mode 100644 index 00000000000..cab864e668d --- /dev/null +++ b/engines/ags/engine/gfx/ali3dogl.cpp @@ -0,0 +1,2065 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS || AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_LINUX + +#include +#include "gfx/ali3dexception.h" +#include "gfx/ali3dogl.h" +#include "gfx/gfxfilter_ogl.h" +#include "gfx/gfxfilter_aaogl.h" +#include "gfx/gfx_util.h" +#include "main/main_allegro.h" +#include "platform/base/agsplatformdriver.h" +#include "util/math.h" +#include "ac/timer.h" + +#if AGS_PLATFORM_OS_ANDROID + +#define glOrtho glOrthof +#define GL_CLAMP GL_CLAMP_TO_EDGE + +// Defined in Allegro +extern "C" +{ + void android_swap_buffers(); + void android_create_screen(int width, int height, int color_depth); + void android_mouse_setup(int left, int right, int top, int bottom, float scaling_x, float scaling_y); +} + +extern "C" void android_debug_printf(char* format, ...); + +extern unsigned int android_screen_physical_width; +extern unsigned int android_screen_physical_height; +extern int android_screen_initialized; + +#define device_screen_initialized android_screen_initialized +#define device_mouse_setup android_mouse_setup +#define device_swap_buffers android_swap_buffers + +const char* fbo_extension_string = "GL_OES_framebuffer_object"; + +#define glGenFramebuffersEXT glGenFramebuffersOES +#define glDeleteFramebuffersEXT glDeleteFramebuffersOES +#define glBindFramebufferEXT glBindFramebufferOES +#define glCheckFramebufferStatusEXT glCheckFramebufferStatusOES +#define glGetFramebufferAttachmentParameterivEXT glGetFramebufferAttachmentParameterivOES +#define glGenerateMipmapEXT glGenerateMipmapOES +#define glFramebufferTexture2DEXT glFramebufferTexture2DOES +#define glFramebufferRenderbufferEXT glFramebufferRenderbufferOES +// TODO: probably should use EGL and function eglSwapInterval on Android to support setting swap interval +// For now this is a dummy function pointer which is only used to test that function is not supported +const void (*glSwapIntervalEXT)(int) = NULL; + +#define GL_FRAMEBUFFER_EXT GL_FRAMEBUFFER_OES +#define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0_OES + +#elif AGS_PLATFORM_OS_IOS + +extern "C" +{ + void ios_swap_buffers(); + void ios_select_buffer(); + void ios_create_screen(); + float get_device_scale(); + void ios_mouse_setup(int left, int right, int top, int bottom, float scaling_x, float scaling_y); +} + +#define glOrtho glOrthof +#define GL_CLAMP GL_CLAMP_TO_EDGE + +extern unsigned int ios_screen_physical_width; +extern unsigned int ios_screen_physical_height; +extern int ios_screen_initialized; + +#define device_screen_initialized ios_screen_initialized +#define device_mouse_setup ios_mouse_setup +#define device_swap_buffers ios_swap_buffers + +const char* fbo_extension_string = "GL_OES_framebuffer_object"; + +#define glGenFramebuffersEXT glGenFramebuffersOES +#define glDeleteFramebuffersEXT glDeleteFramebuffersOES +#define glBindFramebufferEXT glBindFramebufferOES +#define glCheckFramebufferStatusEXT glCheckFramebufferStatusOES +#define glGetFramebufferAttachmentParameterivEXT glGetFramebufferAttachmentParameterivOES +#define glGenerateMipmapEXT glGenerateMipmapOES +#define glFramebufferTexture2DEXT glFramebufferTexture2DOES +#define glFramebufferRenderbufferEXT glFramebufferRenderbufferOES +// TODO: find out how to support swap interval setting on iOS +// For now this is a dummy function pointer which is only used to test that function is not supported +const void (*glSwapIntervalEXT)(int) = NULL; + +#define GL_FRAMEBUFFER_EXT GL_FRAMEBUFFER_OES +#define GL_COLOR_ATTACHMENT0_EXT GL_COLOR_ATTACHMENT0_OES + +#endif + +// Necessary to update textures from 8-bit bitmaps +extern RGB palette[256]; + + +namespace AGS +{ +namespace Engine +{ +namespace OGL +{ + +using namespace AGS::Common; + +void ogl_dummy_vsync() { } + +#define GFX_OPENGL AL_ID('O','G','L',' ') + +GFX_DRIVER gfx_opengl = +{ + GFX_OPENGL, + empty_string, + empty_string, + "OpenGL", + nullptr, // init + nullptr, // exit + nullptr, // AL_METHOD(int, scroll, (int x, int y)); + ogl_dummy_vsync, // vsync + nullptr, // setpalette + nullptr, // AL_METHOD(int, request_scroll, (int x, int y)); + nullptr, // AL_METHOD(int, poll_scroll, (void)); + nullptr, // AL_METHOD(void, enable_triple_buffer, (void)); + nullptr, //create_video_bitmap + nullptr, //destroy_video_bitmap + nullptr, //show_video_bitmap + nullptr, + nullptr, //gfx_directx_create_system_bitmap, + nullptr, //gfx_directx_destroy_system_bitmap, + nullptr, //gfx_directx_set_mouse_sprite, + nullptr, //gfx_directx_show_mouse, + nullptr, //gfx_directx_hide_mouse, + nullptr, //gfx_directx_move_mouse, + nullptr, // AL_METHOD(void, drawing_mode, (void)); + nullptr, // AL_METHOD(void, save_video_state, (void*)); + nullptr, // AL_METHOD(void, restore_video_state, (void*)); + nullptr, // AL_METHOD(void, set_blender_mode, (int mode, int r, int g, int b, int a)); + nullptr, // AL_METHOD(int, fetch_mode_list, (void)); + 0, 0, // int w, h; + FALSE, // int linear; + 0, // long bank_size; + 0, // long bank_gran; + 0, // long vid_mem; + 0, // long vid_phys_base; + TRUE // int windowed; +}; + +void OGLBitmap::Dispose() +{ + if (_tiles != nullptr) + { + for (int i = 0; i < _numTiles; i++) + glDeleteTextures(1, &(_tiles[i].texture)); + + free(_tiles); + _tiles = nullptr; + _numTiles = 0; + } + if (_vertex != nullptr) + { + free(_vertex); + _vertex = nullptr; + } +} + + +OGLGraphicsDriver::ShaderProgram::ShaderProgram() : Program(0), SamplerVar(0), ColorVar(0), AuxVar(0) {} + + +OGLGraphicsDriver::OGLGraphicsDriver() +{ +#if AGS_PLATFORM_OS_WINDOWS + _hDC = NULL; + _hRC = NULL; + _hWnd = NULL; + _hInstance = NULL; + device_screen_physical_width = 0; + device_screen_physical_height = 0; +#elif AGS_PLATFORM_OS_LINUX + device_screen_physical_width = 0; + device_screen_physical_height = 0; + _glxContext = nullptr; + _have_window = false; +#elif AGS_PLATFORM_OS_ANDROID + device_screen_physical_width = android_screen_physical_width; + device_screen_physical_height = android_screen_physical_height; +#elif AGS_PLATFORM_OS_IOS + device_screen_physical_width = ios_screen_physical_width; + device_screen_physical_height = ios_screen_physical_height; +#endif + + _firstTimeInit = false; + _backbuffer = 0; + _fbo = 0; + _legacyPixelShader = false; + _can_render_to_texture = false; + _do_render_to_texture = false; + _super_sampling = 1; + SetupDefaultVertices(); + + // Shifts comply to GL_RGBA + _vmem_r_shift_32 = 0; + _vmem_g_shift_32 = 8; + _vmem_b_shift_32 = 16; + _vmem_a_shift_32 = 24; +} + + +void OGLGraphicsDriver::SetupDefaultVertices() +{ + std::fill(_backbuffer_vertices, _backbuffer_vertices + sizeof(_backbuffer_vertices) / sizeof(GLfloat), 0.0f); + std::fill(_backbuffer_texture_coordinates, _backbuffer_texture_coordinates + sizeof(_backbuffer_texture_coordinates) / sizeof(GLfloat), 0.0f); + + defaultVertices[0].position.x = 0.0f; + defaultVertices[0].position.y = 0.0f; + defaultVertices[0].tu=0.0; + defaultVertices[0].tv=0.0; + + defaultVertices[1].position.x = 1.0f; + defaultVertices[1].position.y = 0.0f; + defaultVertices[1].tu=1.0; + defaultVertices[1].tv=0.0; + + defaultVertices[2].position.x = 0.0f; + defaultVertices[2].position.y = -1.0f; + defaultVertices[2].tu=0.0; + defaultVertices[2].tv=1.0; + + defaultVertices[3].position.x = 1.0f; + defaultVertices[3].position.y = -1.0f; + defaultVertices[3].tu=1.0; + defaultVertices[3].tv=1.0; +} + +#if AGS_PLATFORM_OS_WINDOWS || AGS_PLATFORM_OS_LINUX + +void OGLGraphicsDriver::CreateDesktopScreen(int width, int height, int depth) +{ + device_screen_physical_width = width; + device_screen_physical_height = height; +} + +#elif AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS + +void OGLGraphicsDriver::UpdateDeviceScreen() +{ +#if AGS_PLATFORM_OS_ANDROID + device_screen_physical_width = android_screen_physical_width; + device_screen_physical_height = android_screen_physical_height; +#elif AGS_PLATFORM_OS_IOS + device_screen_physical_width = ios_screen_physical_width; + device_screen_physical_height = ios_screen_physical_height; +#endif + + Debug::Printf("OGL: notified of device screen updated to %d x %d, resizing viewport", device_screen_physical_width, device_screen_physical_height); + _mode.Width = device_screen_physical_width; + _mode.Height = device_screen_physical_height; + InitGlParams(_mode); + if (_initSurfaceUpdateCallback) + _initSurfaceUpdateCallback(); +} + +#endif + +void OGLGraphicsDriver::Vsync() +{ + // do nothing on OpenGL +} + +void OGLGraphicsDriver::RenderSpritesAtScreenResolution(bool enabled, int supersampling) +{ + if (_can_render_to_texture) + { + _do_render_to_texture = !enabled; + _super_sampling = supersampling; + TestSupersampling(); + } + + if (_do_render_to_texture) + glDisable(GL_SCISSOR_TEST); +} + +bool OGLGraphicsDriver::IsModeSupported(const DisplayMode &mode) +{ + if (mode.Width <= 0 || mode.Height <= 0 || mode.ColorDepth <= 0) + { + set_allegro_error("Invalid resolution parameters: %d x %d x %d", mode.Width, mode.Height, mode.ColorDepth); + return false; + } + return true; +} + +bool OGLGraphicsDriver::SupportsGammaControl() +{ + return false; +} + +void OGLGraphicsDriver::SetGamma(int newGamma) +{ +} + +void OGLGraphicsDriver::SetGraphicsFilter(POGLFilter filter) +{ + _filter = filter; + OnSetFilter(); +} + +void OGLGraphicsDriver::SetTintMethod(TintMethod method) +{ + _legacyPixelShader = (method == TintReColourise); +} + +void OGLGraphicsDriver::FirstTimeInit() +{ + String ogl_v_str; +#ifdef GLAPI + ogl_v_str.Format("%d.%d", GLVersion.major, GLVersion.minor); +#else + ogl_v_str = (const char*)glGetString(GL_VERSION); +#endif + Debug::Printf(kDbgMsg_Info, "Running OpenGL: %s", ogl_v_str.GetCStr()); + + // Initialize default sprite batch, it will be used when no other batch was activated + OGLGraphicsDriver::InitSpriteBatch(0, _spriteBatchDesc[0]); + + TestRenderToTexture(); + CreateShaders(); + _firstTimeInit = true; +} + +#if AGS_PLATFORM_OS_LINUX +Atom get_x_atom (const char *atom_name) +{ + Atom atom = XInternAtom(_xwin.display, atom_name, False); + if (atom == None) + { + Debug::Printf(kDbgMsg_Error, "ERROR: X11 atom \"%s\" not found.\n", atom_name); + } + return atom; +} +#endif + +bool OGLGraphicsDriver::InitGlScreen(const DisplayMode &mode) +{ +#if AGS_PLATFORM_OS_ANDROID + android_create_screen(mode.Width, mode.Height, mode.ColorDepth); +#elif AGS_PLATFORM_OS_IOS + ios_create_screen(); + ios_select_buffer(); +#elif AGS_PLATFORM_OS_WINDOWS + if (mode.Windowed) + { + platform->AdjustWindowStyleForWindowed(); + } + else + { + if (platform->EnterFullscreenMode(mode)) + platform->AdjustWindowStyleForFullscreen(); + } + + // NOTE: adjust_window may leave task bar visible, so we do not use it for fullscreen mode + if (mode.Windowed && adjust_window(mode.Width, mode.Height) != 0) + { + set_allegro_error("Window size not supported"); + return false; + } + + _hWnd = win_get_window(); + if (!(_hDC = GetDC(_hWnd))) + return false; + + // First check if we need to recreate GL context, this will only be + // required if different color depth is requested. + if (_hRC) + { + GLuint pixel_fmt = GetPixelFormat(_hDC); + PIXELFORMATDESCRIPTOR pfd; + DescribePixelFormat(_hDC, pixel_fmt, sizeof(PIXELFORMATDESCRIPTOR), &pfd); + if (pfd.cColorBits != mode.ColorDepth) + { + DeleteGlContext(); + } + } + + if (!_hRC) + { + if (!CreateGlContext(mode)) + return false; + } + + if (!gladLoadWGL(_hDC)) { + Debug::Printf(kDbgMsg_Error, "Failed to load WGL."); + return false; + } + + if (!gladLoadGL()) { + Debug::Printf(kDbgMsg_Error, "Failed to load GL."); + return false; + } + + CreateDesktopScreen(mode.Width, mode.Height, mode.ColorDepth); + win_grab_input(); +#elif AGS_PLATFORM_OS_LINUX + if (!_have_window) + { + // Use Allegro to create our window. We don't care what size Allegro uses + // here, we will set that ourselves below by manipulating members of + // Allegro's_xwin structure. We need to use the Allegro routine here (rather + // than create our own X window) to remain compatible with Allegro's mouse & + // keyboard handling. + // + // Note that although _xwin contains a special "fullscreen" Window member + // (_xwin.fs_window), we do not use it for going fullscreen. Instead we ask + // the window manager to take the "managed" Window (_xwin.wm_window) + // fullscreen for us. All drawing goes to the "real" Window (_xwin.window). + if (set_gfx_mode(GFX_AUTODETECT_WINDOWED, 0, 0, 0, 0) != 0) + return false; + _have_window = true; + } + + if (!gladLoadGLX(_xwin.display, DefaultScreen(_xwin.display))) { + Debug::Printf(kDbgMsg_Error, "Failed to load GLX."); + return false; + } + + if (!_glxContext && !CreateGlContext(mode)) + return false; + + if(!gladLoadGL()) { + Debug::Printf(kDbgMsg_Error, "Failed to load GL."); + return false; + } + + { + // Set the size of our "managed" window. + XSizeHints *hints = XAllocSizeHints(); + + if (hints) + { + if (mode.Windowed) + { + // Set a fixed-size window. This is copied from Allegro 4's + // _xwin_private_create_screen(). + hints->flags = PMinSize | PMaxSize | PBaseSize; + hints->min_width = hints->max_width = hints->base_width = mode.Width; + hints->min_height = hints->max_height = hints->base_height = mode.Height; + } + else + { + // Clear any previously set demand for a fixed-size window, otherwise + // the window manager will ignore our request to go full-screen. + hints->flags = 0; + } + + XSetWMNormalHints(_xwin.display, _xwin.wm_window, hints); + } + + XFree(hints); + } + + // Set the window we are actually drawing into to the desired size. + XResizeWindow(_xwin.display, _xwin.window, mode.Width, mode.Height); + + // Make Allegro aware of the new window size, otherwise the mouse cursor + // movement may be erratic. + _xwin.window_width = mode.Width; + _xwin.window_height = mode.Height; + + { + // Ask the window manager to add (or remove) the "fullscreen" property on + // our top-level window. + const Atom wm_state = get_x_atom("_NET_WM_STATE"); + const Atom fullscreen = get_x_atom("_NET_WM_STATE_FULLSCREEN"); + const long remove_property = 0; + const long add_property = 1; + + XEvent xev; + memset(&xev, 0, sizeof(xev)); + xev.type = ClientMessage; + xev.xclient.window = _xwin.wm_window; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = mode.Windowed ? remove_property : add_property; + xev.xclient.data.l[1] = fullscreen; + xev.xclient.data.l[2] = 0; + xev.xclient.data.l[3] = 1; // Message source is a regular application. + Status status = XSendEvent(_xwin.display, DefaultRootWindow(_xwin.display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev); + if (status == 0) + { + Debug::Printf(kDbgMsg_Error, "ERROR: Failed to encode window state message.\n"); + } + } + + CreateDesktopScreen(mode.Width, mode.Height, mode.ColorDepth); +#endif + + gfx_driver = &gfx_opengl; + return true; +} + +void OGLGraphicsDriver::InitGlParams(const DisplayMode &mode) +{ + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glShadeModel(GL_FLAT); + + glEnable(GL_TEXTURE_2D); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, device_screen_physical_width, device_screen_physical_height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, device_screen_physical_width, 0, device_screen_physical_height, 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_NORMAL_ARRAY); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + auto interval = mode.Vsync ? 1 : 0; + bool vsyncEnabled = false; + +#if AGS_PLATFORM_OS_WINDOWS + if (GLAD_WGL_EXT_swap_control) { + vsyncEnabled = wglSwapIntervalEXT(interval) != FALSE; + } +#endif + +#if AGS_PLATFORM_OS_LINUX + if (GLAD_GLX_EXT_swap_control) { + glXSwapIntervalEXT(_xwin.display, _xwin.window, interval); + // glx requires hooking into XSetErrorHandler to test for BadWindow or BadValue + vsyncEnabled = true; + } else if (GLAD_GLX_MESA_swap_control) { + vsyncEnabled = glXSwapIntervalMESA(interval) == 0; + } else if (GLAD_GLX_SGI_swap_control) { + vsyncEnabled = glXSwapIntervalSGI(interval) == 0; + } +#endif + + // TODO: find out how to implement SwapInterval on other platforms, and how to check if it's supported + + if (mode.Vsync && !vsyncEnabled) { + Debug::Printf(kDbgMsg_Warn, "WARNING: Vertical sync could not be enabled. Setting will be kept at driver default."); + } + +#if AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS + // Setup library mouse to have 1:1 coordinate transformation. + // NOTE: cannot move this call to general mouse handling mode. Unfortunately, much of the setup and rendering + // is duplicated in the Android/iOS ports' Allegro library patches, and is run when the Software renderer + // is selected in AGS. This ugly situation causes trouble... + float device_scale = 1.0f; + + #if AGS_PLATFORM_OS_IOS + device_scale = get_device_scale(); + #endif + + device_mouse_setup(0, device_screen_physical_width - 1, 0, device_screen_physical_height - 1, device_scale, device_scale); +#endif +} + +bool OGLGraphicsDriver::CreateGlContext(const DisplayMode &mode) +{ +#if AGS_PLATFORM_OS_WINDOWS + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), + 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, + (BYTE)mode.ColorDepth, + 0, 0, 0, 0, 0, 0, + 0, + 0, + 0, + 0, 0, 0, 0, + 0, + 0, + 0, + PFD_MAIN_PLANE, + 0, + 0, 0, 0 + }; + + _oldPixelFormat = GetPixelFormat(_hDC); + DescribePixelFormat(_hDC, _oldPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &_oldPixelFormatDesc); + + GLuint pixel_fmt; + if (!(pixel_fmt = ChoosePixelFormat(_hDC, &pfd))) + return false; + + if (!SetPixelFormat(_hDC, pixel_fmt, &pfd)) + return false; + + if (!(_hRC = wglCreateContext(_hDC))) + return false; + + if(!wglMakeCurrent(_hDC, _hRC)) + return false; +#endif // AGS_PLATFORM_OS_WINDOWS +#if AGS_PLATFORM_OS_LINUX + int attrib[] = { GLX_RGBA, GLX_DOUBLEBUFFER, None }; + XVisualInfo *vi = glXChooseVisual(_xwin.display, DefaultScreen(_xwin.display), attrib); + if (!vi) + { + Debug::Printf(kDbgMsg_Error, "ERROR: glXChooseVisual() failed.\n"); + return false; + } + + if (!(_glxContext = glXCreateContext(_xwin.display, vi, None, True))) + { + Debug::Printf(kDbgMsg_Error, "ERROR: glXCreateContext() failed.\n"); + return false; + } + + if (!glXMakeCurrent(_xwin.display, _xwin.window, _glxContext)) + { + Debug::Printf(kDbgMsg_Error, "ERROR: glXMakeCurrent() failed.\n"); + return false; + } +#endif + return true; +} + +void OGLGraphicsDriver::DeleteGlContext() +{ +#if AGS_PLATFORM_OS_WINDOWS + if (_hRC) + { + wglMakeCurrent(NULL, NULL); + wglDeleteContext(_hRC); + _hRC = NULL; + } + + if (_oldPixelFormat > 0) + SetPixelFormat(_hDC, _oldPixelFormat, &_oldPixelFormatDesc); +#elif AGS_PLATFORM_OS_LINUX + if (_glxContext) + { + glXMakeCurrent(_xwin.display, None, nullptr); + glXDestroyContext(_xwin.display, _glxContext); + _glxContext = nullptr; + } +#endif +} + +inline bool CanDoFrameBuffer() +{ +#ifdef GLAPI + return GLAD_GL_EXT_framebuffer_object != 0; +#else +#if AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS + const char* fbo_extension_string = "GL_OES_framebuffer_object"; +#else + const char* fbo_extension_string = "GL_EXT_framebuffer_object"; +#endif + const char* extensions = (const char*)glGetString(GL_EXTENSIONS); + return extensions && strstr(extensions, fbo_extension_string) != NULL; +#endif +} + +void OGLGraphicsDriver::TestRenderToTexture() +{ + if (CanDoFrameBuffer()) { + _can_render_to_texture = true; + TestSupersampling(); + } else { + _can_render_to_texture = false; + Debug::Printf(kDbgMsg_Warn, "WARNING: OpenGL extension 'GL_EXT_framebuffer_object' not supported, rendering to texture mode will be disabled."); + } + + if (!_can_render_to_texture) + _do_render_to_texture = false; +} + +void OGLGraphicsDriver::TestSupersampling() +{ + if (!_can_render_to_texture) + return; + // Disable super-sampling if it would cause a too large texture size + if (_super_sampling > 1) + { + int max = 1024; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max); + if ((max < _srcRect.GetWidth() * _super_sampling) || (max < _srcRect.GetHeight() * _super_sampling)) + _super_sampling = 1; + } +} + +void OGLGraphicsDriver::CreateShaders() +{ + if (!GLAD_GL_VERSION_2_0) { + Debug::Printf(kDbgMsg_Error, "ERROR: Shaders require a minimum of OpenGL 2.0 support."); + return; + } + CreateTintShader(); + CreateLightShader(); +} + +void OGLGraphicsDriver::CreateTintShader() +{ + const char *fragment_shader_src = + // NOTE: this shader emulates "historical" AGS software tinting; it is not + // necessarily "proper" tinting in modern terms. + // The RGB-HSV-RGB conversion found in the Internet (copyright unknown); + // Color processing is replicated from Direct3D shader by Chris Jones + // (Engine/resource/tintshaderLegacy.fx). + // Uniforms: + // textID - texture index (usually 0), + // tintHSV - tint color in HSV, + // tintAmnTrsLum - tint parameters: amount, translucence (alpha), luminance. + "\ + #version 120\n\ + uniform sampler2D textID;\n\ + uniform vec3 tintHSV;\n\ + uniform vec3 tintAmnTrsLum;\n\ + \ + vec3 rgb2hsv(vec3 c)\n\ + {\n\ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n\ + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n\ + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n\ + \ + float d = q.x - min(q.w, q.y);\n\ + const float e = 1.0e-10;\n\ + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n\ + }\n\ + \ + vec3 hsv2rgb(vec3 c)\n\ + {\n\ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n\ + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n\ + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n\ + }\n\ + \ + float getValue(vec3 color)\n\ + {\n\ + float colorMax = max (color[0], color[1]);\n\ + colorMax = max (colorMax, color[2]);\n\ + return colorMax;\n\ + }\n\ + \ + void main()\n\ + {\n\ + vec4 src_col = texture2D(textID, gl_TexCoord[0].xy);\n\ + float amount = tintAmnTrsLum[0];\n\ + float lum = getValue(src_col.xyz);\n\ + lum = max(lum - (1.0 - tintAmnTrsLum[2]), 0.0);\n\ + vec3 new_col = (hsv2rgb(vec3(tintHSV[0], tintHSV[1], lum)) * amount + src_col.xyz * (1.0 - amount));\n\ + gl_FragColor = vec4(new_col, src_col.w * tintAmnTrsLum[1]);\n\ + }\n\ + "; + CreateShaderProgram(_tintShader, "Tinting", fragment_shader_src, "textID", "tintHSV", "tintAmnTrsLum"); +} + +void OGLGraphicsDriver::CreateLightShader() +{ + const char *fragment_shader_src = + // NOTE: due to how the lighting works in AGS, this is combined MODULATE / ADD shader. + // if the light is < 0, then MODULATE operation is used, otherwise ADD is used. + // NOTE: it's been said that using branching in shaders produces inefficient code. + // If that will ever become a real problem, we can easily split this shader in two. + // Uniforms: + // textID - texture index (usually 0), + // light - light level, + // alpha - color alpha value. + "\ + #version 120\n\ + uniform sampler2D textID;\n\ + uniform float light;\n\ + uniform float alpha;\n\ + \ + void main()\n\ + {\n\ + vec4 src_col = texture2D(textID, gl_TexCoord[0].xy);\n\ + if (light >= 0.0)\n\ + gl_FragColor = vec4(src_col.xyz + vec3(light, light, light), src_col.w * alpha);\n\ + else\n\ + gl_FragColor = vec4(src_col.xyz * abs(light), src_col.w * alpha);\n\ + }\n\ + "; + CreateShaderProgram(_lightShader, "Lighting", fragment_shader_src, "textID", "light", "alpha"); +} + +void OGLGraphicsDriver::CreateShaderProgram(ShaderProgram &prg, const char *name, const char *fragment_shader_src, + const char *sampler_var, const char *color_var, const char *aux_var) +{ + GLint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_shader, 1, &fragment_shader_src, nullptr); + glCompileShader(fragment_shader); + GLint result; + glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &result); + if (result == GL_FALSE) + { + OutputShaderError(fragment_shader, String::FromFormat("%s program's fragment shader", name), true); + glDeleteShader(fragment_shader); + return; + } + + GLuint program = glCreateProgram(); + glAttachShader(program, fragment_shader); + glLinkProgram(program); + glGetProgramiv(program, GL_LINK_STATUS, &result); + if(result == GL_FALSE) + { + OutputShaderError(program, String::FromFormat("%s program", name), false); + glDeleteProgram(program); + glDeleteShader(fragment_shader); + return; + } + glDetachShader(program, fragment_shader); + glDeleteShader(fragment_shader); + + prg.Program = program; + prg.SamplerVar = glGetUniformLocation(program, sampler_var); + prg.ColorVar = glGetUniformLocation(program, color_var); + prg.AuxVar = glGetUniformLocation(program, aux_var); + Debug::Printf("OGL: %s shader program created successfully", name); +} + +void OGLGraphicsDriver::DeleteShaderProgram(ShaderProgram &prg) +{ + if (prg.Program) + glDeleteProgram(prg.Program); + prg.Program = 0; +} + +void OGLGraphicsDriver::OutputShaderError(GLuint obj_id, const String &obj_name, bool is_shader) +{ + GLint log_len; + if (is_shader) + glGetShaderiv(obj_id, GL_INFO_LOG_LENGTH, &log_len); + else + glGetProgramiv(obj_id, GL_INFO_LOG_LENGTH, &log_len); + std::vector errorLog(log_len); + if (log_len > 0) + { + if (is_shader) + glGetShaderInfoLog(obj_id, log_len, &log_len, &errorLog[0]); + else + glGetProgramInfoLog(obj_id, log_len, &log_len, &errorLog[0]); + } + + Debug::Printf(kDbgMsg_Error, "ERROR: OpenGL: %s %s:", obj_name.GetCStr(), is_shader ? "failed to compile" : "failed to link"); + if (errorLog.size() > 0) + { + Debug::Printf(kDbgMsg_Error, "----------------------------------------"); + Debug::Printf(kDbgMsg_Error, "%s", &errorLog[0]); + Debug::Printf(kDbgMsg_Error, "----------------------------------------"); + } + else + { + Debug::Printf(kDbgMsg_Error, "Shader info log was empty."); + } +} + +void OGLGraphicsDriver::SetupBackbufferTexture() +{ + // NOTE: ability to render to texture depends on OGL context, which is + // created in SetDisplayMode, therefore creation of textures require + // both native size set and context capabilities test passed. + if (!IsNativeSizeValid() || !_can_render_to_texture) + return; + + DeleteBackbufferTexture(); + + // _backbuffer_texture_coordinates defines translation from wanted texture size to actual supported texture size + _backRenderSize = _srcRect.GetSize() * _super_sampling; + _backTextureSize = _backRenderSize; + AdjustSizeToNearestSupportedByCard(&_backTextureSize.Width, &_backTextureSize.Height); + const float back_ratio_w = (float)_backRenderSize.Width / (float)_backTextureSize.Width; + const float back_ratio_h = (float)_backRenderSize.Height / (float)_backTextureSize.Height; + std::fill(_backbuffer_texture_coordinates, _backbuffer_texture_coordinates + sizeof(_backbuffer_texture_coordinates) / sizeof(GLfloat), 0.0f); + _backbuffer_texture_coordinates[2] = _backbuffer_texture_coordinates[6] = back_ratio_w; + _backbuffer_texture_coordinates[5] = _backbuffer_texture_coordinates[7] = back_ratio_h; + + glGenTextures(1, &_backbuffer); + glBindTexture(GL_TEXTURE_2D, _backbuffer); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _backTextureSize.Width, _backTextureSize.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffersEXT(1, &_fbo); + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo); + glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, _backbuffer, 0); + + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + + // Assign vertices of the backbuffer texture position in the scene + _backbuffer_vertices[0] = _backbuffer_vertices[4] = 0; + _backbuffer_vertices[2] = _backbuffer_vertices[6] = _srcRect.GetWidth(); + _backbuffer_vertices[5] = _backbuffer_vertices[7] = _srcRect.GetHeight(); + _backbuffer_vertices[1] = _backbuffer_vertices[3] = 0; +} + +void OGLGraphicsDriver::DeleteBackbufferTexture() +{ + if (_backbuffer) + glDeleteTextures(1, &_backbuffer); + if (_fbo) + glDeleteFramebuffersEXT(1, &_fbo); + _backbuffer = 0; + _fbo = 0; +} + +void OGLGraphicsDriver::SetupViewport() +{ + if (!IsModeSet() || !IsRenderFrameValid()) + return; + + // Setup viewport rect and scissor + _viewportRect = ConvertTopDownRect(_dstRect, device_screen_physical_height); + glScissor(_viewportRect.Left, _viewportRect.Top, _viewportRect.GetWidth(), _viewportRect.GetHeight()); +} + +Rect OGLGraphicsDriver::ConvertTopDownRect(const Rect &rect, int surface_height) +{ + return RectWH(rect.Left, surface_height - 1 - rect.Bottom, rect.GetWidth(), rect.GetHeight()); +} + +bool OGLGraphicsDriver::SetDisplayMode(const DisplayMode &mode, volatile int *loopTimer) +{ + ReleaseDisplayMode(); + + if (mode.ColorDepth < 15) + { + set_allegro_error("OpenGL driver does not support 256-color display mode"); + return false; + } + + try + { + if (!InitGlScreen(mode)) + return false; + if (!_firstTimeInit) + FirstTimeInit(); + InitGlParams(mode); + } + catch (Ali3DException exception) + { + if (exception._message != get_allegro_error()) + set_allegro_error(exception._message); + return false; + } + + OnInit(loopTimer); + + // On certain platforms OpenGL renderer ignores requested screen sizes + // and uses values imposed by the operating system (device). + DisplayMode final_mode = mode; + final_mode.Width = device_screen_physical_width; + final_mode.Height = device_screen_physical_height; + OnModeSet(final_mode); + + // If we already have a native size set, then update virtual screen and setup backbuffer texture immediately + CreateVirtualScreen(); + SetupBackbufferTexture(); + // If we already have a render frame configured, then setup viewport and backbuffer mappings immediately + SetupViewport(); + return true; +} + +void OGLGraphicsDriver::CreateVirtualScreen() +{ + if (!IsModeSet() || !IsNativeSizeValid()) + return; + // create initial stage screen for plugin raw drawing + _stageVirtualScreen = CreateStageScreen(0, _srcRect.GetSize()); + // we must set Allegro's screen pointer to **something** + screen = (BITMAP*)_stageVirtualScreen->GetAllegroBitmap(); +} + +bool OGLGraphicsDriver::SetNativeSize(const Size &src_size) +{ + OnSetNativeSize(src_size); + SetupBackbufferTexture(); + // If we already have a gfx mode set, then update virtual screen immediately + CreateVirtualScreen(); + TestSupersampling(); + return !_srcRect.IsEmpty(); +} + +bool OGLGraphicsDriver::SetRenderFrame(const Rect &dst_rect) +{ + if (!IsNativeSizeValid()) + return false; + OnSetRenderFrame(dst_rect); + // Also make sure viewport and backbuffer mappings are updated using new native & destination rectangles + SetupViewport(); + return !_dstRect.IsEmpty(); +} + +int OGLGraphicsDriver::GetDisplayDepthForNativeDepth(int native_color_depth) const +{ + // TODO: check for device caps to know which depth is supported? + return 32; +} + +IGfxModeList *OGLGraphicsDriver::GetSupportedModeList(int color_depth) +{ + std::vector modes; + platform->GetSystemDisplayModes(modes); + return new OGLDisplayModeList(modes); +} + +PGfxFilter OGLGraphicsDriver::GetGraphicsFilter() const +{ + return _filter; +} + +void OGLGraphicsDriver::ReleaseDisplayMode() +{ + if (!IsModeSet()) + return; + + OnModeReleased(); + ClearDrawLists(); + ClearDrawBackups(); + DeleteBackbufferTexture(); + DestroyFxPool(); + DestroyAllStageScreens(); + + gfx_driver = nullptr; + + platform->ExitFullscreenMode(); +} + +void OGLGraphicsDriver::UnInit() +{ + OnUnInit(); + ReleaseDisplayMode(); + + DeleteGlContext(); +#if AGS_PLATFORM_OS_WINDOWS + _hWnd = NULL; + _hDC = NULL; +#endif + + DeleteShaderProgram(_tintShader); + DeleteShaderProgram(_lightShader); +} + +OGLGraphicsDriver::~OGLGraphicsDriver() +{ + OGLGraphicsDriver::UnInit(); +} + +void OGLGraphicsDriver::ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse) +{ + // NOTE: this function is practically useless at the moment, because OGL redraws whole game frame each time +} + +bool OGLGraphicsDriver::GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt) +{ + (void)at_native_res; // TODO: support this at some point + + // TODO: following implementation currently only reads GL pixels in 32-bit RGBA. + // this **should** work regardless of actual display mode because OpenGL is + // responsible to convert and fill pixel buffer correctly. + // If you like to support writing directly into 16-bit bitmap, please take + // care of ammending the pixel reading code below. + const int read_in_colordepth = 32; + Size need_size = _do_render_to_texture ? _backRenderSize : _dstRect.GetSize(); + if (destination->GetColorDepth() != read_in_colordepth || destination->GetSize() != need_size) + { + if (want_fmt) + *want_fmt = GraphicResolution(need_size.Width, need_size.Height, read_in_colordepth); + return false; + } + + Rect retr_rect; + if (_do_render_to_texture) + { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo); + retr_rect = RectWH(0, 0, _backRenderSize.Width, _backRenderSize.Height); + } + else + { +#if AGS_PLATFORM_OS_IOS + ios_select_buffer(); +#elif AGS_PLATFORM_OS_WINDOWS || AGS_PLATFORM_OS_LINUX + glReadBuffer(GL_FRONT); +#endif + retr_rect = _dstRect; + } + + int bpp = read_in_colordepth / 8; + int bufferSize = retr_rect.GetWidth() * retr_rect.GetHeight() * bpp; + + unsigned char* buffer = new unsigned char[bufferSize]; + if (buffer) + { + glReadPixels(retr_rect.Left, retr_rect.Top, retr_rect.GetWidth(), retr_rect.GetHeight(), GL_RGBA, GL_UNSIGNED_BYTE, buffer); + + unsigned char* sourcePtr = buffer; + for (int y = destination->GetHeight() - 1; y >= 0; y--) + { + unsigned int * destPtr = reinterpret_cast(&destination->GetScanLineForWriting(y)[0]); + for (int dx = 0, sx = 0; dx < destination->GetWidth(); ++dx, sx = dx * bpp) + { + destPtr[dx] = makeacol32(sourcePtr[sx + 0], sourcePtr[sx + 1], sourcePtr[sx + 2], sourcePtr[sx + 3]); + } + sourcePtr += retr_rect.GetWidth() * bpp; + } + + if (_pollingCallback) + _pollingCallback(); + + delete [] buffer; + } + return true; +} + +void OGLGraphicsDriver::RenderToBackBuffer() +{ + throw Ali3DException("OGL driver does not have a back buffer"); +} + +void OGLGraphicsDriver::Render() +{ + Render(0, 0, kFlip_None); +} + +void OGLGraphicsDriver::Render(int /*xoff*/, int /*yoff*/, GlobalFlipType /*flip*/) +{ + _render(true); +} + +void OGLGraphicsDriver::_reDrawLastFrame() +{ + RestoreDrawLists(); +} + +void OGLGraphicsDriver::_renderSprite(const OGLDrawListEntry *drawListEntry, const GLMATRIX &matGlobal) +{ + OGLBitmap *bmpToDraw = drawListEntry->bitmap; + + if (bmpToDraw->_transparency >= 255) + return; + + const bool do_tint = bmpToDraw->_tintSaturation > 0 && _tintShader.Program > 0; + const bool do_light = bmpToDraw->_tintSaturation == 0 && bmpToDraw->_lightLevel > 0 && _lightShader.Program > 0; + if (do_tint) + { + // Use tinting shader + glUseProgram(_tintShader.Program); + float rgb[3]; + float sat_trs_lum[3]; // saturation / transparency / luminance + if (_legacyPixelShader) + { + rgb_to_hsv(bmpToDraw->_red, bmpToDraw->_green, bmpToDraw->_blue, &rgb[0], &rgb[1], &rgb[2]); + rgb[0] /= 360.0; // In HSV, Hue is 0-360 + } + else + { + rgb[0] = (float)bmpToDraw->_red / 255.0; + rgb[1] = (float)bmpToDraw->_green / 255.0; + rgb[2] = (float)bmpToDraw->_blue / 255.0; + } + + sat_trs_lum[0] = (float)bmpToDraw->_tintSaturation / 255.0; + + if (bmpToDraw->_transparency > 0) + sat_trs_lum[1] = (float)bmpToDraw->_transparency / 255.0; + else + sat_trs_lum[1] = 1.0f; + + if (bmpToDraw->_lightLevel > 0) + sat_trs_lum[2] = (float)bmpToDraw->_lightLevel / 255.0; + else + sat_trs_lum[2] = 1.0f; + + glUniform1i(_tintShader.SamplerVar, 0); + glUniform3f(_tintShader.ColorVar, rgb[0], rgb[1], rgb[2]); + glUniform3f(_tintShader.AuxVar, sat_trs_lum[0], sat_trs_lum[1], sat_trs_lum[2]); + } + else if (do_light) + { + // Use light shader + glUseProgram(_lightShader.Program); + float light_lev = 1.0f; + float alpha = 1.0f; + + // Light level parameter in DDB is weird, it is measured in units of + // 1/255 (although effectively 1/250, see draw.cpp), but contains two + // ranges: 1-255 is darker range and 256-511 is brighter range. + // (light level of 0 means "default color") + if ((bmpToDraw->_lightLevel > 0) && (bmpToDraw->_lightLevel < 256)) + { + // darkening the sprite... this stupid calculation is for + // consistency with the allegro software-mode code that does + // a trans blend with a (8,8,8) sprite + light_lev = -((bmpToDraw->_lightLevel * 192) / 256 + 64) / 255.f; // darker, uses MODULATE op + } + else if (bmpToDraw->_lightLevel > 256) + { + light_lev = ((bmpToDraw->_lightLevel - 256) / 2) / 255.f; // brighter, uses ADD op + } + + if (bmpToDraw->_transparency > 0) + alpha = bmpToDraw->_transparency / 255.f; + + glUniform1i(_lightShader.SamplerVar, 0); + glUniform1f(_lightShader.ColorVar, light_lev); + glUniform1f(_lightShader.AuxVar, alpha); + } + else + { + // Use default processing + if (bmpToDraw->_transparency == 0) + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + else + glColor4f(1.0f, 1.0f, 1.0f, bmpToDraw->_transparency / 255.0f); + } + + float width = bmpToDraw->GetWidthToRender(); + float height = bmpToDraw->GetHeightToRender(); + float xProportion = (float)width / (float)bmpToDraw->_width; + float yProportion = (float)height / (float)bmpToDraw->_height; + int drawAtX = drawListEntry->x; + int drawAtY = drawListEntry->y; + + for (int ti = 0; ti < bmpToDraw->_numTiles; ti++) + { + width = bmpToDraw->_tiles[ti].width * xProportion; + height = bmpToDraw->_tiles[ti].height * yProportion; + float xOffs; + float yOffs = bmpToDraw->_tiles[ti].y * yProportion; + if (bmpToDraw->_flipped) + xOffs = (bmpToDraw->_width - (bmpToDraw->_tiles[ti].x + bmpToDraw->_tiles[ti].width)) * xProportion; + else + xOffs = bmpToDraw->_tiles[ti].x * xProportion; + int thisX = drawAtX + xOffs; + int thisY = drawAtY + yOffs; + thisX = (-(_srcRect.GetWidth() / 2)) + thisX; + thisY = (_srcRect.GetHeight() / 2) - thisY; + + //Setup translation and scaling matrices + float widthToScale = (float)width; + float heightToScale = (float)height; + if (bmpToDraw->_flipped) + { + // The usual transform changes 0..1 into 0..width + // So first negate it (which changes 0..w into -w..0) + widthToScale = -widthToScale; + // and now shift it over to make it 0..w again + thisX += width; + } + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // + // IMPORTANT: in OpenGL order of transformation is REVERSE to the order of commands! + // + // Origin is at the middle of the surface + if (_do_render_to_texture) + glTranslatef(_backRenderSize.Width / 2.0f, _backRenderSize.Height / 2.0f, 0.0f); + else + glTranslatef(_srcRect.GetWidth() / 2.0f, _srcRect.GetHeight() / 2.0f, 0.0f); + + // Global batch transform + glMultMatrixf(matGlobal.m); + // Self sprite transform (first scale, then rotate and then translate, reversed) + glTranslatef((float)thisX, (float)thisY, 0.0f); + glRotatef(0.f, 0.f, 0.f, 1.f); + glScalef(widthToScale, heightToScale, 1.0f); + + glBindTexture(GL_TEXTURE_2D, bmpToDraw->_tiles[ti].texture); + + if ((_smoothScaling) && bmpToDraw->_useResampler && (bmpToDraw->_stretchToHeight > 0) && + ((bmpToDraw->_stretchToHeight != bmpToDraw->_height) || + (bmpToDraw->_stretchToWidth != bmpToDraw->_width))) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + else if (_do_render_to_texture) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + } + else + { + _filter->SetFilteringForStandardSprite(); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + + if (bmpToDraw->_vertex != nullptr) + { + glTexCoordPointer(2, GL_FLOAT, sizeof(OGLCUSTOMVERTEX), &(bmpToDraw->_vertex[ti * 4].tu)); + glVertexPointer(2, GL_FLOAT, sizeof(OGLCUSTOMVERTEX), &(bmpToDraw->_vertex[ti * 4].position)); + } + else + { + glTexCoordPointer(2, GL_FLOAT, sizeof(OGLCUSTOMVERTEX), &defaultVertices[0].tu); + glVertexPointer(2, GL_FLOAT, sizeof(OGLCUSTOMVERTEX), &defaultVertices[0].position); + } + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + glUseProgram(0); +} + +void OGLGraphicsDriver::_render(bool clearDrawListAfterwards) +{ +#if AGS_PLATFORM_OS_IOS + ios_select_buffer(); +#endif + +#if AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS + // TODO: + // For some reason, mobile ports initialize actual display size after a short delay. + // This is why we update display mode and related parameters (projection, viewport) + // at the first render pass. + // Ofcourse this is not a good thing, ideally the display size should be made + // known before graphic mode is initialized. This would require analysis and rewrite + // of the platform-specific part of the code (Java app for Android / XCode for iOS). + if (!device_screen_initialized) + { + UpdateDeviceScreen(); + device_screen_initialized = 1; + } +#endif + + if (_do_render_to_texture) + { + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _fbo); + + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, _backRenderSize.Width, _backRenderSize.Height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, _backRenderSize.Width, 0, _backRenderSize.Height, 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + } + else + { + glDisable(GL_SCISSOR_TEST); + glClear(GL_COLOR_BUFFER_BIT); + glEnable(GL_SCISSOR_TEST); + + glViewport(_viewportRect.Left, _viewportRect.Top, _viewportRect.GetWidth(), _viewportRect.GetHeight()); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, _srcRect.GetWidth(), 0, _srcRect.GetHeight(), 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + } + + RenderSpriteBatches(); + + if (_do_render_to_texture) + { + // Texture is ready, now create rectangle in the world space and draw texture upon it +#if AGS_PLATFORM_OS_IOS + ios_select_buffer(); +#else + glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); +#endif + + glViewport(_viewportRect.Left, _viewportRect.Top, _viewportRect.GetWidth(), _viewportRect.GetHeight()); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, _srcRect.GetWidth(), 0, _srcRect.GetHeight(), 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDisable(GL_BLEND); + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + + // use correct sampling method when stretching buffer to the final rect + _filter->SetFilteringForStandardSprite(); + + glBindTexture(GL_TEXTURE_2D, _backbuffer); + + glTexCoordPointer(2, GL_FLOAT, 0, _backbuffer_texture_coordinates); + glVertexPointer(2, GL_FLOAT, 0, _backbuffer_vertices); + glClear(GL_COLOR_BUFFER_BIT); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glEnable(GL_BLEND); + } + + glFinish(); + +#if AGS_PLATFORM_OS_WINDOWS + SwapBuffers(_hDC); +#elif AGS_PLATFORM_OS_LINUX + glXSwapBuffers(_xwin.display, _xwin.window); +#elif AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS + device_swap_buffers(); +#endif + + if (clearDrawListAfterwards) + { + BackupDrawLists(); + ClearDrawLists(); + } + ResetFxPool(); +} + +void OGLGraphicsDriver::RenderSpriteBatches() +{ + // Render all the sprite batches with necessary transformations + Rect main_viewport = _do_render_to_texture ? _srcRect : _viewportRect; + int surface_height = _do_render_to_texture ? _srcRect.GetHeight() : device_screen_physical_height; + // TODO: see if it's possible to refactor and not enable/disable scissor test + // TODO: also maybe sync scissor code logic with D3D renderer + if (_do_render_to_texture) + glEnable(GL_SCISSOR_TEST); + + for (size_t i = 0; i <= _actSpriteBatch; ++i) + { + const Rect &viewport = _spriteBatches[i].Viewport; + const OGLSpriteBatch &batch = _spriteBatches[i]; + if (!viewport.IsEmpty()) + { + Rect scissor = _do_render_to_texture ? viewport : _scaling.ScaleRange(viewport); + scissor = ConvertTopDownRect(scissor, surface_height); + glScissor(scissor.Left, scissor.Top, scissor.GetWidth(), scissor.GetHeight()); + } + else + { + glScissor(main_viewport.Left, main_viewport.Top, main_viewport.GetWidth(), main_viewport.GetHeight()); + } + _stageVirtualScreen = GetStageScreen(i); + RenderSpriteBatch(batch); + } + + _stageVirtualScreen = GetStageScreen(0); + glScissor(main_viewport.Left, main_viewport.Top, main_viewport.GetWidth(), main_viewport.GetHeight()); + if (_do_render_to_texture) + glDisable(GL_SCISSOR_TEST); +} + +void OGLGraphicsDriver::RenderSpriteBatch(const OGLSpriteBatch &batch) +{ + OGLDrawListEntry stageEntry; // raw-draw plugin support + + const std::vector &listToDraw = batch.List; + for (size_t i = 0; i < listToDraw.size(); i++) + { + if (listToDraw[i].skip) + continue; + + const OGLDrawListEntry *sprite = &listToDraw[i]; + if (listToDraw[i].bitmap == nullptr) + { + if (DoNullSpriteCallback(listToDraw[i].x, listToDraw[i].y)) + stageEntry = OGLDrawListEntry((OGLBitmap*)_stageVirtualScreenDDB); + else + continue; + sprite = &stageEntry; + } + + this->_renderSprite(sprite, batch.Matrix); + } +} + +void OGLGraphicsDriver::InitSpriteBatch(size_t index, const SpriteBatchDesc &desc) +{ + if (_spriteBatches.size() <= index) + _spriteBatches.resize(index + 1); + _spriteBatches[index].List.clear(); + + Rect orig_viewport = desc.Viewport; + Rect node_viewport = desc.Viewport; + // Combine both world transform and viewport transform into one matrix for faster perfomance + // NOTE: in OpenGL order of transformation is REVERSE to the order of commands! + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + // Global node transformation (flip and offset) + int node_tx = desc.Offset.X, node_ty = desc.Offset.Y; + float node_sx = 1.f, node_sy = 1.f; + if ((desc.Flip == kFlip_Vertical) || (desc.Flip == kFlip_Both)) + { + int left = _srcRect.GetWidth() - (orig_viewport.Right + 1); + node_viewport.MoveToX(left); + node_sx = -1.f; + } + if ((desc.Flip == kFlip_Horizontal) || (desc.Flip == kFlip_Both)) + { + int top = _srcRect.GetHeight() - (orig_viewport.Bottom + 1); + node_viewport.MoveToY(top); + node_sy = -1.f; + } + _spriteBatches[index].Viewport = Rect::MoveBy(node_viewport, node_tx, node_ty); + glTranslatef(node_tx, -(node_ty), 0.0f); + glScalef(node_sx, node_sy, 1.f); + // NOTE: before node, translate to viewport position; remove this if this + // is changed to a separate operation at some point + // TODO: find out if this is an optimal way to translate scaled room into Top-Left screen coordinates + float scaled_offx = (_srcRect.GetWidth() - desc.Transform.ScaleX * (float)_srcRect.GetWidth()) / 2.f; + float scaled_offy = (_srcRect.GetHeight() - desc.Transform.ScaleY * (float)_srcRect.GetHeight()) / 2.f; + glTranslatef((float)(orig_viewport.Left - scaled_offx), (float)-(orig_viewport.Top - scaled_offy), 0.0f); + // IMPORTANT: while the sprites are usually transformed in the order of Scale-Rotate-Translate, + // the camera's transformation is essentially reverse world transformation. And the operations + // are inverse: Translate-Rotate-Scale (here they are double inverse because OpenGL). + glScalef(desc.Transform.ScaleX, desc.Transform.ScaleY, 1.f); // scale camera + glRotatef(Math::RadiansToDegrees(desc.Transform.Rotate), 0.f, 0.f, 1.f); // rotate camera + glTranslatef((float)desc.Transform.X, (float)-desc.Transform.Y, 0.0f); // translate camera + glGetFloatv(GL_MODELVIEW_MATRIX, _spriteBatches[index].Matrix.m); + glLoadIdentity(); + + // create stage screen for plugin raw drawing + int src_w = orig_viewport.GetWidth() / desc.Transform.ScaleX; + int src_h = orig_viewport.GetHeight() / desc.Transform.ScaleY; + CreateStageScreen(index, Size(src_w, src_h)); +} + +void OGLGraphicsDriver::ResetAllBatches() +{ + for (size_t i = 0; i < _spriteBatches.size(); ++i) + _spriteBatches[i].List.clear(); +} + +void OGLGraphicsDriver::ClearDrawBackups() +{ + _backupBatchDescs.clear(); + _backupBatches.clear(); +} + +void OGLGraphicsDriver::BackupDrawLists() +{ + ClearDrawBackups(); + for (size_t i = 0; i <= _actSpriteBatch; ++i) + { + _backupBatchDescs.push_back(_spriteBatchDesc[i]); + _backupBatches.push_back(_spriteBatches[i]); + } +} + +void OGLGraphicsDriver::RestoreDrawLists() +{ + if (_backupBatchDescs.size() == 0) + { + ClearDrawLists(); + return; + } + _spriteBatchDesc = _backupBatchDescs; + _spriteBatches = _backupBatches; + _actSpriteBatch = _backupBatchDescs.size() - 1; +} + +void OGLGraphicsDriver::DrawSprite(int x, int y, IDriverDependantBitmap* bitmap) +{ + _spriteBatches[_actSpriteBatch].List.push_back(OGLDrawListEntry((OGLBitmap*)bitmap, x, y)); +} + +void OGLGraphicsDriver::DestroyDDB(IDriverDependantBitmap* bitmap) +{ + // Remove deleted DDB from backups + for (OGLSpriteBatches::iterator it = _backupBatches.begin(); it != _backupBatches.end(); ++it) + { + std::vector &drawlist = it->List; + for (size_t i = 0; i < drawlist.size(); i++) + { + if (drawlist[i].bitmap == bitmap) + drawlist[i].skip = true; + } + } + delete bitmap; +} + + +void OGLGraphicsDriver::UpdateTextureRegion(OGLTextureTile *tile, Bitmap *bitmap, OGLBitmap *target, bool hasAlpha) +{ + int textureHeight = tile->height; + int textureWidth = tile->width; + + // TODO: this seem to be tad overcomplicated, these conversions were made + // when texture is just created. Check later if this operation here may be removed. + AdjustSizeToNearestSupportedByCard(&textureWidth, &textureHeight); + + int tilex = 0, tiley = 0, tileWidth = tile->width, tileHeight = tile->height; + if (textureWidth > tile->width) + { + int texxoff = Math::Min(textureWidth - tile->width - 1, 1); + tilex = texxoff; + tileWidth += 1 + texxoff; + } + if (textureHeight > tile->height) + { + int texyoff = Math::Min(textureHeight - tile->height - 1, 1); + tiley = texyoff; + tileHeight += 1 + texyoff; + } + + const bool usingLinearFiltering = _filter->UseLinearFiltering(); + char *origPtr = (char*)malloc(sizeof(int) * tileWidth * tileHeight); + const int pitch = tileWidth * sizeof(int); + char *memPtr = origPtr + pitch * tiley + tilex * sizeof(int); + + TextureTile fixedTile; + fixedTile.x = tile->x; + fixedTile.y = tile->y; + fixedTile.width = Math::Min(tile->width, tileWidth); + fixedTile.height = Math::Min(tile->height, tileHeight); + if (target->_opaque) + BitmapToVideoMemOpaque(bitmap, hasAlpha, &fixedTile, target, memPtr, pitch); + else + BitmapToVideoMem(bitmap, hasAlpha, &fixedTile, target, memPtr, pitch, usingLinearFiltering); + + // Mimic the behaviour of GL_CLAMP_EDGE for the tile edges + // NOTE: on some platforms GL_CLAMP_EDGE does not work with the version of OpenGL we're using. + if (tile->width < tileWidth) + { + if (tilex > 0) + { + for (int y = 0; y < tileHeight; y++) + { + unsigned int* edge_left_col = (unsigned int*)(origPtr + y * pitch + (tilex - 1) * sizeof(int)); + unsigned int* bm_left_col = (unsigned int*)(origPtr + y * pitch + (tilex) * sizeof(int)); + *edge_left_col = *bm_left_col & 0x00FFFFFF; + } + } + for (int y = 0; y < tileHeight; y++) + { + unsigned int* edge_right_col = (unsigned int*)(origPtr + y * pitch + (tilex + tile->width) * sizeof(int)); + unsigned int* bm_right_col = edge_right_col - 1; + *edge_right_col = *bm_right_col & 0x00FFFFFF; + } + } + if (tile->height < tileHeight) + { + if (tiley > 0) + { + unsigned int* edge_top_row = (unsigned int*)(origPtr + pitch * (tiley - 1)); + unsigned int* bm_top_row = (unsigned int*)(origPtr + pitch * (tiley)); + for (int x = 0; x < tileWidth; x++) + { + edge_top_row[x] = bm_top_row[x] & 0x00FFFFFF; + } + } + unsigned int* edge_bottom_row = (unsigned int*)(origPtr + pitch * (tiley + tile->height)); + unsigned int* bm_bottom_row = (unsigned int*)(origPtr + pitch * (tiley + tile->height - 1)); + for (int x = 0; x < tileWidth; x++) + { + edge_bottom_row[x] = bm_bottom_row[x] & 0x00FFFFFF; + } + } + + glBindTexture(GL_TEXTURE_2D, tile->texture); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, tileWidth, tileHeight, GL_RGBA, GL_UNSIGNED_BYTE, origPtr); + + free(origPtr); +} + +void OGLGraphicsDriver::UpdateDDBFromBitmap(IDriverDependantBitmap* bitmapToUpdate, Bitmap *bitmap, bool hasAlpha) +{ + OGLBitmap *target = (OGLBitmap*)bitmapToUpdate; + if (target->_width != bitmap->GetWidth() || target->_height != bitmap->GetHeight()) + throw Ali3DException("UpdateDDBFromBitmap: mismatched bitmap size"); + const int color_depth = bitmap->GetColorDepth(); + if (color_depth != target->_colDepth) + throw Ali3DException("UpdateDDBFromBitmap: mismatched colour depths"); + + target->_hasAlpha = hasAlpha; + if (color_depth == 8) + select_palette(palette); + + for (int i = 0; i < target->_numTiles; i++) + { + UpdateTextureRegion(&target->_tiles[i], bitmap, target, hasAlpha); + } + + if (color_depth == 8) + unselect_palette(); +} + +int OGLGraphicsDriver::GetCompatibleBitmapFormat(int color_depth) +{ + if (color_depth == 8) + return 8; + if (color_depth > 8 && color_depth <= 16) + return 16; + return 32; +} + +void OGLGraphicsDriver::AdjustSizeToNearestSupportedByCard(int *width, int *height) +{ + int allocatedWidth = *width, allocatedHeight = *height; + + bool foundWidth = false, foundHeight = false; + int tryThis = 2; + while ((!foundWidth) || (!foundHeight)) + { + if ((tryThis >= allocatedWidth) && (!foundWidth)) + { + allocatedWidth = tryThis; + foundWidth = true; + } + + if ((tryThis >= allocatedHeight) && (!foundHeight)) + { + allocatedHeight = tryThis; + foundHeight = true; + } + + tryThis = tryThis << 1; + } + + *width = allocatedWidth; + *height = allocatedHeight; +} + + + +IDriverDependantBitmap* OGLGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) +{ + int allocatedWidth = bitmap->GetWidth(); + int allocatedHeight = bitmap->GetHeight(); + // NOTE: original bitmap object is not modified in this function + if (bitmap->GetColorDepth() != GetCompatibleBitmapFormat(bitmap->GetColorDepth())) + throw Ali3DException("CreateDDBFromBitmap: bitmap colour depth not supported"); + int colourDepth = bitmap->GetColorDepth(); + + OGLBitmap *ddb = new OGLBitmap(bitmap->GetWidth(), bitmap->GetHeight(), colourDepth, opaque); + + AdjustSizeToNearestSupportedByCard(&allocatedWidth, &allocatedHeight); + int tilesAcross = 1, tilesDown = 1; + + // Calculate how many textures will be necessary to + // store this image + + int MaxTextureWidth = 512; + int MaxTextureHeight = 512; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &MaxTextureWidth); + MaxTextureHeight = MaxTextureWidth; + + tilesAcross = (allocatedWidth + MaxTextureWidth - 1) / MaxTextureWidth; + tilesDown = (allocatedHeight + MaxTextureHeight - 1) / MaxTextureHeight; + int tileWidth = bitmap->GetWidth() / tilesAcross; + int lastTileExtraWidth = bitmap->GetWidth() % tilesAcross; + int tileHeight = bitmap->GetHeight() / tilesDown; + int lastTileExtraHeight = bitmap->GetHeight() % tilesDown; + int tileAllocatedWidth = tileWidth; + int tileAllocatedHeight = tileHeight; + + AdjustSizeToNearestSupportedByCard(&tileAllocatedWidth, &tileAllocatedHeight); + + int numTiles = tilesAcross * tilesDown; + OGLTextureTile *tiles = (OGLTextureTile*)malloc(sizeof(OGLTextureTile) * numTiles); + memset(tiles, 0, sizeof(OGLTextureTile) * numTiles); + + OGLCUSTOMVERTEX *vertices = nullptr; + + if ((numTiles == 1) && + (allocatedWidth == bitmap->GetWidth()) && + (allocatedHeight == bitmap->GetHeight())) + { + // use default whole-image vertices + } + else + { + // The texture is not the same as the bitmap, so create some custom vertices + // so that only the relevant portion of the texture is rendered + int vertexBufferSize = numTiles * 4 * sizeof(OGLCUSTOMVERTEX); + + ddb->_vertex = vertices = (OGLCUSTOMVERTEX*)malloc(vertexBufferSize); + } + + for (int x = 0; x < tilesAcross; x++) + { + for (int y = 0; y < tilesDown; y++) + { + OGLTextureTile *thisTile = &tiles[y * tilesAcross + x]; + int thisAllocatedWidth = tileAllocatedWidth; + int thisAllocatedHeight = tileAllocatedHeight; + thisTile->x = x * tileWidth; + thisTile->y = y * tileHeight; + thisTile->width = tileWidth; + thisTile->height = tileHeight; + if (x == tilesAcross - 1) + { + thisTile->width += lastTileExtraWidth; + thisAllocatedWidth = thisTile->width; + AdjustSizeToNearestSupportedByCard(&thisAllocatedWidth, &thisAllocatedHeight); + } + if (y == tilesDown - 1) + { + thisTile->height += lastTileExtraHeight; + thisAllocatedHeight = thisTile->height; + AdjustSizeToNearestSupportedByCard(&thisAllocatedWidth, &thisAllocatedHeight); + } + + if (vertices != nullptr) + { + const int texxoff = (thisAllocatedWidth - thisTile->width) > 1 ? 1 : 0; + const int texyoff = (thisAllocatedHeight - thisTile->height) > 1 ? 1 : 0; + for (int vidx = 0; vidx < 4; vidx++) + { + int i = (y * tilesAcross + x) * 4 + vidx; + vertices[i] = defaultVertices[vidx]; + if (vertices[i].tu > 0.0) + { + vertices[i].tu = (float)(texxoff + thisTile->width) / (float)thisAllocatedWidth; + } + else + { + vertices[i].tu = (float)(texxoff) / (float)thisAllocatedWidth; + } + if (vertices[i].tv > 0.0) + { + vertices[i].tv = (float)(texyoff + thisTile->height) / (float)thisAllocatedHeight; + } + else + { + vertices[i].tv = (float)(texyoff) / (float)thisAllocatedHeight; + } + } + } + + glGenTextures(1, &thisTile->texture); + glBindTexture(GL_TEXTURE_2D, thisTile->texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + // NOTE: pay attention that the texture format depends on the **display mode**'s format, + // rather than source bitmap's color depth! + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, thisAllocatedWidth, thisAllocatedHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + } + } + + ddb->_numTiles = numTiles; + ddb->_tiles = tiles; + + UpdateDDBFromBitmap(ddb, bitmap, hasAlpha); + + return ddb; +} + +void OGLGraphicsDriver::do_fade(bool fadingOut, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) +{ + // Construct scene in order: game screen, fade fx, post game overlay + // NOTE: please keep in mind: redrawing last saved frame here instead of constructing new one + // is done because of backwards-compatibility issue: originally AGS faded out using frame + // drawn before the script that triggers blocking fade (e.g. instigated by ChangeRoom). + // Unfortunately some existing games were changing looks of the screen during same function, + // but these were not supposed to get on screen until before fade-in. + if (fadingOut) + this->_reDrawLastFrame(); + else if (_drawScreenCallback != nullptr) + _drawScreenCallback(); + Bitmap *blackSquare = BitmapHelper::CreateBitmap(16, 16, 32); + blackSquare->Clear(makecol32(targetColourRed, targetColourGreen, targetColourBlue)); + IDriverDependantBitmap *d3db = this->CreateDDBFromBitmap(blackSquare, false, true); + delete blackSquare; + BeginSpriteBatch(_srcRect, SpriteTransform()); + d3db->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false); + this->DrawSprite(0, 0, d3db); + if (_drawPostScreenCallback != NULL) + _drawPostScreenCallback(); + + if (speed <= 0) speed = 16; + speed *= 2; // harmonise speeds with software driver which is faster + for (int a = 1; a < 255; a += speed) + { + d3db->SetTransparency(fadingOut ? a : (255 - a)); + this->_render(false); + + if (_pollingCallback) + _pollingCallback(); + WaitForNextFrame(); + } + + if (fadingOut) + { + d3db->SetTransparency(0); + this->_render(false); + } + + this->DestroyDDB(d3db); + this->ClearDrawLists(); + ResetFxPool(); +} + +void OGLGraphicsDriver::FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) +{ + do_fade(true, speed, targetColourRed, targetColourGreen, targetColourBlue); +} + +void OGLGraphicsDriver::FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue) +{ + do_fade(false, speed, targetColourRed, targetColourGreen, targetColourBlue); +} + +void OGLGraphicsDriver::BoxOutEffect(bool blackingOut, int speed, int delay) +{ + // Construct scene in order: game screen, fade fx, post game overlay + if (blackingOut) + this->_reDrawLastFrame(); + else if (_drawScreenCallback != nullptr) + _drawScreenCallback(); + Bitmap *blackSquare = BitmapHelper::CreateBitmap(16, 16, 32); + blackSquare->Clear(); + IDriverDependantBitmap *d3db = this->CreateDDBFromBitmap(blackSquare, false, true); + delete blackSquare; + BeginSpriteBatch(_srcRect, SpriteTransform()); + size_t fx_batch = _actSpriteBatch; + d3db->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false); + this->DrawSprite(0, 0, d3db); + if (!blackingOut) + { + // when fading in, draw four black boxes, one + // across each side of the screen + this->DrawSprite(0, 0, d3db); + this->DrawSprite(0, 0, d3db); + this->DrawSprite(0, 0, d3db); + } + if (_drawPostScreenCallback != NULL) + _drawPostScreenCallback(); + + int yspeed = _srcRect.GetHeight() / (_srcRect.GetWidth() / speed); + int boxWidth = speed; + int boxHeight = yspeed; + + while (boxWidth < _srcRect.GetWidth()) + { + boxWidth += speed; + boxHeight += yspeed; + OGLSpriteBatch &batch = _spriteBatches[fx_batch]; + std::vector &drawList = batch.List; + size_t last = drawList.size() - 1; + if (blackingOut) + { + drawList[last].x = _srcRect.GetWidth() / 2- boxWidth / 2; + drawList[last].y = _srcRect.GetHeight() / 2 - boxHeight / 2; + d3db->SetStretch(boxWidth, boxHeight, false); + } + else + { + drawList[last - 3].x = _srcRect.GetWidth() / 2 - boxWidth / 2 - _srcRect.GetWidth(); + drawList[last - 2].y = _srcRect.GetHeight() / 2 - boxHeight / 2 - _srcRect.GetHeight(); + drawList[last - 1].x = _srcRect.GetWidth() / 2 + boxWidth / 2; + drawList[last ].y = _srcRect.GetHeight() / 2 + boxHeight / 2; + d3db->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false); + } + + this->_render(false); + + if (_pollingCallback) + _pollingCallback(); + platform->Delay(delay); + } + + this->DestroyDDB(d3db); + this->ClearDrawLists(); + ResetFxPool(); +} + +void OGLGraphicsDriver::SetScreenFade(int red, int green, int blue) +{ + OGLBitmap *ddb = static_cast(MakeFx(red, green, blue)); + ddb->SetStretch(_spriteBatches[_actSpriteBatch].Viewport.GetWidth(), + _spriteBatches[_actSpriteBatch].Viewport.GetHeight(), false); + ddb->SetTransparency(0); + _spriteBatches[_actSpriteBatch].List.push_back(OGLDrawListEntry(ddb)); +} + +void OGLGraphicsDriver::SetScreenTint(int red, int green, int blue) +{ + if (red == 0 && green == 0 && blue == 0) return; + OGLBitmap *ddb = static_cast(MakeFx(red, green, blue)); + ddb->SetStretch(_spriteBatches[_actSpriteBatch].Viewport.GetWidth(), + _spriteBatches[_actSpriteBatch].Viewport.GetHeight(), false); + ddb->SetTransparency(128); + _spriteBatches[_actSpriteBatch].List.push_back(OGLDrawListEntry(ddb)); +} + + +OGLGraphicsFactory *OGLGraphicsFactory::_factory = nullptr; + +OGLGraphicsFactory::~OGLGraphicsFactory() +{ + _factory = nullptr; +} + +size_t OGLGraphicsFactory::GetFilterCount() const +{ + return 2; +} + +const GfxFilterInfo *OGLGraphicsFactory::GetFilterInfo(size_t index) const +{ + switch (index) + { + case 0: + return &OGLGfxFilter::FilterInfo; + case 1: + return &AAOGLGfxFilter::FilterInfo; + default: + return nullptr; + } +} + +String OGLGraphicsFactory::GetDefaultFilterID() const +{ + return OGLGfxFilter::FilterInfo.Id; +} + +/* static */ OGLGraphicsFactory *OGLGraphicsFactory::GetFactory() +{ + if (!_factory) + _factory = new OGLGraphicsFactory(); + return _factory; +} + +OGLGraphicsDriver *OGLGraphicsFactory::EnsureDriverCreated() +{ + if (!_driver) + _driver = new OGLGraphicsDriver(); + return _driver; +} + +OGLGfxFilter *OGLGraphicsFactory::CreateFilter(const String &id) +{ + if (OGLGfxFilter::FilterInfo.Id.CompareNoCase(id) == 0) + return new OGLGfxFilter(); + else if (AAOGLGfxFilter::FilterInfo.Id.CompareNoCase(id) == 0) + return new AAOGLGfxFilter(); + return nullptr; +} + +} // namespace OGL +} // namespace Engine +} // namespace AGS + +#endif // only on Windows, Android and iOS diff --git a/engines/ags/engine/gfx/ali3dogl.h b/engines/ags/engine/gfx/ali3dogl.h new file mode 100644 index 00000000000..019073df14c --- /dev/null +++ b/engines/ags/engine/gfx/ali3dogl.h @@ -0,0 +1,362 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// OpenGL graphics factory +// +//============================================================================= + +#ifndef __AGS_EE_GFX__ALI3DOGL_H +#define __AGS_EE_GFX__ALI3DOGL_H + +#include +#include "gfx/bitmap.h" +#include "gfx/ddb.h" +#include "gfx/gfxdriverfactorybase.h" +#include "gfx/gfxdriverbase.h" +#include "util/string.h" +#include "util/version.h" + +#include "ogl_headers.h" + +namespace AGS +{ +namespace Engine +{ + +namespace OGL +{ + +using Common::Bitmap; +using Common::String; +using Common::Version; + +typedef struct _OGLVECTOR { + float x; + float y; +} OGLVECTOR2D; + + +struct OGLCUSTOMVERTEX +{ + OGLVECTOR2D position; + float tu; + float tv; +}; + +struct OGLTextureTile : public TextureTile +{ + unsigned int texture; +}; + +class OGLBitmap : public VideoMemDDB +{ +public: + // Transparency is a bit counter-intuitive + // 0=not transparent, 255=invisible, 1..254 barely visible .. mostly visible + void SetTransparency(int transparency) override { _transparency = transparency; } + void SetFlippedLeftRight(bool isFlipped) override { _flipped = isFlipped; } + void SetStretch(int width, int height, bool useResampler = true) override + { + _stretchToWidth = width; + _stretchToHeight = height; + _useResampler = useResampler; + } + void SetLightLevel(int lightLevel) override { _lightLevel = lightLevel; } + void SetTint(int red, int green, int blue, int tintSaturation) override + { + _red = red; + _green = green; + _blue = blue; + _tintSaturation = tintSaturation; + } + + bool _flipped; + int _stretchToWidth, _stretchToHeight; + bool _useResampler; + int _red, _green, _blue; + int _tintSaturation; + int _lightLevel; + bool _hasAlpha; + int _transparency; + OGLCUSTOMVERTEX* _vertex; + OGLTextureTile *_tiles; + int _numTiles; + + OGLBitmap(int width, int height, int colDepth, bool opaque) + { + _width = width; + _height = height; + _colDepth = colDepth; + _flipped = false; + _hasAlpha = false; + _stretchToWidth = 0; + _stretchToHeight = 0; + _useResampler = false; + _red = _green = _blue = 0; + _tintSaturation = 0; + _lightLevel = 0; + _transparency = 0; + _opaque = opaque; + _vertex = nullptr; + _tiles = nullptr; + _numTiles = 0; + } + + int GetWidthToRender() const { return (_stretchToWidth > 0) ? _stretchToWidth : _width; } + int GetHeightToRender() const { return (_stretchToHeight > 0) ? _stretchToHeight : _height; } + + void Dispose(); + + ~OGLBitmap() override + { + Dispose(); + } +}; + +typedef SpriteDrawListEntry OGLDrawListEntry; +typedef struct GLMATRIX { GLfloat m[16]; } GLMATRIX; +struct OGLSpriteBatch +{ + // List of sprites to render + std::vector List; + // Clipping viewport + Rect Viewport; + // Transformation matrix, built from the batch description + GLMATRIX Matrix; +}; +typedef std::vector OGLSpriteBatches; + + +class OGLDisplayModeList : public IGfxModeList +{ +public: + OGLDisplayModeList(const std::vector &modes) + : _modes(modes) + { + } + + int GetModeCount() const override + { + return _modes.size(); + } + + bool GetMode(int index, DisplayMode &mode) const override + { + if (index >= 0 && (size_t)index < _modes.size()) + { + mode = _modes[index]; + return true; + } + return false; + } + +private: + std::vector _modes; +}; + + +class OGLGfxFilter; + +class OGLGraphicsDriver : public VideoMemoryGraphicsDriver +{ +public: + const char*GetDriverName() override { return "OpenGL"; } + const char*GetDriverID() override { return "OGL"; } + void SetTintMethod(TintMethod method) override; + bool SetDisplayMode(const DisplayMode &mode, volatile int *loopTimer) override; + bool SetNativeSize(const Size &src_size) override; + bool SetRenderFrame(const Rect &dst_rect) override; + int GetDisplayDepthForNativeDepth(int native_color_depth) const override; + IGfxModeList *GetSupportedModeList(int color_depth) override; + bool IsModeSupported(const DisplayMode &mode) override; + PGfxFilter GetGraphicsFilter() const override; + void UnInit(); + // Clears the screen rectangle. The coordinates are expected in the **native game resolution**. + void ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse) override; + int GetCompatibleBitmapFormat(int color_depth) override; + IDriverDependantBitmap* CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) override; + void UpdateDDBFromBitmap(IDriverDependantBitmap* bitmapToUpdate, Bitmap *bitmap, bool hasAlpha) override; + void DestroyDDB(IDriverDependantBitmap* bitmap) override; + void DrawSprite(int x, int y, IDriverDependantBitmap* bitmap) override; + void RenderToBackBuffer() override; + void Render() override; + void Render(int xoff, int yoff, GlobalFlipType flip) override; + bool GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt) override; + void EnableVsyncBeforeRender(bool enabled) override { } + void Vsync() override; + void RenderSpritesAtScreenResolution(bool enabled, int supersampling) override; + void FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) override; + void FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue) override; + void BoxOutEffect(bool blackingOut, int speed, int delay) override; + bool SupportsGammaControl() override; + void SetGamma(int newGamma) override; + void UseSmoothScaling(bool enabled) override { _smoothScaling = enabled; } + bool RequiresFullRedrawEachFrame() override { return true; } + bool HasAcceleratedTransform() override { return true; } + void SetScreenFade(int red, int green, int blue) override; + void SetScreenTint(int red, int green, int blue) override; + + typedef std::shared_ptr POGLFilter; + + void SetGraphicsFilter(POGLFilter filter); + + OGLGraphicsDriver(); + ~OGLGraphicsDriver() override; + +private: + POGLFilter _filter; + +#if AGS_PLATFORM_OS_WINDOWS + HDC _hDC; + HGLRC _hRC; + HWND _hWnd; + HINSTANCE _hInstance; + GLuint _oldPixelFormat; + PIXELFORMATDESCRIPTOR _oldPixelFormatDesc; +#endif +#if AGS_PLATFORM_OS_LINUX + bool _have_window; + GLXContext _glxContext; +#endif + bool _firstTimeInit; + // Position of backbuffer texture in world space + GLfloat _backbuffer_vertices[8]; + // Relative position of source image on the backbuffer texture, + // in local coordinates + GLfloat _backbuffer_texture_coordinates[8]; + OGLCUSTOMVERTEX defaultVertices[4]; + String previousError; + bool _smoothScaling; + bool _legacyPixelShader; + // Shader program and its variable references; + // the variables are rather specific for AGS use (sprite tinting). + struct ShaderProgram + { + GLuint Program; + GLuint SamplerVar; // texture ID + GLuint ColorVar; // primary operation variable + GLuint AuxVar; // auxiliary variable + + ShaderProgram(); + }; + ShaderProgram _tintShader; + ShaderProgram _lightShader; + + int device_screen_physical_width; + int device_screen_physical_height; + + // Viewport and scissor rect, in OpenGL screen coordinates (0,0 is at left-bottom) + Rect _viewportRect; + + // These two flags define whether driver can, and should (respectively) + // render sprites to texture, and then texture to screen, as opposed to + // rendering to screen directly. This is known as supersampling mode + bool _can_render_to_texture; + bool _do_render_to_texture; + // Backbuffer texture multiplier, used to determine a size of texture + // relative to the native game size. + int _super_sampling; + unsigned int _backbuffer; + unsigned int _fbo; + // Size of the backbuffer drawing area, equals to native size + // multiplied by _super_sampling + Size _backRenderSize; + // Actual size of the backbuffer texture, created by OpenGL + Size _backTextureSize; + + OGLSpriteBatches _spriteBatches; + // TODO: these draw list backups are needed only for the fade-in/out effects + // find out if it's possible to reimplement these effects in main drawing routine. + SpriteBatchDescs _backupBatchDescs; + OGLSpriteBatches _backupBatches; + + void InitSpriteBatch(size_t index, const SpriteBatchDesc &desc) override; + void ResetAllBatches() override; + + // Sets up GL objects not related to particular display mode + void FirstTimeInit(); + // Initializes Gl rendering context + bool InitGlScreen(const DisplayMode &mode); + bool CreateGlContext(const DisplayMode &mode); + void DeleteGlContext(); + // Sets up general rendering parameters + void InitGlParams(const DisplayMode &mode); + void SetupDefaultVertices(); + // Test if rendering to texture is supported + void TestRenderToTexture(); + // Test if supersampling should be allowed with the current setup + void TestSupersampling(); + // Create shader programs for sprite tinting and changing light level + void CreateShaders(); + void CreateTintShader(); + void CreateLightShader(); + void CreateShaderProgram(ShaderProgram &prg, const char *name, const char *fragment_shader_src, + const char *sampler_var, const char *color_var, const char *aux_var); + void DeleteShaderProgram(ShaderProgram &prg); + void OutputShaderError(GLuint obj_id, const String &obj_name, bool is_shader); + // Configure backbuffer texture, that is used in render-to-texture mode + void SetupBackbufferTexture(); + void DeleteBackbufferTexture(); +#if AGS_PLATFORM_OS_WINDOWS || AGS_PLATFORM_OS_LINUX + void CreateDesktopScreen(int width, int height, int depth); +#elif AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS + void UpdateDeviceScreen(); +#endif + // Unset parameters and release resources related to the display mode + void ReleaseDisplayMode(); + void AdjustSizeToNearestSupportedByCard(int *width, int *height); + void UpdateTextureRegion(OGLTextureTile *tile, Bitmap *bitmap, OGLBitmap *target, bool hasAlpha); + void CreateVirtualScreen(); + void do_fade(bool fadingOut, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue); + void _renderSprite(const OGLDrawListEntry *entry, const GLMATRIX &matGlobal); + void SetupViewport(); + // Converts rectangle in top->down coordinates into OpenGL's native bottom->up coordinates + Rect ConvertTopDownRect(const Rect &top_down_rect, int surface_height); + + // Backup all draw lists in the temp storage + void BackupDrawLists(); + // Restore draw lists from the temp storage + void RestoreDrawLists(); + // Deletes draw list backups + void ClearDrawBackups(); + void _render(bool clearDrawListAfterwards); + void RenderSpriteBatches(); + void RenderSpriteBatch(const OGLSpriteBatch &batch); + void _reDrawLastFrame(); +}; + + +class OGLGraphicsFactory : public GfxDriverFactoryBase +{ +public: + ~OGLGraphicsFactory() override; + + size_t GetFilterCount() const override; + const GfxFilterInfo *GetFilterInfo(size_t index) const override; + String GetDefaultFilterID() const override; + + static OGLGraphicsFactory *GetFactory(); + +private: + OGLGraphicsDriver *EnsureDriverCreated() override; + OGLGfxFilter *CreateFilter(const String &id) override; + + static OGLGraphicsFactory *_factory; +}; + +} // namespace OGL +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__ALI3DOGL_H diff --git a/engines/ags/engine/gfx/ali3dsw.cpp b/engines/ags/engine/gfx/ali3dsw.cpp new file mode 100644 index 00000000000..ddc09b3342b --- /dev/null +++ b/engines/ags/engine/gfx/ali3dsw.cpp @@ -0,0 +1,911 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Allegro Interface for 3D; Software mode Allegro driver +// +//============================================================================= + +#include "gfx/ali3dsw.h" + +#include "core/platform.h" +#include "gfx/ali3dexception.h" +#include "gfx/gfxfilter_allegro.h" +#include "gfx/gfxfilter_hqx.h" +#include "gfx/gfx_util.h" +#include "main/main_allegro.h" +#include "platform/base/agsplatformdriver.h" +#include "ac/timer.h" + +#if AGS_DDRAW_GAMMA_CONTROL +// NOTE: this struct and variables are defined internally in Allegro +typedef struct DDRAW_SURFACE { + LPDIRECTDRAWSURFACE2 id; + int flags; + int lock_nesting; + BITMAP *parent_bmp; + struct DDRAW_SURFACE *next; + struct DDRAW_SURFACE *prev; +} DDRAW_SURFACE; + +extern "C" extern LPDIRECTDRAW2 directdraw; +extern "C" DDRAW_SURFACE *gfx_directx_primary_surface; +#endif // AGS_DDRAW_GAMMA_CONTROL + +#ifndef AGS_NO_VIDEO_PLAYER +extern int dxmedia_play_video (const char*, bool, int, int); +#endif + +namespace AGS +{ +namespace Engine +{ +namespace ALSW +{ + +using namespace Common; + +bool ALSoftwareGfxModeList::GetMode(int index, DisplayMode &mode) const +{ + if (_gfxModeList && index >= 0 && index < _gfxModeList->num_modes) + { + mode.Width = _gfxModeList->mode[index].width; + mode.Height = _gfxModeList->mode[index].height; + mode.ColorDepth = _gfxModeList->mode[index].bpp; + return true; + } + return false; +} + +unsigned long _trans_alpha_blender32(unsigned long x, unsigned long y, unsigned long n); +RGB faded_out_palette[256]; + + +ALSoftwareGraphicsDriver::ALSoftwareGraphicsDriver() +{ + _tint_red = 0; + _tint_green = 0; + _tint_blue = 0; + _autoVsync = false; + //_spareTintingScreen = nullptr; + _gfxModeList = nullptr; +#if AGS_DDRAW_GAMMA_CONTROL + dxGammaControl = nullptr; +#endif + _allegroScreenWrapper = nullptr; + _origVirtualScreen = nullptr; + virtualScreen = nullptr; + _stageVirtualScreen = nullptr; + + // Initialize default sprite batch, it will be used when no other batch was activated + ALSoftwareGraphicsDriver::InitSpriteBatch(0, _spriteBatchDesc[0]); +} + +bool ALSoftwareGraphicsDriver::IsModeSupported(const DisplayMode &mode) +{ + if (mode.Width <= 0 || mode.Height <= 0 || mode.ColorDepth <= 0) + { + set_allegro_error("Invalid resolution parameters: %d x %d x %d", mode.Width, mode.Height, mode.ColorDepth); + return false; + } +#if AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_MACOS + // Everything is drawn to a virtual screen, so all resolutions are supported. + return true; +#endif + + if (mode.Windowed) + { + return true; + } + if (_gfxModeList == nullptr) + { + _gfxModeList = get_gfx_mode_list(GetAllegroGfxDriverID(mode.Windowed)); + } + if (_gfxModeList != nullptr) + { + // if a list is available, check if the mode exists. This prevents the screen flicking + // between loads of unsupported resolutions + for (int i = 0; i < _gfxModeList->num_modes; i++) + { + if ((_gfxModeList->mode[i].width == mode.Width) && + (_gfxModeList->mode[i].height == mode.Height) && + (_gfxModeList->mode[i].bpp == mode.ColorDepth)) + { + return true; + } + } + set_allegro_error("This graphics mode is not supported"); + return false; + } + return true; +} + +int ALSoftwareGraphicsDriver::GetDisplayDepthForNativeDepth(int native_color_depth) const +{ + // TODO: check for device caps to know which depth is supported? + if (native_color_depth > 8) + return 32; + return native_color_depth; +} + +IGfxModeList *ALSoftwareGraphicsDriver::GetSupportedModeList(int color_depth) +{ + if (_gfxModeList == nullptr) + { + _gfxModeList = get_gfx_mode_list(GetAllegroGfxDriverID(false)); + } + if (_gfxModeList == nullptr) + { + return nullptr; + } + return new ALSoftwareGfxModeList(_gfxModeList); +} + +PGfxFilter ALSoftwareGraphicsDriver::GetGraphicsFilter() const +{ + return _filter; +} + +int ALSoftwareGraphicsDriver::GetAllegroGfxDriverID(bool windowed) +{ +#if AGS_PLATFORM_OS_WINDOWS + if (windowed) + return GFX_DIRECTX_WIN; + return GFX_DIRECTX; +#elif AGS_PLATFORM_OS_LINUX && (!defined (ALLEGRO_MAGIC_DRV)) + if (windowed) + return GFX_XWINDOWS; + return GFX_XWINDOWS_FULLSCREEN; +#elif AGS_PLATFORM_OS_MACOS + if (windowed) { + return GFX_COCOAGL_WINDOW; + } + return GFX_COCOAGL_FULLSCREEN; +#else + if (windowed) + return GFX_AUTODETECT_WINDOWED; + return GFX_AUTODETECT_FULLSCREEN; +#endif +} + +void ALSoftwareGraphicsDriver::SetGraphicsFilter(PALSWFilter filter) +{ + _filter = filter; + OnSetFilter(); + + // If we already have a gfx mode set, then use the new filter to update virtual screen immediately + CreateVirtualScreen(); +} + +void ALSoftwareGraphicsDriver::SetTintMethod(TintMethod method) +{ + // TODO: support new D3D-style tint method +} + +bool ALSoftwareGraphicsDriver::SetDisplayMode(const DisplayMode &mode, volatile int *loopTimer) +{ + ReleaseDisplayMode(); + + const int driver = GetAllegroGfxDriverID(mode.Windowed); + + set_color_depth(mode.ColorDepth); + + if (_initGfxCallback != nullptr) + _initGfxCallback(nullptr); + + if (!IsModeSupported(mode) || set_gfx_mode(driver, mode.Width, mode.Height, 0, 0) != 0) + return false; + + OnInit(loopTimer); + OnModeSet(mode); + // set_gfx_mode is an allegro function that creates screen bitmap; + // following code assumes the screen is already created, therefore we should + // ensure global bitmap wraps over existing allegro screen bitmap. + _allegroScreenWrapper = BitmapHelper::CreateRawBitmapWrapper(screen); + _allegroScreenWrapper->Clear(); + + // If we already have a gfx filter, then use it to update virtual screen immediately + CreateVirtualScreen(); + +#if AGS_DDRAW_GAMMA_CONTROL + if (!mode.Windowed) + { + memset(&ddrawCaps, 0, sizeof(ddrawCaps)); + ddrawCaps.dwSize = sizeof(ddrawCaps); + IDirectDraw2_GetCaps(directdraw, &ddrawCaps, NULL); + + if ((ddrawCaps.dwCaps2 & DDCAPS2_PRIMARYGAMMA) == 0) { } + else if (IDirectDrawSurface2_QueryInterface(gfx_directx_primary_surface->id, IID_IDirectDrawGammaControl, (void **)&dxGammaControl) == 0) + { + dxGammaControl->GetGammaRamp(0, &defaultGammaRamp); + } + } +#endif + + return true; +} + +void ALSoftwareGraphicsDriver::CreateVirtualScreen() +{ + if (!IsModeSet() || !IsRenderFrameValid() || !IsNativeSizeValid() || !_filter) + return; + DestroyVirtualScreen(); + // Adjust clipping so nothing gets drawn outside the game frame + _allegroScreenWrapper->SetClip(_dstRect); + // Initialize scaling filter and receive virtual screen pointer + // (which may or not be the same as real screen) + _origVirtualScreen = _filter->InitVirtualScreen(_allegroScreenWrapper, _srcRect.GetSize(), _dstRect); + // Apparently we must still create a virtual screen even if its same size and color depth, + // because drawing sprites directly on real screen bitmap causes blinking (unless I missed something here...) + if (_origVirtualScreen == _allegroScreenWrapper) + { + _origVirtualScreen = BitmapHelper::CreateBitmap(_srcRect.GetWidth(), _srcRect.GetHeight(), _mode.ColorDepth); + } + virtualScreen = _origVirtualScreen; + _stageVirtualScreen = virtualScreen; + // Set Allegro's screen pointer to what may be the real or virtual screen + screen = (BITMAP*)_origVirtualScreen->GetAllegroBitmap(); +} + +void ALSoftwareGraphicsDriver::DestroyVirtualScreen() +{ + if (_filter && _origVirtualScreen) + { + screen = (BITMAP*)_filter->ShutdownAndReturnRealScreen()->GetAllegroBitmap(); + } + _origVirtualScreen = nullptr; + virtualScreen = nullptr; + _stageVirtualScreen = nullptr; +} + +void ALSoftwareGraphicsDriver::ReleaseDisplayMode() +{ + OnModeReleased(); + ClearDrawLists(); + +#if AGS_DDRAW_GAMMA_CONTROL + if (dxGammaControl != NULL) + { + dxGammaControl->Release(); + dxGammaControl = NULL; + } +#endif + + DestroyVirtualScreen(); + + // Note this does not destroy the underlying allegro screen bitmap, only wrapper. + delete _allegroScreenWrapper; + _allegroScreenWrapper = nullptr; +} + +bool ALSoftwareGraphicsDriver::SetNativeSize(const Size &src_size) +{ + OnSetNativeSize(src_size); + // If we already have a gfx mode and gfx filter set, then use it to update virtual screen immediately + CreateVirtualScreen(); + return !_srcRect.IsEmpty(); +} + +bool ALSoftwareGraphicsDriver::SetRenderFrame(const Rect &dst_rect) +{ + OnSetRenderFrame(dst_rect); + // If we already have a gfx mode and gfx filter set, then use it to update virtual screen immediately + CreateVirtualScreen(); + return !_dstRect.IsEmpty(); +} + +void ALSoftwareGraphicsDriver::ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse) +{ + if (!_filter) return; + int color = 0; + if (colorToUse != nullptr) + color = makecol_depth(_mode.ColorDepth, colorToUse->r, colorToUse->g, colorToUse->b); + // NOTE: filter will do coordinate scaling for us + _filter->ClearRect(x1, y1, x2, y2, color); +} + +ALSoftwareGraphicsDriver::~ALSoftwareGraphicsDriver() +{ + ALSoftwareGraphicsDriver::UnInit(); +} + +void ALSoftwareGraphicsDriver::UnInit() +{ + OnUnInit(); + ReleaseDisplayMode(); + + if (_gfxModeList != nullptr) + { + destroy_gfx_mode_list(_gfxModeList); + _gfxModeList = nullptr; + } +} + +bool ALSoftwareGraphicsDriver::SupportsGammaControl() +{ +#if AGS_DDRAW_GAMMA_CONTROL + + if (dxGammaControl != NULL) + { + return 1; + } + +#endif + + return 0; +} + +void ALSoftwareGraphicsDriver::SetGamma(int newGamma) +{ +#if AGS_DDRAW_GAMMA_CONTROL + for (int i = 0; i < 256; i++) { + int newValue = ((int)defaultGammaRamp.red[i] * newGamma) / 100; + if (newValue >= 65535) + newValue = 65535; + gammaRamp.red[i] = newValue; + gammaRamp.green[i] = newValue; + gammaRamp.blue[i] = newValue; + } + + dxGammaControl->SetGammaRamp(0, &gammaRamp); +#endif +} + +int ALSoftwareGraphicsDriver::GetCompatibleBitmapFormat(int color_depth) +{ + return color_depth; +} + +IDriverDependantBitmap* ALSoftwareGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) +{ + ALSoftwareBitmap* newBitmap = new ALSoftwareBitmap(bitmap, opaque, hasAlpha); + return newBitmap; +} + +void ALSoftwareGraphicsDriver::UpdateDDBFromBitmap(IDriverDependantBitmap* bitmapToUpdate, Bitmap *bitmap, bool hasAlpha) +{ + ALSoftwareBitmap* alSwBmp = (ALSoftwareBitmap*)bitmapToUpdate; + alSwBmp->_bmp = bitmap; + alSwBmp->_hasAlpha = hasAlpha; +} + +void ALSoftwareGraphicsDriver::DestroyDDB(IDriverDependantBitmap* bitmap) +{ + delete bitmap; +} + +void ALSoftwareGraphicsDriver::InitSpriteBatch(size_t index, const SpriteBatchDesc &desc) +{ + if (_spriteBatches.size() <= index) + _spriteBatches.resize(index + 1); + ALSpriteBatch &batch = _spriteBatches[index]; + batch.List.clear(); + // TODO: correct offsets to have pre-scale (source) and post-scale (dest) offsets! + const int src_w = desc.Viewport.GetWidth() / desc.Transform.ScaleX; + const int src_h = desc.Viewport.GetHeight() / desc.Transform.ScaleY; + // Surface was prepared externally (common for room cameras) + if (desc.Surface != nullptr) + { + batch.Surface = std::static_pointer_cast(desc.Surface); + batch.Opaque = true; + batch.IsVirtualScreen = false; + } + // In case something was not initialized + else if (desc.Viewport.IsEmpty() || !virtualScreen) + { + batch.Surface.reset(); + batch.Opaque = false; + batch.IsVirtualScreen = false; + } + // Drawing directly on a viewport without transformation (other than offset) + else if (desc.Transform.ScaleX == 1.f && desc.Transform.ScaleY == 1.f) + { + if (!batch.Surface || !batch.IsVirtualScreen || batch.Surface->GetWidth() != src_w || batch.Surface->GetHeight() != src_h) + { + Rect rc = RectWH(desc.Viewport.Left, desc.Viewport.Top, desc.Viewport.GetWidth(), desc.Viewport.GetHeight()); + batch.Surface.reset(BitmapHelper::CreateSubBitmap(virtualScreen, rc)); + } + batch.Opaque = true; + batch.IsVirtualScreen = true; + } + // No surface prepared and has transformation other than offset + else if (!batch.Surface || batch.IsVirtualScreen || batch.Surface->GetWidth() != src_w || batch.Surface->GetHeight() != src_h) + { + batch.Surface.reset(new Bitmap(src_w, src_h)); + batch.Opaque = false; + batch.IsVirtualScreen = false; + } +} + +void ALSoftwareGraphicsDriver::ResetAllBatches() +{ + for (ALSpriteBatches::iterator it = _spriteBatches.begin(); it != _spriteBatches.end(); ++it) + it->List.clear(); +} + +void ALSoftwareGraphicsDriver::DrawSprite(int x, int y, IDriverDependantBitmap* bitmap) +{ + _spriteBatches[_actSpriteBatch].List.push_back(ALDrawListEntry((ALSoftwareBitmap*)bitmap, x, y)); +} + +void ALSoftwareGraphicsDriver::SetScreenFade(int red, int green, int blue) +{ + // TODO: was not necessary atm +} + +void ALSoftwareGraphicsDriver::SetScreenTint(int red, int green, int blue) +{ + _tint_red = red; _tint_green = green; _tint_blue = blue; + if (((_tint_red > 0) || (_tint_green > 0) || (_tint_blue > 0)) && (_mode.ColorDepth > 8)) + { + _spriteBatches[_actSpriteBatch].List.push_back(ALDrawListEntry((ALSoftwareBitmap*)0x1, 0, 0)); + } +} + +void ALSoftwareGraphicsDriver::RenderToBackBuffer() +{ + // Render all the sprite batches with necessary transformations + // + // NOTE: that's not immediately clear whether it would be faster to first draw upon a camera-sized + // surface then stretch final result to the viewport on screen, or stretch-blit each individual + // sprite right onto screen bitmap. We'd need to do proper profiling to know that. + // An important thing is that Allegro does not provide stretching functions for drawing sprites + // with blending and translucency; it seems you'd have to first stretch the original sprite onto a + // temp buffer and then TransBlendBlt / LitBlendBlt it to the final destination. Of course, doing + // that here would slow things down significantly, so if we ever go that way sprite caching will + // be required (similarily to how AGS caches flipped/scaled object sprites now for). + // + for (size_t i = 0; i <= _actSpriteBatch; ++i) + { + const Rect &viewport = _spriteBatchDesc[i].Viewport; + const SpriteTransform &transform = _spriteBatchDesc[i].Transform; + const ALSpriteBatch &batch = _spriteBatches[i]; + + virtualScreen->SetClip(viewport); + Bitmap *surface = batch.Surface.get(); + const int view_offx = viewport.Left; + const int view_offy = viewport.Top; + if (surface) + { + if (!batch.Opaque) + surface->ClearTransparent(); + _stageVirtualScreen = surface; + RenderSpriteBatch(batch, surface, transform.X, transform.Y); + if (!batch.IsVirtualScreen) + virtualScreen->StretchBlt(surface, RectWH(view_offx, view_offy, viewport.GetWidth(), viewport.GetHeight()), + batch.Opaque ? kBitmap_Copy : kBitmap_Transparency); + } + else + { + RenderSpriteBatch(batch, virtualScreen, view_offx + transform.X, view_offy + transform.Y); + } + _stageVirtualScreen = virtualScreen; + } + ClearDrawLists(); +} + +void ALSoftwareGraphicsDriver::RenderSpriteBatch(const ALSpriteBatch &batch, Common::Bitmap *surface, int surf_offx, int surf_offy) +{ + const std::vector &drawlist = batch.List; + for (size_t i = 0; i < drawlist.size(); i++) + { + if (drawlist[i].bitmap == nullptr) + { + if (_nullSpriteCallback) + _nullSpriteCallback(drawlist[i].x, drawlist[i].y); + else + throw Ali3DException("Unhandled attempt to draw null sprite"); + + continue; + } + else if (drawlist[i].bitmap == (ALSoftwareBitmap*)0x1) + { + // draw screen tint fx + set_trans_blender(_tint_red, _tint_green, _tint_blue, 0); + surface->LitBlendBlt(surface, 0, 0, 128); + continue; + } + + ALSoftwareBitmap* bitmap = drawlist[i].bitmap; + int drawAtX = drawlist[i].x + surf_offx; + int drawAtY = drawlist[i].y + surf_offy; + + if (bitmap->_transparency >= 255) {} // fully transparent, do nothing + else if ((bitmap->_opaque) && (bitmap->_bmp == surface) && (bitmap->_transparency == 0)) {} + else if (bitmap->_opaque) + { + surface->Blit(bitmap->_bmp, 0, 0, drawAtX, drawAtY, bitmap->_bmp->GetWidth(), bitmap->_bmp->GetHeight()); + // TODO: we need to also support non-masked translucent blend, but... + // Allegro 4 **does not have such function ready** :( (only masked blends, where it skips magenta pixels); + // I am leaving this problem for the future, as coincidentally software mode does not need this atm. + } + else if (bitmap->_hasAlpha) + { + if (bitmap->_transparency == 0) // no global transparency, simple alpha blend + set_alpha_blender(); + else + // here _transparency is used as alpha (between 1 and 254) + set_blender_mode(nullptr, nullptr, _trans_alpha_blender32, 0, 0, 0, bitmap->_transparency); + + surface->TransBlendBlt(bitmap->_bmp, drawAtX, drawAtY); + } + else + { + // here _transparency is used as alpha (between 1 and 254), but 0 means opaque! + GfxUtil::DrawSpriteWithTransparency(surface, bitmap->_bmp, drawAtX, drawAtY, + bitmap->_transparency ? bitmap->_transparency : 255); + } + } + // NOTE: following is experimental tint code (currently unused) +/* This alternate method gives the correct (D3D-style) result, but is just too slow! + if ((_spareTintingScreen != NULL) && + ((_spareTintingScreen->GetWidth() != surface->GetWidth()) || (_spareTintingScreen->GetHeight() != surface->GetHeight()))) + { + destroy_bitmap(_spareTintingScreen); + _spareTintingScreen = NULL; + } + if (_spareTintingScreen == NULL) + { + _spareTintingScreen = BitmapHelper::CreateBitmap_(GetColorDepth(surface), surface->GetWidth(), surface->GetHeight()); + } + tint_image(surface, _spareTintingScreen, _tint_red, _tint_green, _tint_blue, 100, 255); + Blit(_spareTintingScreen, surface, 0, 0, 0, 0, _spareTintingScreen->GetWidth(), _spareTintingScreen->GetHeight());*/ +} + +void ALSoftwareGraphicsDriver::Render(int xoff, int yoff, GlobalFlipType flip) +{ + RenderToBackBuffer(); + + if (_autoVsync) + this->Vsync(); + + if (flip == kFlip_None) + _filter->RenderScreen(virtualScreen, xoff, yoff); + else + _filter->RenderScreenFlipped(virtualScreen, xoff, yoff, flip); +} + +void ALSoftwareGraphicsDriver::Render() +{ + Render(0, 0, kFlip_None); +} + +void ALSoftwareGraphicsDriver::Vsync() +{ + vsync(); +} + +Bitmap *ALSoftwareGraphicsDriver::GetMemoryBackBuffer() +{ + return virtualScreen; +} + +void ALSoftwareGraphicsDriver::SetMemoryBackBuffer(Bitmap *backBuffer) +{ + if (backBuffer) + { + virtualScreen = backBuffer; + } + else + { + virtualScreen = _origVirtualScreen; + } + _stageVirtualScreen = virtualScreen; + + // Reset old virtual screen's subbitmaps + for (auto &batch : _spriteBatches) + { + if (batch.IsVirtualScreen) + batch.Surface.reset(); + } +} + +Bitmap *ALSoftwareGraphicsDriver::GetStageBackBuffer() +{ + return _stageVirtualScreen; +} + +bool ALSoftwareGraphicsDriver::GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt) +{ + (void)at_native_res; // software driver always renders at native resolution at the moment + // software filter is taught to copy to any size + if (destination->GetColorDepth() != _mode.ColorDepth) + { + if (want_fmt) + *want_fmt = GraphicResolution(destination->GetWidth(), destination->GetHeight(), _mode.ColorDepth); + return false; + } + _filter->GetCopyOfScreenIntoBitmap(destination); + return true; +} + +/** + fade.c - High Color Fading Routines + + Last Revision: 21 June, 2002 + + Author: Matthew Leverton +**/ +void ALSoftwareGraphicsDriver::highcolor_fade_in(Bitmap *vs, void(*draw_callback)(), int offx, int offy, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) +{ + Bitmap *bmp_orig = vs; + const int col_depth = bmp_orig->GetColorDepth(); + const int clearColor = makecol_depth(col_depth, targetColourRed, targetColourGreen, targetColourBlue); + if (speed <= 0) speed = 16; + + Bitmap *bmp_buff = new Bitmap(bmp_orig->GetWidth(), bmp_orig->GetHeight(), col_depth); + SetMemoryBackBuffer(bmp_buff); + for (int a = 0; a < 256; a+=speed) + { + bmp_buff->Fill(clearColor); + set_trans_blender(0,0,0,a); + bmp_buff->TransBlendBlt(bmp_orig, 0, 0); + if (draw_callback) + { + draw_callback(); + RenderToBackBuffer(); + } + this->Vsync(); + _filter->RenderScreen(bmp_buff, offx, offy); + if (_pollingCallback) + _pollingCallback(); + WaitForNextFrame(); + } + delete bmp_buff; + + SetMemoryBackBuffer(vs); + if (draw_callback) + { + draw_callback(); + RenderToBackBuffer(); + } + _filter->RenderScreen(vs, offx, offy); +} + +void ALSoftwareGraphicsDriver::highcolor_fade_out(Bitmap *vs, void(*draw_callback)(), int offx, int offy, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) +{ + Bitmap *bmp_orig = vs; + const int col_depth = vs->GetColorDepth(); + const int clearColor = makecol_depth(col_depth, targetColourRed, targetColourGreen, targetColourBlue); + if (speed <= 0) speed = 16; + + Bitmap *bmp_buff = new Bitmap(bmp_orig->GetWidth(), bmp_orig->GetHeight(), col_depth); + SetMemoryBackBuffer(bmp_buff); + for (int a = 255 - speed; a > 0; a -= speed) + { + bmp_buff->Fill(clearColor); + set_trans_blender(0, 0, 0, a); + bmp_buff->TransBlendBlt(bmp_orig, 0, 0); + if (draw_callback) + { + draw_callback(); + RenderToBackBuffer(); + } + this->Vsync(); + _filter->RenderScreen(bmp_buff, offx, offy); + if (_pollingCallback) + _pollingCallback(); + WaitForNextFrame(); + } + delete bmp_buff; + + SetMemoryBackBuffer(vs); + vs->Clear(clearColor); + if (draw_callback) + { + draw_callback(); + RenderToBackBuffer(); + } + _filter->RenderScreen(vs, offx, offy); +} +/** END FADE.C **/ + +// palette fading routiens +// from allegro, modified for mp3 +void initialize_fade_256(int r, int g, int b) { + int a; + for (a = 0; a < 256; a++) { + faded_out_palette[a].r = r / 4; + faded_out_palette[a].g = g / 4; + faded_out_palette[a].b = b / 4; + } +} + +void ALSoftwareGraphicsDriver::__fade_from_range(PALETTE source, PALETTE dest, int speed, int from, int to) +{ + PALETTE temp; + int c; + + for (c=0; c 8) + { + highcolor_fade_out(virtualScreen, _drawPostScreenCallback, 0, 0, speed * 4, targetColourRed, targetColourGreen, targetColourBlue); + } + else + { + __fade_out_range(speed, 0, 255, targetColourRed, targetColourGreen, targetColourBlue); + } +} + +void ALSoftwareGraphicsDriver::FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue) { + if (_drawScreenCallback) + { + _drawScreenCallback(); + RenderToBackBuffer(); + } + if (_mode.ColorDepth > 8) + { + highcolor_fade_in(virtualScreen, _drawPostScreenCallback, 0, 0, speed * 4, targetColourRed, targetColourGreen, targetColourBlue); + } + else + { + initialize_fade_256(targetColourRed, targetColourGreen, targetColourBlue); + __fade_from_range(faded_out_palette, p, speed, 0,255); + } +} + +void ALSoftwareGraphicsDriver::BoxOutEffect(bool blackingOut, int speed, int delay) +{ + if (blackingOut) + { + int yspeed = _srcRect.GetHeight() / (_srcRect.GetWidth() / speed); + int boxwid = speed, boxhit = yspeed; + Bitmap *bmp_orig = virtualScreen; + Bitmap *bmp_buff = new Bitmap(bmp_orig->GetWidth(), bmp_orig->GetHeight(), bmp_orig->GetColorDepth()); + SetMemoryBackBuffer(bmp_buff); + + while (boxwid < _srcRect.GetWidth()) { + boxwid += speed; + boxhit += yspeed; + int vcentre = _srcRect.GetHeight() / 2; + bmp_orig->FillRect(Rect(_srcRect.GetWidth() / 2 - boxwid / 2, vcentre - boxhit / 2, + _srcRect.GetWidth() / 2 + boxwid / 2, vcentre + boxhit / 2), 0); + bmp_buff->Fill(0); + bmp_buff->Blit(bmp_orig); + if (_drawPostScreenCallback) + { + _drawPostScreenCallback(); + RenderToBackBuffer(); + } + this->Vsync(); + _filter->RenderScreen(bmp_buff, 0, 0); + + if (_pollingCallback) + _pollingCallback(); + + platform->Delay(delay); + } + delete bmp_buff; + SetMemoryBackBuffer(bmp_orig); + } + else + { + throw Ali3DException("BoxOut fade-in not implemented in sw gfx driver"); + } +} +// end fading routines + +#ifndef AGS_NO_VIDEO_PLAYER + +bool ALSoftwareGraphicsDriver::PlayVideo(const char *filename, bool useAVISound, VideoSkipType skipType, bool stretchToFullScreen) +{ +#if AGS_PLATFORM_OS_WINDOWS + int result = dxmedia_play_video(filename, useAVISound, skipType, stretchToFullScreen ? 1 : 0); + return (result == 0); +#else + return 0; +#endif +} + +#endif + +// add the alpha values together, used for compositing alpha images +unsigned long _trans_alpha_blender32(unsigned long x, unsigned long y, unsigned long n) +{ + unsigned long res, g; + + n = (n * geta32(x)) / 256; + + if (n) + n++; + + res = ((x & 0xFF00FF) - (y & 0xFF00FF)) * n / 256 + y; + y &= 0xFF00; + x &= 0xFF00; + g = (x - y) * n / 256 + y; + + res &= 0xFF00FF; + g &= 0xFF00; + + return res | g; +} + + +ALSWGraphicsFactory *ALSWGraphicsFactory::_factory = nullptr; + +ALSWGraphicsFactory::~ALSWGraphicsFactory() +{ + _factory = nullptr; +} + +size_t ALSWGraphicsFactory::GetFilterCount() const +{ + return 2; +} + +const GfxFilterInfo *ALSWGraphicsFactory::GetFilterInfo(size_t index) const +{ + switch (index) + { + case 0: + return &AllegroGfxFilter::FilterInfo; + case 1: + return &HqxGfxFilter::FilterInfo; + default: + return nullptr; + } +} + +String ALSWGraphicsFactory::GetDefaultFilterID() const +{ + return AllegroGfxFilter::FilterInfo.Id; +} + +/* static */ ALSWGraphicsFactory *ALSWGraphicsFactory::GetFactory() +{ + if (!_factory) + _factory = new ALSWGraphicsFactory(); + return _factory; +} + +ALSoftwareGraphicsDriver *ALSWGraphicsFactory::EnsureDriverCreated() +{ + if (!_driver) + _driver = new ALSoftwareGraphicsDriver(); + return _driver; +} + +AllegroGfxFilter *ALSWGraphicsFactory::CreateFilter(const String &id) +{ + if (AllegroGfxFilter::FilterInfo.Id.CompareNoCase(id) == 0) + return new AllegroGfxFilter(); + else if (HqxGfxFilter::FilterInfo.Id.CompareNoCase(id) == 0) + return new HqxGfxFilter(); + return nullptr; +} + +} // namespace ALSW +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/gfx/ali3dsw.h b/engines/ags/engine/gfx/ali3dsw.h new file mode 100644 index 00000000000..8cb4f0fb9e0 --- /dev/null +++ b/engines/ags/engine/gfx/ali3dsw.h @@ -0,0 +1,270 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Software graphics factory, based on Allegro +// +//============================================================================= + +#ifndef __AGS_EE_GFX__ALI3DSW_H +#define __AGS_EE_GFX__ALI3DSW_H + +#include + +#include "core/platform.h" +#define AGS_DDRAW_GAMMA_CONTROL (AGS_PLATFORM_OS_WINDOWS) + +#include + +#if AGS_DDRAW_GAMMA_CONTROL +#include +#include +#endif + +#include "gfx/bitmap.h" +#include "gfx/ddb.h" +#include "gfx/gfxdriverfactorybase.h" +#include "gfx/gfxdriverbase.h" + +namespace AGS +{ +namespace Engine +{ +namespace ALSW +{ + +class AllegroGfxFilter; +using AGS::Common::Bitmap; + +class ALSoftwareBitmap : public IDriverDependantBitmap +{ +public: + // NOTE by CJ: + // Transparency is a bit counter-intuitive + // 0=not transparent, 255=invisible, 1..254 barely visible .. mostly visible + void SetTransparency(int transparency) override { _transparency = transparency; } + void SetFlippedLeftRight(bool isFlipped) override { _flipped = isFlipped; } + void SetStretch(int width, int height, bool useResampler = true) override + { + _stretchToWidth = width; + _stretchToHeight = height; + } + int GetWidth() override { return _width; } + int GetHeight() override { return _height; } + int GetColorDepth() override { return _colDepth; } + void SetLightLevel(int lightLevel) override { } + void SetTint(int red, int green, int blue, int tintSaturation) override { } + + Bitmap *_bmp; + int _width, _height; + int _colDepth; + bool _flipped; + int _stretchToWidth, _stretchToHeight; + bool _opaque; // no mask color + bool _hasAlpha; + int _transparency; + + ALSoftwareBitmap(Bitmap *bmp, bool opaque, bool hasAlpha) + { + _bmp = bmp; + _width = bmp->GetWidth(); + _height = bmp->GetHeight(); + _colDepth = bmp->GetColorDepth(); + _flipped = false; + _stretchToWidth = 0; + _stretchToHeight = 0; + _transparency = 0; + _opaque = opaque; + _hasAlpha = hasAlpha; + } + + int GetWidthToRender() { return (_stretchToWidth > 0) ? _stretchToWidth : _width; } + int GetHeightToRender() { return (_stretchToHeight > 0) ? _stretchToHeight : _height; } + + void Dispose() + { + // do we want to free the bitmap? + } + + ~ALSoftwareBitmap() override + { + Dispose(); + } +}; + + +class ALSoftwareGfxModeList : public IGfxModeList +{ +public: + ALSoftwareGfxModeList(GFX_MODE_LIST *alsw_gfx_mode_list) + : _gfxModeList(alsw_gfx_mode_list) + { + } + + int GetModeCount() const override + { + return _gfxModeList ? _gfxModeList->num_modes : 0; + } + + bool GetMode(int index, DisplayMode &mode) const override; + +private: + GFX_MODE_LIST *_gfxModeList; +}; + + +typedef SpriteDrawListEntry ALDrawListEntry; +// Software renderer's sprite batch +struct ALSpriteBatch +{ + // List of sprites to render + std::vector List; + // Intermediate surface which will be drawn upon and transformed if necessary + std::shared_ptr Surface; + // Whether surface is a virtual screen's region + bool IsVirtualScreen; + // Tells whether the surface is treated as opaque or transparent + bool Opaque; +}; +typedef std::vector ALSpriteBatches; + + +class ALSoftwareGraphicsDriver : public GraphicsDriverBase +{ +public: + ALSoftwareGraphicsDriver(); + + const char*GetDriverName() override { return "Software renderer"; } + const char*GetDriverID() override { return "Software"; } + void SetTintMethod(TintMethod method) override; + bool SetDisplayMode(const DisplayMode &mode, volatile int *loopTimer) override; + bool SetNativeSize(const Size &src_size) override; + bool SetRenderFrame(const Rect &dst_rect) override; + bool IsModeSupported(const DisplayMode &mode) override; + int GetDisplayDepthForNativeDepth(int native_color_depth) const override; + IGfxModeList *GetSupportedModeList(int color_depth) override; + PGfxFilter GetGraphicsFilter() const override; + void UnInit(); + // Clears the screen rectangle. The coordinates are expected in the **native game resolution**. + void ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse) override; + int GetCompatibleBitmapFormat(int color_depth) override; + IDriverDependantBitmap* CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) override; + void UpdateDDBFromBitmap(IDriverDependantBitmap* bitmapToUpdate, Bitmap *bitmap, bool hasAlpha) override; + void DestroyDDB(IDriverDependantBitmap* bitmap) override; + + void DrawSprite(int x, int y, IDriverDependantBitmap* bitmap) override; + void SetScreenFade(int red, int green, int blue) override; + void SetScreenTint(int red, int green, int blue) override; + + void RenderToBackBuffer() override; + void Render() override; + void Render(int xoff, int yoff, GlobalFlipType flip) override; + bool GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt) override; + void FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) override; + void FadeIn(int speed, PALETTE pal, int targetColourRed, int targetColourGreen, int targetColourBlue) override; + void BoxOutEffect(bool blackingOut, int speed, int delay) override; +#ifndef AGS_NO_VIDEO_PLAYER + bool PlayVideo(const char *filename, bool useAVISound, VideoSkipType skipType, bool stretchToFullScreen) override; +#endif + bool SupportsGammaControl() override ; + void SetGamma(int newGamma) override; + void UseSmoothScaling(bool enabled) override { } + void EnableVsyncBeforeRender(bool enabled) override { _autoVsync = enabled; } + void Vsync() override; + void RenderSpritesAtScreenResolution(bool enabled, int supersampling) override { } + bool RequiresFullRedrawEachFrame() override { return false; } + bool HasAcceleratedTransform() override { return false; } + bool UsesMemoryBackBuffer() override { return true; } + Bitmap *GetMemoryBackBuffer() override; + void SetMemoryBackBuffer(Bitmap *backBuffer) override; + Bitmap *GetStageBackBuffer() override; + ~ALSoftwareGraphicsDriver() override; + + typedef std::shared_ptr PALSWFilter; + + void SetGraphicsFilter(PALSWFilter filter); + +private: + PALSWFilter _filter; + + bool _autoVsync; + Bitmap *_allegroScreenWrapper; + // Virtual screen bitmap is either a wrapper over Allegro's real screen + // bitmap, or bitmap provided by the graphics filter. It should not be + // disposed by the renderer: it is up to filter object to manage it. + Bitmap *_origVirtualScreen; + // Current virtual screen bitmap; may be provided either by graphics + // filter or by external user. It should not be disposed by the renderer. + Bitmap *virtualScreen; + // Stage screen meant for particular rendering stages, may be referencing + // actual virtual screen or separate bitmap of different size that is + // blitted to virtual screen at the stage finalization. + Bitmap *_stageVirtualScreen; + //Bitmap *_spareTintingScreen; + int _tint_red, _tint_green, _tint_blue; + + ALSpriteBatches _spriteBatches; + GFX_MODE_LIST *_gfxModeList; + +#if AGS_DDRAW_GAMMA_CONTROL + IDirectDrawGammaControl* dxGammaControl; + // The gamma ramp is a lookup table for each possible R, G and B value + // in 32-bit colour (from 0-255) it maps them to a brightness value + // from 0-65535. The default gamma ramp just multiplies each value by 256 + DDGAMMARAMP gammaRamp; + DDGAMMARAMP defaultGammaRamp; + DDCAPS ddrawCaps; +#endif + + void InitSpriteBatch(size_t index, const SpriteBatchDesc &desc) override; + void ResetAllBatches() override; + + // Use gfx filter to create a new virtual screen + void CreateVirtualScreen(); + void DestroyVirtualScreen(); + // Unset parameters and release resources related to the display mode + void ReleaseDisplayMode(); + // Renders single sprite batch on the precreated surface + void RenderSpriteBatch(const ALSpriteBatch &batch, Common::Bitmap *surface, int surf_offx, int surf_offy); + + void highcolor_fade_in(Bitmap *vs, void(*draw_callback)(), int offx, int offy, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue); + void highcolor_fade_out(Bitmap *vs, void(*draw_callback)(), int offx, int offy, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue); + void __fade_from_range(PALETTE source, PALETTE dest, int speed, int from, int to) ; + void __fade_out_range(int speed, int from, int to, int targetColourRed, int targetColourGreen, int targetColourBlue) ; + int GetAllegroGfxDriverID(bool windowed); +}; + + +class ALSWGraphicsFactory : public GfxDriverFactoryBase +{ +public: + ~ALSWGraphicsFactory() override; + + size_t GetFilterCount() const override; + const GfxFilterInfo *GetFilterInfo(size_t index) const override; + String GetDefaultFilterID() const override; + + static ALSWGraphicsFactory *GetFactory(); + +private: + ALSoftwareGraphicsDriver *EnsureDriverCreated() override; + AllegroGfxFilter *CreateFilter(const String &id) override; + + static ALSWGraphicsFactory *_factory; +}; + +} // namespace ALSW +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__ALI3DSW_H diff --git a/engines/ags/engine/gfx/blender.cpp b/engines/ags/engine/gfx/blender.cpp new file mode 100644 index 00000000000..18a48cf6c10 --- /dev/null +++ b/engines/ags/engine/gfx/blender.cpp @@ -0,0 +1,300 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/types.h" +#include "gfx/blender.h" +#include "util/wgt2allg.h" + +extern "C" { + // Fallback routine for when we don't have anything better to do. + unsigned long _blender_black(unsigned long x, unsigned long y, unsigned long n); + // Standard Allegro 4 trans blenders for 16 and 15-bit color modes + unsigned long _blender_trans15(unsigned long x, unsigned long y, unsigned long n); + unsigned long _blender_trans16(unsigned long x, unsigned long y, unsigned long n); + // Standard Allegro 4 alpha blenders for 16 and 15-bit color modes + unsigned long _blender_alpha15(unsigned long x, unsigned long y, unsigned long n); + unsigned long _blender_alpha16(unsigned long x, unsigned long y, unsigned long n); + unsigned long _blender_alpha24(unsigned long x, unsigned long y, unsigned long n); +} + +// the allegro "inline" ones are not actually inline, so #define +// over them to speed it up +#define getr32(xx) ((xx >> _rgb_r_shift_32) & 0xFF) +#define getg32(xx) ((xx >> _rgb_g_shift_32) & 0xFF) +#define getb32(xx) ((xx >> _rgb_b_shift_32) & 0xFF) +#define geta32(xx) ((xx >> _rgb_a_shift_32) & 0xFF) +#define makeacol32(r,g,b,a) ((r << _rgb_r_shift_32) | (g << _rgb_g_shift_32) | (b << _rgb_b_shift_32) | (a << _rgb_a_shift_32)) + +// Take hue and saturation of blend colour, luminance of image +unsigned long _myblender_color15_light(unsigned long x, unsigned long y, unsigned long n) +{ + float xh, xs, xv; + float yh, ys, yv; + int r, g, b; + + rgb_to_hsv(getr15(x), getg15(x), getb15(x), &xh, &xs, &xv); + rgb_to_hsv(getr15(y), getg15(y), getb15(y), &yh, &ys, &yv); + + // adjust luminance + yv -= (1.0 - ((float)n / 250.0)); + if (yv < 0.0) yv = 0.0; + + hsv_to_rgb(xh, xs, yv, &r, &g, &b); + + return makecol15(r, g, b); +} + +// Take hue and saturation of blend colour, luminance of image +// n is the last parameter passed to draw_lit_sprite +unsigned long _myblender_color16_light(unsigned long x, unsigned long y, unsigned long n) +{ + float xh, xs, xv; + float yh, ys, yv; + int r, g, b; + + rgb_to_hsv(getr16(x), getg16(x), getb16(x), &xh, &xs, &xv); + rgb_to_hsv(getr16(y), getg16(y), getb16(y), &yh, &ys, &yv); + + // adjust luminance + yv -= (1.0 - ((float)n / 250.0)); + if (yv < 0.0) yv = 0.0; + + hsv_to_rgb(xh, xs, yv, &r, &g, &b); + + return makecol16(r, g, b); +} + +// Take hue and saturation of blend colour, luminance of image +unsigned long _myblender_color32_light(unsigned long x, unsigned long y, unsigned long n) +{ + float xh, xs, xv; + float yh, ys, yv; + int r, g, b; + + rgb_to_hsv(getr32(x), getg32(x), getb32(x), &xh, &xs, &xv); + rgb_to_hsv(getr32(y), getg32(y), getb32(y), &yh, &ys, &yv); + + // adjust luminance + yv -= (1.0 - ((float)n / 250.0)); + if (yv < 0.0) yv = 0.0; + + hsv_to_rgb(xh, xs, yv, &r, &g, &b); + + return makeacol32(r, g, b, geta32(y)); +} + +// Take hue and saturation of blend colour, luminance of image +unsigned long _myblender_color15(unsigned long x, unsigned long y, unsigned long n) +{ + float xh, xs, xv; + float yh, ys, yv; + int r, g, b; + + rgb_to_hsv(getr15(x), getg15(x), getb15(x), &xh, &xs, &xv); + rgb_to_hsv(getr15(y), getg15(y), getb15(y), &yh, &ys, &yv); + + hsv_to_rgb(xh, xs, yv, &r, &g, &b); + + return makecol15(r, g, b); +} + +// Take hue and saturation of blend colour, luminance of image +unsigned long _myblender_color16(unsigned long x, unsigned long y, unsigned long n) +{ + float xh, xs, xv; + float yh, ys, yv; + int r, g, b; + + rgb_to_hsv(getr16(x), getg16(x), getb16(x), &xh, &xs, &xv); + rgb_to_hsv(getr16(y), getg16(y), getb16(y), &yh, &ys, &yv); + + hsv_to_rgb(xh, xs, yv, &r, &g, &b); + + return makecol16(r, g, b); +} + +// Take hue and saturation of blend colour, luminance of image +unsigned long _myblender_color32(unsigned long x, unsigned long y, unsigned long n) +{ + float xh, xs, xv; + float yh, ys, yv; + int r, g, b; + + rgb_to_hsv(getr32(x), getg32(x), getb32(x), &xh, &xs, &xv); + rgb_to_hsv(getr32(y), getg32(y), getb32(y), &yh, &ys, &yv); + + hsv_to_rgb(xh, xs, yv, &r, &g, &b); + + return makeacol32(r, g, b, geta32(y)); +} + +// trans24 blender, but preserve alpha channel from image +unsigned long _myblender_alpha_trans24(unsigned long x, unsigned long y, unsigned long n) +{ + unsigned long res, g, alph; + + if (n) + n++; + + alph = y & 0xff000000; + y &= 0x00ffffff; + + res = ((x & 0xFF00FF) - (y & 0xFF00FF)) * n / 256 + y; + y &= 0xFF00; + x &= 0xFF00; + g = (x - y) * n / 256 + y; + + res &= 0xFF00FF; + g &= 0xFF00; + + return res | g | alph; +} + +void set_my_trans_blender(int r, int g, int b, int a) +{ + // use standard allegro 15 and 16 bit blenders, but customize + // the 32-bit one to preserve the alpha channel + set_blender_mode(_blender_trans15, _blender_trans16, _myblender_alpha_trans24, r, g, b, a); +} + +// plain copy source to destination +// assign new alpha value as a summ of alphas. +unsigned long _additive_alpha_copysrc_blender(unsigned long x, unsigned long y, unsigned long n) +{ + unsigned long newAlpha = ((x & 0xff000000) >> 24) + ((y & 0xff000000) >> 24); + + if (newAlpha > 0xff) newAlpha = 0xff; + + return (newAlpha << 24) | (x & 0x00ffffff); +} + +FORCEINLINE unsigned long argb2argb_blend_core(unsigned long src_col, unsigned long dst_col, unsigned long src_alpha) +{ + unsigned long dst_g, dst_alpha; + src_alpha++; + dst_alpha = geta32(dst_col); + if (dst_alpha) + dst_alpha++; + + // dst_g now contains the green hue from destination color + dst_g = (dst_col & 0x00FF00) * dst_alpha / 256; + // dst_col now contains the red & blue hues from destination color + dst_col = (dst_col & 0xFF00FF) * dst_alpha / 256; + + // res_g now contains the green hue of the pre-final color + dst_g = (((src_col & 0x00FF00) - (dst_g & 0x00FF00)) * src_alpha / 256 + dst_g) & 0x00FF00; + // res_rb now contains the red & blue hues of the pre-final color + dst_col = (((src_col & 0xFF00FF) - (dst_col & 0xFF00FF)) * src_alpha / 256 + dst_col) & 0xFF00FF; + + // dst_alpha now contains the final alpha + // we assume that final alpha will never be zero + dst_alpha = 256 - (256 - src_alpha) * (256 - dst_alpha) / 256; + // src_alpha is now the final alpha factor made for being multiplied by, + // instead of divided by: this makes it possible to use it in faster + // calculation below + src_alpha = /* 256 * 256 == */ 0x10000 / dst_alpha; + + // setting up final color hues + dst_g = (dst_g * src_alpha / 256) & 0x00FF00; + dst_col = (dst_col * src_alpha / 256) & 0xFF00FF; + return dst_col | dst_g | (--dst_alpha << 24); +} + +// blend source to destination with respect to source and destination alphas; +// assign new alpha value as a multiplication of translucenses. +// combined_alpha = front.alpha + back.alpha * (1 - front.alpha); +// combined_rgb = (front.rgb * front.alpha + back.rgb * (1 - front.alpha) * back.alpha) / combined_alpha; +unsigned long _argb2argb_blender(unsigned long src_col, unsigned long dst_col, unsigned long src_alpha) +{ + if (src_alpha > 0) + src_alpha = geta32(src_col) * ((src_alpha & 0xFF) + 1) / 256; + else + src_alpha = geta32(src_col); + if (src_alpha == 0) + return dst_col; + return argb2argb_blend_core(src_col, dst_col, src_alpha); +} + +unsigned long _rgb2argb_blender(unsigned long src_col, unsigned long dst_col, unsigned long src_alpha) +{ + if (src_alpha == 0 || src_alpha == 0xFF) + return src_col | 0xFF000000; + return argb2argb_blend_core(src_col | 0xFF000000, dst_col, src_alpha); +} + +unsigned long _argb2rgb_blender(unsigned long src_col, unsigned long dst_col, unsigned long src_alpha) +{ + unsigned long res, g; + + if (src_alpha > 0) + src_alpha = geta32(src_col) * ((src_alpha & 0xFF) + 1) / 256; + else + src_alpha = geta32(src_col); + if (src_alpha) + src_alpha++; + + res = ((src_col & 0xFF00FF) - (dst_col & 0xFF00FF)) * src_alpha / 256 + dst_col; + dst_col &= 0xFF00; + src_col &= 0xFF00; + g = (src_col - dst_col) * src_alpha / 256 + dst_col; + + res &= 0xFF00FF; + g &= 0xFF00; + + return res | g; +} + +// Based on _blender_alpha16, but keep source pixel if dest is transparent +unsigned long skiptranspixels_blender_alpha16(unsigned long x, unsigned long y, unsigned long n) +{ + unsigned long result; + if ((y & 0xFFFF) == 0xF81F) + return x; + n = geta32(x); + if (n) + n = (n + 1) / 8; + x = makecol16(getr32(x), getg32(x), getb32(x)); + x = (x | (x << 16)) & 0x7E0F81F; + y = ((y & 0xFFFF) | (y << 16)) & 0x7E0F81F; + result = ((x - y) * n / 32 + y) & 0x7E0F81F; + return ((result & 0xFFFF) | (result >> 16)); +} + +void set_additive_alpha_blender() +{ + set_blender_mode(nullptr, nullptr, _additive_alpha_copysrc_blender, 0, 0, 0, 0); +} + +void set_argb2argb_blender(int alpha) +{ + set_blender_mode(nullptr, nullptr, _argb2argb_blender, 0, 0, 0, alpha); +} + +// sets the alpha channel to opaque. used when drawing a non-alpha sprite onto an alpha-sprite +unsigned long _opaque_alpha_blender(unsigned long x, unsigned long y, unsigned long n) +{ + return x | 0xff000000; +} + +void set_opaque_alpha_blender() +{ + set_blender_mode(nullptr, nullptr, _opaque_alpha_blender, 0, 0, 0, 0); +} + +void set_argb2any_blender() +{ + set_blender_mode_ex(_blender_black, _blender_black, _blender_black, _argb2argb_blender, + _blender_alpha15, skiptranspixels_blender_alpha16, _blender_alpha24, + 0, 0, 0, 0xff); // TODO: do we need to support proper 15- and 24-bit here? +} diff --git a/engines/ags/engine/gfx/blender.h b/engines/ags/engine/gfx/blender.h new file mode 100644 index 00000000000..fcefa935eb7 --- /dev/null +++ b/engines/ags/engine/gfx/blender.h @@ -0,0 +1,65 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AGS specific color blending routines for transparency and tinting effects +// +//============================================================================= + +#ifndef __AC_BLENDER_H +#define __AC_BLENDER_H + +// +// Allegro's standard alpha blenders result in: +// - src and dst RGB are combined proportionally to src alpha +// (src.rgb * src.alpha + dst.rgb * (1 - dst.alpha)); +// - final alpha is zero. +// This blender is suggested for use with opaque destinations +// (ones without alpha channel). +// +/* Declared in Allegro's color.h: +void set_alpha_blender(); +*/ + +unsigned long _myblender_color15(unsigned long x, unsigned long y, unsigned long n); +unsigned long _myblender_color16(unsigned long x, unsigned long y, unsigned long n); +unsigned long _myblender_color32(unsigned long x, unsigned long y, unsigned long n); +unsigned long _myblender_color15_light(unsigned long x, unsigned long y, unsigned long n); +unsigned long _myblender_color16_light(unsigned long x, unsigned long y, unsigned long n); +unsigned long _myblender_color32_light(unsigned long x, unsigned long y, unsigned long n); +// Customizable alpha blender that uses the supplied alpha value as src alpha, +// and preserves destination's alpha channel (if there was one); +void set_my_trans_blender(int r, int g, int b, int a); +// Argb2argb alpha blender combines RGBs proportionally to src alpha, but also +// applies dst alpha factor to the dst RGB used in the merge; +// The final alpha is calculated by multiplying two translucences (1 - .alpha). +// Custom alpha parameter, when not zero, is treated as fraction of source +// alpha that has to be used in color blending. +unsigned long _argb2argb_blender(unsigned long src_col, unsigned long dst_col, unsigned long src_alpha); +// Argb2rgb blender combines RGBs proportionally to src alpha, but discards alpha in the end. +// It is almost a clone of Allegro's _blender_alpha32, except it also applies optional overall alpha. +unsigned long _argb2rgb_blender(unsigned long src_col, unsigned long dst_col, unsigned long src_alpha); +// Rgb2argb blender treats all src pixels as if having opaque alpha. +unsigned long _rgb2argb_blender(unsigned long src_col, unsigned long dst_col, unsigned long src_alpha); +// Sets the alpha channel to opaque. Used when drawing a non-alpha sprite onto an alpha-sprite. +unsigned long _opaque_alpha_blender(unsigned long src_col, unsigned long dst_col, unsigned long src_alpha); + +// Additive alpha blender plain copies src over, applying a summ of src and +// dst alpha values. +void set_additive_alpha_blender(); +// Opaque alpha blender plain copies src over, applying opaque alpha value. +void set_opaque_alpha_blender(); +// Sets argb2argb for 32-bit mode, and provides appropriate funcs for blending 32-bit onto 15/16/24-bit destination +void set_argb2any_blender(); + +#endif // __AC_BLENDER_H diff --git a/engines/ags/engine/gfx/color_engine.cpp b/engines/ags/engine/gfx/color_engine.cpp new file mode 100644 index 00000000000..4946b2b4bf5 --- /dev/null +++ b/engines/ags/engine/gfx/color_engine.cpp @@ -0,0 +1,59 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Implementation from wgt2allg.cpp specific to Engine runtime +// +//============================================================================= + +#include "core/platform.h" + +#include "util/wgt2allg.h" +#include "gfx/bitmap.h" + +void __my_setcolor(int *ctset, int newcol, int wantColDep) + { + if (wantColDep == 8) + ctset[0] = newcol; + else if (newcol & 0x40000000) // already calculated it + ctset[0] = newcol; + else if ((newcol >= 32) && (wantColDep > 16)) { + // true-color +#ifdef SWAP_RB_HICOL_FOR_32to24_32 + ctset[0] = makeacol32(getb16(newcol), getg16(newcol), getr16(newcol), 255); +#else + ctset[0] = makeacol32(getr16(newcol), getg16(newcol), getb16(newcol), 255); +#endif + } + else if (newcol >= 32) { + + // If it's 15-bit, convert the color + if (wantColDep == 15) + ctset[0] = (newcol & 0x001f) | ((newcol >> 1) & 0x7fe0); + else + ctset[0] = newcol; + } + else + { + ctset[0] = makecol_depth(wantColDep, col_lookups[newcol] >> 16, + (col_lookups[newcol] >> 8) & 0x000ff, col_lookups[newcol] & 0x000ff); + + // in case it's used on an alpha-channel sprite, make sure it's visible + if (wantColDep > 16) + ctset[0] |= 0xff000000; + } + + // if it's 32-bit color, signify that the colour has been calculated + //if (wantColDep >= 24) +// ctset[0] |= 0x40000000; + } diff --git a/engines/ags/engine/gfx/ddb.h b/engines/ags/engine/gfx/ddb.h new file mode 100644 index 00000000000..553b36f17d0 --- /dev/null +++ b/engines/ags/engine/gfx/ddb.h @@ -0,0 +1,45 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Driver-dependant bitmap interface +// +//============================================================================= +#ifndef __AGS_EE_GFX__DDB_H +#define __AGS_EE_GFX__DDB_H + +namespace AGS +{ +namespace Engine +{ + +class IDriverDependantBitmap +{ +public: + virtual ~IDriverDependantBitmap() = default; + + virtual void SetTransparency(int transparency) = 0; // 0-255 + virtual void SetFlippedLeftRight(bool isFlipped) = 0; + virtual void SetStretch(int width, int height, bool useResampler = true) = 0; + virtual void SetLightLevel(int light_level) = 0; // 0-255 + virtual void SetTint(int red, int green, int blue, int tintSaturation) = 0; // 0-255 + + virtual int GetWidth() = 0; + virtual int GetHeight() = 0; + virtual int GetColorDepth() = 0; +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__DDB_H diff --git a/engines/ags/engine/gfx/gfx_util.cpp b/engines/ags/engine/gfx/gfx_util.cpp new file mode 100644 index 00000000000..04b76d0b803 --- /dev/null +++ b/engines/ags/engine/gfx/gfx_util.cpp @@ -0,0 +1,190 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" +#include "gfx/gfx_util.h" +#include "gfx/blender.h" + +// CHECKME: is this hack still relevant? +#if AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_ANDROID +extern int psp_gfx_renderer; +#endif + +namespace AGS +{ +namespace Engine +{ + +using namespace Common; + +namespace GfxUtil +{ + +Bitmap *ConvertBitmap(Bitmap *src, int dst_color_depth) +{ + int src_col_depth = src->GetColorDepth(); + if (src_col_depth != dst_color_depth) + { + int old_conv = get_color_conversion(); + // TODO: find out what is this, and why do we need to call this every time (do we?) + set_color_conversion(COLORCONV_KEEP_TRANS | COLORCONV_TOTAL); + Bitmap *dst = BitmapHelper::CreateBitmapCopy(src, dst_color_depth); + set_color_conversion(old_conv); + return dst; + } + return src; +} + + +typedef BLENDER_FUNC PfnBlenderCb; + +struct BlendModeSetter +{ + // Blender setter for destination with and without alpha channel; + // assign null pointer if not supported + PfnBlenderCb AllAlpha; // src w alpha -> dst w alpha + PfnBlenderCb AlphaToOpaque; // src w alpha -> dst w/o alpha + PfnBlenderCb OpaqueToAlpha; // src w/o alpha -> dst w alpha + PfnBlenderCb OpaqueToAlphaNoTrans; // src w/o alpha -> dst w alpha (opt-ed for no transparency) + PfnBlenderCb AllOpaque; // src w/o alpha -> dst w/o alpha +}; + +// Array of blender descriptions +// NOTE: set NULL function pointer to fallback to common image blitting +static const BlendModeSetter BlendModeSets[kNumBlendModes] = +{ + { nullptr, nullptr, nullptr, nullptr, nullptr }, // kBlendMode_NoAlpha + { _argb2argb_blender, _argb2rgb_blender, _rgb2argb_blender, _opaque_alpha_blender, nullptr }, // kBlendMode_Alpha + // NOTE: add new modes here +}; + +bool SetBlender(BlendMode blend_mode, bool dst_has_alpha, bool src_has_alpha, int blend_alpha) +{ + if (blend_mode < 0 || blend_mode > kNumBlendModes) + return false; + const BlendModeSetter &set = BlendModeSets[blend_mode]; + PfnBlenderCb blender; + if (dst_has_alpha) + blender = src_has_alpha ? set.AllAlpha : + (blend_alpha == 0xFF ? set.OpaqueToAlphaNoTrans : set.OpaqueToAlpha); + else + blender = src_has_alpha ? set.AlphaToOpaque : set.AllOpaque; + + if (blender) + { + set_blender_mode(nullptr, nullptr, blender, 0, 0, 0, blend_alpha); + return true; + } + return false; +} + +void DrawSpriteBlend(Bitmap *ds, const Point &ds_at, Bitmap *sprite, + BlendMode blend_mode, bool dst_has_alpha, bool src_has_alpha, int blend_alpha) +{ + if (blend_alpha <= 0) + return; // do not draw 100% transparent image + + if (// support only 32-bit blending at the moment + ds->GetColorDepth() == 32 && sprite->GetColorDepth() == 32 && + // set blenders if applicable and tell if succeeded + SetBlender(blend_mode, dst_has_alpha, src_has_alpha, blend_alpha)) + { + ds->TransBlendBlt(sprite, ds_at.X, ds_at.Y); + } + else + { + GfxUtil::DrawSpriteWithTransparency(ds, sprite, ds_at.X, ds_at.Y, blend_alpha); + } +} + +void DrawSpriteWithTransparency(Bitmap *ds, Bitmap *sprite, int x, int y, int alpha) +{ + if (alpha <= 0) + { + // fully transparent, don't draw it at all + return; + } + + int surface_depth = ds->GetColorDepth(); + int sprite_depth = sprite->GetColorDepth(); + + if (sprite_depth < surface_depth + // CHECKME: what is the purpose of this hack and is this still relevant? +#if AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_ANDROID + || (ds->GetBPP() < surface_depth && psp_gfx_renderer > 0) // Fix for corrupted speechbox outlines with the OGL driver +#endif + ) + { + // If sprite is lower color depth than destination surface, e.g. + // 8-bit sprites drawn on 16/32-bit surfaces. + if (sprite_depth == 8 && surface_depth >= 24) + { + // 256-col sprite -> truecolor background + // this is automatically supported by allegro, no twiddling needed + ds->Blit(sprite, x, y, kBitmap_Transparency); + return; + } + + // 256-col sprite -> hi-color background, or + // 16-bit sprite -> 32-bit background + Bitmap hctemp; + hctemp.CreateCopy(sprite, surface_depth); + if (sprite_depth == 8) + { + // only do this for 256-col -> hi-color, cos the Blit call converts + // transparency for 16->32 bit + color_t mask_color = hctemp.GetMaskColor(); + for (int scan_y = 0; scan_y < hctemp.GetHeight(); ++scan_y) + { + // we know this must be 1 bpp source and 2 bpp pixel destination + const uint8_t *src_scanline = sprite->GetScanLine(scan_y); + uint16_t *dst_scanline = (uint16_t*)hctemp.GetScanLineForWriting(scan_y); + for (int scan_x = 0; scan_x < hctemp.GetWidth(); ++scan_x) + { + if (src_scanline[scan_x] == 0) + { + dst_scanline[scan_x] = mask_color; + } + } + } + } + + if (alpha < 0xFF) + { + set_trans_blender(0, 0, 0, alpha); + ds->TransBlendBlt(&hctemp, x, y); + } + else + { + ds->Blit(&hctemp, x, y, kBitmap_Transparency); + } + } + else + { + if (alpha < 0xFF && surface_depth > 8 && sprite_depth > 8) + { + set_trans_blender(0, 0, 0, alpha); + ds->TransBlendBlt(sprite, x, y); + } + else + { + ds->Blit(sprite, x, y, kBitmap_Transparency); + } + } +} + +} // namespace GfxUtil + +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/gfx/gfx_util.h b/engines/ags/engine/gfx/gfx_util.h new file mode 100644 index 00000000000..924e3cc755d --- /dev/null +++ b/engines/ags/engine/gfx/gfx_util.h @@ -0,0 +1,59 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Intermediate level drawing utility functions. +// +// GfxUtil namespace is meant for intermediate-to-lower level functions, that +// implement specific conversions, tricks and hacks for drawing bitmaps and +// geometry. +// The suggested convention is to add only those functions, that do not require +// any knowledge of higher-level engine types and objects. +// +//============================================================================= +#ifndef __AGS_EE_GFX__GFXUTIL_H +#define __AGS_EE_GFX__GFXUTIL_H + +#include "gfx/bitmap.h" +#include "gfx/gfx_def.h" + +namespace AGS +{ +namespace Engine +{ + +using Common::Bitmap; + +namespace GfxUtil +{ + // Creates a COPY of the source bitmap, converted to the given format. + Bitmap *ConvertBitmap(Bitmap *src, int dst_color_depth); + + // Considers the given information about source and destination surfaces, + // then draws a bimtap over another either using requested blending mode, + // or fallbacks to common "magic pink" transparency mode; + // optionally uses blending alpha (overall image transparency). + void DrawSpriteBlend(Bitmap *ds, const Point &ds_at, Bitmap *sprite, + Common::BlendMode blend_mode, bool dst_has_alpha = true, bool src_has_alpha = true, int blend_alpha = 0xFF); + + // Draws a bitmap over another one with given alpha level (0 - 255), + // takes account of the bitmap's mask color, + // ignores image's alpha channel, even if there's one; + // does proper conversion depending on respected color depths. + void DrawSpriteWithTransparency(Bitmap *ds, Bitmap *sprite, int x, int y, int alpha = 0xFF); +} // namespace GfxUtil + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__GFXUTIL_H diff --git a/engines/ags/engine/gfx/gfxdefines.h b/engines/ags/engine/gfx/gfxdefines.h new file mode 100644 index 00000000000..0efa99e0750 --- /dev/null +++ b/engines/ags/engine/gfx/gfxdefines.h @@ -0,0 +1,82 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_GFX__GFXDEFINES_H +#define __AGS_EE_GFX__GFXDEFINES_H + +#include "core/types.h" + +namespace AGS +{ +namespace Engine +{ + +// TODO: find the way to merge this with sprite batch transform +enum GlobalFlipType +{ + kFlip_None, + kFlip_Horizontal, // this means - mirror over horizontal middle line + kFlip_Vertical, // this means - mirror over vertical middle line + kFlip_Both +}; + +// GraphicResolution struct determines image size and color depth +struct GraphicResolution +{ + int32_t Width; + int32_t Height; + int32_t ColorDepth; + + GraphicResolution() + : Width(0) + , Height(0) + , ColorDepth(0) + { + } + + GraphicResolution(int32_t width, int32_t height, int32_t color_depth) + { + Width = width; + Height = height; + ColorDepth = color_depth; + } + + inline bool IsValid() const { return Width > 0 && Height > 0 && ColorDepth > 0; } +}; + +// DisplayMode struct provides extended description of display mode +struct DisplayMode : public GraphicResolution +{ + int32_t RefreshRate; + bool Vsync; + bool Windowed; + + DisplayMode() + : RefreshRate(0) + , Vsync(false) + , Windowed(false) + {} + + DisplayMode(const GraphicResolution &res, bool windowed = false, int32_t refresh = 0, bool vsync = false) + : GraphicResolution(res) + , RefreshRate(refresh) + , Vsync(vsync) + , Windowed(windowed) + {} +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__GFXDEFINES_H diff --git a/engines/ags/engine/gfx/gfxdriverbase.cpp b/engines/ags/engine/gfx/gfxdriverbase.cpp new file mode 100644 index 00000000000..737d6a5071b --- /dev/null +++ b/engines/ags/engine/gfx/gfxdriverbase.cpp @@ -0,0 +1,498 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/wgt2allg.h" +#include "gfx/ali3dexception.h" +#include "gfx/bitmap.h" +#include "gfx/gfxfilter.h" +#include "gfx/gfxdriverbase.h" +#include "gfx/gfx_util.h" + +using namespace AGS::Common; + +namespace AGS +{ +namespace Engine +{ + +GraphicsDriverBase::GraphicsDriverBase() + : _pollingCallback(nullptr) + , _drawScreenCallback(nullptr) + , _nullSpriteCallback(nullptr) + , _initGfxCallback(nullptr) + , _initSurfaceUpdateCallback(nullptr) +{ + // Initialize default sprite batch, it will be used when no other batch was activated + _actSpriteBatch = 0; + _spriteBatchDesc.push_back(SpriteBatchDesc()); +} + +bool GraphicsDriverBase::IsModeSet() const +{ + return _mode.Width != 0 && _mode.Height != 0 && _mode.ColorDepth != 0; +} + +bool GraphicsDriverBase::IsNativeSizeValid() const +{ + return !_srcRect.IsEmpty(); +} + +bool GraphicsDriverBase::IsRenderFrameValid() const +{ + return !_srcRect.IsEmpty() && !_dstRect.IsEmpty(); +} + +DisplayMode GraphicsDriverBase::GetDisplayMode() const +{ + return _mode; +} + +Size GraphicsDriverBase::GetNativeSize() const +{ + return _srcRect.GetSize(); +} + +Rect GraphicsDriverBase::GetRenderDestination() const +{ + return _dstRect; +} + +void GraphicsDriverBase::BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform, + const Point offset, GlobalFlipType flip, PBitmap surface) +{ + _actSpriteBatch++; + _spriteBatchDesc.push_back(SpriteBatchDesc(viewport, transform, offset, flip, surface)); + InitSpriteBatch(_actSpriteBatch, _spriteBatchDesc[_actSpriteBatch]); +} + +void GraphicsDriverBase::ClearDrawLists() +{ + ResetAllBatches(); + _actSpriteBatch = 0; + _spriteBatchDesc.resize(1); +} + +void GraphicsDriverBase::OnInit(volatile int *loopTimer) +{ +} + +void GraphicsDriverBase::OnUnInit() +{ +} + +void GraphicsDriverBase::OnModeSet(const DisplayMode &mode) +{ + _mode = mode; +} + +void GraphicsDriverBase::OnModeReleased() +{ + _mode = DisplayMode(); + _dstRect = Rect(); +} + +void GraphicsDriverBase::OnScalingChanged() +{ + PGfxFilter filter = GetGraphicsFilter(); + if (filter) + _filterRect = filter->SetTranslation(_srcRect.GetSize(), _dstRect); + else + _filterRect = Rect(); + _scaling.Init(_srcRect.GetSize(), _dstRect); +} + +void GraphicsDriverBase::OnSetNativeSize(const Size &src_size) +{ + _srcRect = RectWH(0, 0, src_size.Width, src_size.Height); + OnScalingChanged(); + + // Adjust default sprite batch making it comply to native size + _spriteBatchDesc[0].Viewport = RectWH(src_size); + InitSpriteBatch(_actSpriteBatch, _spriteBatchDesc[_actSpriteBatch]); +} + +void GraphicsDriverBase::OnSetRenderFrame(const Rect &dst_rect) +{ + _dstRect = dst_rect; + OnScalingChanged(); +} + +void GraphicsDriverBase::OnSetFilter() +{ + _filterRect = GetGraphicsFilter()->SetTranslation(Size(_srcRect.GetSize()), _dstRect); +} + + +VideoMemoryGraphicsDriver::VideoMemoryGraphicsDriver() + : _stageVirtualScreenDDB(nullptr) + , _stageScreenDirty(false) + , _fxIndex(0) +{ + // Only to have something meaningful as default + _vmem_a_shift_32 = 24; + _vmem_r_shift_32 = 16; + _vmem_g_shift_32 = 8; + _vmem_b_shift_32 = 0; +} + +VideoMemoryGraphicsDriver::~VideoMemoryGraphicsDriver() +{ + DestroyAllStageScreens(); +} + +bool VideoMemoryGraphicsDriver::UsesMemoryBackBuffer() +{ + // Although we do use ours, we do not let engine draw upon it; + // only plugin handling are allowed to request our mem buffer. + // TODO: find better workaround? + return false; +} + +Bitmap *VideoMemoryGraphicsDriver::GetMemoryBackBuffer() +{ + return nullptr; +} + +void VideoMemoryGraphicsDriver::SetMemoryBackBuffer(Bitmap *backBuffer) +{ // do nothing, video-memory drivers don't use main back buffer, only stage bitmaps they pass to plugins +} + +Bitmap *VideoMemoryGraphicsDriver::GetStageBackBuffer() +{ + _stageScreenDirty = true; + return _stageVirtualScreen.get(); +} + +PBitmap VideoMemoryGraphicsDriver::CreateStageScreen(size_t index, const Size &sz) +{ + if (_stageScreens.size() <= index) + _stageScreens.resize(index + 1); + if (sz.IsNull()) + _stageScreens[index].reset(); + else if (_stageScreens[index] == nullptr || _stageScreens[index]->GetSize() != sz) + _stageScreens[index].reset(new Bitmap(sz.Width, sz.Height, _mode.ColorDepth)); + return _stageScreens[index]; +} + +PBitmap VideoMemoryGraphicsDriver::GetStageScreen(size_t index) +{ + if (index < _stageScreens.size()) + return _stageScreens[index]; + return nullptr; +} + +void VideoMemoryGraphicsDriver::DestroyAllStageScreens() +{ + if (_stageVirtualScreenDDB) + this->DestroyDDB(_stageVirtualScreenDDB); + _stageVirtualScreenDDB = nullptr; + + for (size_t i = 0; i < _stageScreens.size(); ++i) + _stageScreens[i].reset(); + _stageVirtualScreen.reset(); +} + +bool VideoMemoryGraphicsDriver::DoNullSpriteCallback(int x, int y) +{ + if (!_nullSpriteCallback) + throw Ali3DException("Unhandled attempt to draw null sprite"); + _stageScreenDirty = false; + _stageVirtualScreen->ClearTransparent(); + // NOTE: this is not clear whether return value of callback may be + // relied on. Existing plugins do not seem to return anything but 0, + // even if they handle this event. + _nullSpriteCallback(x, y); + if (_stageScreenDirty) + { + if (_stageVirtualScreenDDB) + UpdateDDBFromBitmap(_stageVirtualScreenDDB, _stageVirtualScreen.get(), true); + else + _stageVirtualScreenDDB = CreateDDBFromBitmap(_stageVirtualScreen.get(), true); + return true; + } + return false; +} + +IDriverDependantBitmap *VideoMemoryGraphicsDriver::MakeFx(int r, int g, int b) +{ + if (_fxIndex == _fxPool.size()) _fxPool.push_back(ScreenFx()); + ScreenFx &fx = _fxPool[_fxIndex]; + if (fx.DDB == nullptr) + { + fx.Raw = BitmapHelper::CreateBitmap(16, 16, _mode.ColorDepth); + fx.DDB = CreateDDBFromBitmap(fx.Raw, false, true); + } + if (r != fx.Red || g != fx.Green || b != fx.Blue) + { + fx.Raw->Clear(makecol_depth(fx.Raw->GetColorDepth(), r, g, b)); + this->UpdateDDBFromBitmap(fx.DDB, fx.Raw, false); + fx.Red = r; + fx.Green = g; + fx.Blue = b; + } + _fxIndex++; + return fx.DDB; +} + +void VideoMemoryGraphicsDriver::ResetFxPool() +{ + _fxIndex = 0; +} + +void VideoMemoryGraphicsDriver::DestroyFxPool() +{ + for (auto &fx : _fxPool) + { + if (fx.DDB) + DestroyDDB(fx.DDB); + delete fx.Raw; + } + _fxPool.clear(); + _fxIndex = 0; +} + + +#define algetr32(c) getr32(c) +#define algetg32(c) getg32(c) +#define algetb32(c) getb32(c) +#define algeta32(c) geta32(c) + +#define algetr16(c) getr16(c) +#define algetg16(c) getg16(c) +#define algetb16(c) getb16(c) + +#define algetr8(c) getr8(c) +#define algetg8(c) getg8(c) +#define algetb8(c) getb8(c) + + +__inline void get_pixel_if_not_transparent8(unsigned char *pixel, unsigned char *red, unsigned char *green, unsigned char *blue, unsigned char *divisor) +{ + if (pixel[0] != MASK_COLOR_8) + { + *red += algetr8(pixel[0]); + *green += algetg8(pixel[0]); + *blue += algetb8(pixel[0]); + divisor[0]++; + } +} + +__inline void get_pixel_if_not_transparent16(unsigned short *pixel, unsigned short *red, unsigned short *green, unsigned short *blue, unsigned short *divisor) +{ + if (pixel[0] != MASK_COLOR_16) + { + *red += algetr16(pixel[0]); + *green += algetg16(pixel[0]); + *blue += algetb16(pixel[0]); + divisor[0]++; + } +} + +__inline void get_pixel_if_not_transparent32(unsigned int *pixel, unsigned int *red, unsigned int *green, unsigned int *blue, unsigned int *divisor) +{ + if (pixel[0] != MASK_COLOR_32) + { + *red += algetr32(pixel[0]); + *green += algetg32(pixel[0]); + *blue += algetb32(pixel[0]); + divisor[0]++; + } +} + + +#define VMEMCOLOR_RGBA(r,g,b,a) \ + ( (((a) & 0xFF) << _vmem_a_shift_32) | (((r) & 0xFF) << _vmem_r_shift_32) | (((g) & 0xFF) << _vmem_g_shift_32) | (((b) & 0xFF) << _vmem_b_shift_32) ) + + +void VideoMemoryGraphicsDriver::BitmapToVideoMem(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile, const VideoMemDDB *target, + char *dst_ptr, const int dst_pitch, const bool usingLinearFiltering) +{ + const int src_depth = bitmap->GetColorDepth(); + bool lastPixelWasTransparent = false; + for (int y = 0; y < tile->height; y++) + { + lastPixelWasTransparent = false; + const uint8_t *scanline_before = bitmap->GetScanLine(y + tile->y - 1); + const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y); + const uint8_t *scanline_after = bitmap->GetScanLine(y + tile->y + 1); + unsigned int* memPtrLong = (unsigned int*)dst_ptr; + + for (int x = 0; x < tile->width; x++) + { + if (src_depth == 8) + { + unsigned char* srcData = (unsigned char*)&scanline_at[(x + tile->x) * sizeof(char)]; + if (*srcData == MASK_COLOR_8) + { + if (!usingLinearFiltering) + memPtrLong[x] = 0; + // set to transparent, but use the colour from the neighbouring + // pixel to stop the linear filter doing black outlines + else + { + unsigned char red = 0, green = 0, blue = 0, divisor = 0; + if (x > 0) + get_pixel_if_not_transparent8(&srcData[-1], &red, &green, &blue, &divisor); + if (x < tile->width - 1) + get_pixel_if_not_transparent8(&srcData[1], &red, &green, &blue, &divisor); + if (y > 0) + get_pixel_if_not_transparent8((unsigned char*)&scanline_before[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor); + if (y < tile->height - 1) + get_pixel_if_not_transparent8((unsigned char*)&scanline_after[(x + tile->x) * sizeof(char)], &red, &green, &blue, &divisor); + if (divisor > 0) + memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0); + else + memPtrLong[x] = 0; + } + lastPixelWasTransparent = true; + } + else + { + memPtrLong[x] = VMEMCOLOR_RGBA(algetr8(*srcData), algetg8(*srcData), algetb8(*srcData), 0xFF); + if (lastPixelWasTransparent) + { + // update the colour of the previous tranparent pixel, to + // stop black outlines when linear filtering + memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF; + lastPixelWasTransparent = false; + } + } + } + else if (src_depth == 16) + { + unsigned short* srcData = (unsigned short*)&scanline_at[(x + tile->x) * sizeof(short)]; + if (*srcData == MASK_COLOR_16) + { + if (!usingLinearFiltering) + memPtrLong[x] = 0; + // set to transparent, but use the colour from the neighbouring + // pixel to stop the linear filter doing black outlines + else + { + unsigned short red = 0, green = 0, blue = 0, divisor = 0; + if (x > 0) + get_pixel_if_not_transparent16(&srcData[-1], &red, &green, &blue, &divisor); + if (x < tile->width - 1) + get_pixel_if_not_transparent16(&srcData[1], &red, &green, &blue, &divisor); + if (y > 0) + get_pixel_if_not_transparent16((unsigned short*)&scanline_before[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor); + if (y < tile->height - 1) + get_pixel_if_not_transparent16((unsigned short*)&scanline_after[(x + tile->x) * sizeof(short)], &red, &green, &blue, &divisor); + if (divisor > 0) + memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0); + else + memPtrLong[x] = 0; + } + lastPixelWasTransparent = true; + } + else + { + memPtrLong[x] = VMEMCOLOR_RGBA(algetr16(*srcData), algetg16(*srcData), algetb16(*srcData), 0xFF); + if (lastPixelWasTransparent) + { + // update the colour of the previous tranparent pixel, to + // stop black outlines when linear filtering + memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF; + lastPixelWasTransparent = false; + } + } + } + else if (src_depth == 32) + { + unsigned int* memPtrLong = (unsigned int*)dst_ptr; + unsigned int* srcData = (unsigned int*)&scanline_at[(x + tile->x) * sizeof(int)]; + if (*srcData == MASK_COLOR_32) + { + if (!usingLinearFiltering) + memPtrLong[x] = 0; + // set to transparent, but use the colour from the neighbouring + // pixel to stop the linear filter doing black outlines + else + { + unsigned int red = 0, green = 0, blue = 0, divisor = 0; + if (x > 0) + get_pixel_if_not_transparent32(&srcData[-1], &red, &green, &blue, &divisor); + if (x < tile->width - 1) + get_pixel_if_not_transparent32(&srcData[1], &red, &green, &blue, &divisor); + if (y > 0) + get_pixel_if_not_transparent32((unsigned int*)&scanline_before[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor); + if (y < tile->height - 1) + get_pixel_if_not_transparent32((unsigned int*)&scanline_after[(x + tile->x) * sizeof(int)], &red, &green, &blue, &divisor); + if (divisor > 0) + memPtrLong[x] = VMEMCOLOR_RGBA(red / divisor, green / divisor, blue / divisor, 0); + else + memPtrLong[x] = 0; + } + lastPixelWasTransparent = true; + } + else if (has_alpha) + { + memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), algeta32(*srcData)); + } + else + { + memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), 0xFF); + if (lastPixelWasTransparent) + { + // update the colour of the previous tranparent pixel, to + // stop black outlines when linear filtering + memPtrLong[x - 1] = memPtrLong[x] & 0x00FFFFFF; + lastPixelWasTransparent = false; + } + } + } + } + + dst_ptr += dst_pitch; + } +} + +void VideoMemoryGraphicsDriver::BitmapToVideoMemOpaque(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile, const VideoMemDDB *target, + char *dst_ptr, const int dst_pitch) +{ + const int src_depth = bitmap->GetColorDepth(); + for (int y = 0; y < tile->height; y++) + { + const uint8_t *scanline_at = bitmap->GetScanLine(y + tile->y); + unsigned int* memPtrLong = (unsigned int*)dst_ptr; + + for (int x = 0; x < tile->width; x++) + { + if (src_depth == 8) + { + unsigned char* srcData = (unsigned char*)&scanline_at[(x + tile->x) * sizeof(char)]; + memPtrLong[x] = VMEMCOLOR_RGBA(algetr8(*srcData), algetg8(*srcData), algetb8(*srcData), 0xFF); + } + else if (src_depth == 16) + { + unsigned short* srcData = (unsigned short*)&scanline_at[(x + tile->x) * sizeof(short)]; + memPtrLong[x] = VMEMCOLOR_RGBA(algetr16(*srcData), algetg16(*srcData), algetb16(*srcData), 0xFF); + } + else if (src_depth == 32) + { + unsigned int* memPtrLong = (unsigned int*)dst_ptr; + unsigned int* srcData = (unsigned int*)&scanline_at[(x + tile->x) * sizeof(int)]; + if (has_alpha) + memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), algeta32(*srcData)); + else + memPtrLong[x] = VMEMCOLOR_RGBA(algetr32(*srcData), algetg32(*srcData), algetb32(*srcData), 0xFF); + } + } + + dst_ptr += dst_pitch; + } +} + +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/gfx/gfxdriverbase.h b/engines/ags/engine/gfx/gfxdriverbase.h new file mode 100644 index 00000000000..a44534e5279 --- /dev/null +++ b/engines/ags/engine/gfx/gfxdriverbase.h @@ -0,0 +1,251 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Implementation base for graphics driver +// +//============================================================================= +#ifndef __AGS_EE_GFX__GFXDRIVERBASE_H +#define __AGS_EE_GFX__GFXDRIVERBASE_H + +#include +#include "gfx/ddb.h" +#include "gfx/graphicsdriver.h" +#include "util/scaling.h" + +namespace AGS +{ +namespace Engine +{ + +using Common::Bitmap; + +// Sprite batch, defines viewport and an optional model transformation for the list of sprites +struct SpriteBatchDesc +{ + // View rectangle for positioning and clipping, in resolution coordinates + // (this may be screen or game frame resolution, depending on circumstances) + Rect Viewport; + // Optional model transformation, to be applied to each sprite + SpriteTransform Transform; + // Global node offset applied to the whole batch as the last transform + Point Offset; + // Global node flip applied to the whole batch as the last transform + GlobalFlipType Flip; + // Optional bitmap to draw sprites upon. Used exclusively by the software rendering mode. + PBitmap Surface; + + SpriteBatchDesc() : Flip(kFlip_None) {} + SpriteBatchDesc(const Rect viewport, const SpriteTransform &transform, const Point offset = Point(), + GlobalFlipType flip = kFlip_None, PBitmap surface = nullptr) + : Viewport(viewport) + , Transform(transform) + , Offset(offset) + , Flip(flip) + , Surface(surface) + { + } +}; + +typedef std::vector SpriteBatchDescs; + +// The single sprite entry in the render list +template +struct SpriteDrawListEntry +{ + T_DDB *bitmap; // TODO: use shared pointer? + int x, y; // sprite position, in camera coordinates + bool skip; + + SpriteDrawListEntry() + : bitmap(nullptr) + , x(0) + , y(0) + , skip(false) + { + } + + SpriteDrawListEntry(T_DDB *ddb, int x_ = 0, int y_ = 0) + : bitmap(ddb) + , x(x_) + , y(y_) + , skip(false) + { + } +}; + + +// GraphicsDriverBase - is the parent class for all graphics drivers in AGS, +// that incapsulates the most common functionality. +class GraphicsDriverBase : public IGraphicsDriver +{ +public: + GraphicsDriverBase(); + + bool IsModeSet() const override; + bool IsNativeSizeValid() const override; + bool IsRenderFrameValid() const override; + DisplayMode GetDisplayMode() const override; + Size GetNativeSize() const override; + Rect GetRenderDestination() const override; + + void BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform, + const Point offset = Point(), GlobalFlipType flip = kFlip_None, PBitmap surface = nullptr) override; + void ClearDrawLists() override; + + void SetCallbackForPolling(GFXDRV_CLIENTCALLBACK callback) override { _pollingCallback = callback; } + void SetCallbackToDrawScreen(GFXDRV_CLIENTCALLBACK callback, GFXDRV_CLIENTCALLBACK post_callback) override + { _drawScreenCallback = callback; _drawPostScreenCallback = post_callback; } + void SetCallbackOnInit(GFXDRV_CLIENTCALLBACKINITGFX callback) override { _initGfxCallback = callback; } + void SetCallbackOnSurfaceUpdate(GFXDRV_CLIENTCALLBACKSURFACEUPDATE callback) override { _initSurfaceUpdateCallback = callback; } + void SetCallbackForNullSprite(GFXDRV_CLIENTCALLBACKXY callback) override { _nullSpriteCallback = callback; } + +protected: + // Called after graphics driver was initialized for use for the first time + virtual void OnInit(volatile int *loopTimer); + // Called just before graphics mode is going to be uninitialized and its + // resources released + virtual void OnUnInit(); + // Called after new mode was successfully initialized + virtual void OnModeSet(const DisplayMode &mode); + // Called when the new native size is set + virtual void OnSetNativeSize(const Size &src_size); + // Called before display mode is going to be released + virtual void OnModeReleased(); + // Called when new render frame is set + virtual void OnSetRenderFrame(const Rect &dst_rect); + // Called when the new filter is set + virtual void OnSetFilter(); + // Initialize sprite batch and allocate necessary resources + virtual void InitSpriteBatch(size_t index, const SpriteBatchDesc &desc) = 0; + // Clears sprite lists + virtual void ResetAllBatches() = 0; + + void OnScalingChanged(); + + DisplayMode _mode; // display mode settings + Rect _srcRect; // rendering source rect + Rect _dstRect; // rendering destination rect + Rect _filterRect; // filter scaling destination rect (before final scaling) + PlaneScaling _scaling; // native -> render dest coordinate transformation + + // Callbacks + GFXDRV_CLIENTCALLBACK _pollingCallback; + GFXDRV_CLIENTCALLBACK _drawScreenCallback; + GFXDRV_CLIENTCALLBACK _drawPostScreenCallback; + GFXDRV_CLIENTCALLBACKXY _nullSpriteCallback; + GFXDRV_CLIENTCALLBACKINITGFX _initGfxCallback; + GFXDRV_CLIENTCALLBACKSURFACEUPDATE _initSurfaceUpdateCallback; + + // Sprite batch parameters + SpriteBatchDescs _spriteBatchDesc; // sprite batches list + size_t _actSpriteBatch; // active batch index +}; + + + +// Generic TextureTile base +struct TextureTile +{ + int x, y; + int width, height; +}; + +// Parent class for the video memory DDBs +class VideoMemDDB : public IDriverDependantBitmap +{ +public: + int GetWidth() override { return _width; } + int GetHeight() override { return _height; } + int GetColorDepth() override { return _colDepth; } + + int _width, _height; + int _colDepth; + bool _opaque; // no mask color +}; + +// VideoMemoryGraphicsDriver - is the parent class for the graphic drivers +// which drawing method is based on passing the sprite stack into GPU, +// rather than blitting to flat screen bitmap. +class VideoMemoryGraphicsDriver : public GraphicsDriverBase +{ +public: + VideoMemoryGraphicsDriver(); + ~VideoMemoryGraphicsDriver() override; + + bool UsesMemoryBackBuffer() override; + Bitmap *GetMemoryBackBuffer() override; + void SetMemoryBackBuffer(Bitmap *backBuffer) override; + Bitmap* GetStageBackBuffer() override; + +protected: + // Stage screens are raw bitmap buffers meant to be sent to plugins on demand + // at certain drawing stages. If used at least once these buffers are then + // rendered as additional sprites in their respected order. + PBitmap CreateStageScreen(size_t index, const Size &sz); + PBitmap GetStageScreen(size_t index); + void DestroyAllStageScreens(); + // Use engine callback to acquire replacement for the null sprite; + // returns true if the sprite was provided onto the virtual screen, + // and false if this entry should be skipped. + bool DoNullSpriteCallback(int x, int y); + + // Prepare and get fx item from the pool + IDriverDependantBitmap *MakeFx(int r, int g, int b); + // Resets fx pool counter + void ResetFxPool(); + // Disposes all items in the fx pool + void DestroyFxPool(); + + // Prepares bitmap to be applied to the texture, copies pixels to the provided buffer + void BitmapToVideoMem(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile, const VideoMemDDB *target, + char *dst_ptr, const int dst_pitch, const bool usingLinearFiltering); + // Same but optimized for opaque source bitmaps which ignore transparent "mask color" + void BitmapToVideoMemOpaque(const Bitmap *bitmap, const bool has_alpha, const TextureTile *tile, const VideoMemDDB *target, + char *dst_ptr, const int dst_pitch); + + // Stage virtual screen is used to let plugins draw custom graphics + // in between render stages (between room and GUI, after GUI, and so on) + PBitmap _stageVirtualScreen; + IDriverDependantBitmap *_stageVirtualScreenDDB; + + // Color component shifts in video bitmap format (set by implementations) + int _vmem_a_shift_32; + int _vmem_r_shift_32; + int _vmem_g_shift_32; + int _vmem_b_shift_32; + +private: + // Virtual screens for rendering stages (sprite batches) + std::vector _stageScreens; + // Flag which indicates whether stage screen was drawn upon during engine + // callback and has to be inserted into sprite stack. + bool _stageScreenDirty; + + // Fx quads pool (for screen overlay effects) + struct ScreenFx + { + Bitmap *Raw = nullptr; + IDriverDependantBitmap *DDB = nullptr; + int Red = -1; + int Green = -1; + int Blue = -1; + }; + std::vector _fxPool; + size_t _fxIndex; // next free pool item +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__GFXDRIVERBASE_H diff --git a/engines/ags/engine/gfx/gfxdriverfactory.cpp b/engines/ags/engine/gfx/gfxdriverfactory.cpp new file mode 100644 index 00000000000..8dc58b92764 --- /dev/null +++ b/engines/ags/engine/gfx/gfxdriverfactory.cpp @@ -0,0 +1,70 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "gfx/gfxdriverfactory.h" + +#include "core/platform.h" + +#define AGS_HAS_DIRECT3D (AGS_PLATFORM_OS_WINDOWS) +#define AGS_HAS_OPENGL (AGS_PLATFORM_OS_WINDOWS || AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_LINUX) + +#include "gfx/ali3dsw.h" +#include "gfx/gfxfilter_allegro.h" + +#if AGS_HAS_OPENGL +#include "gfx/ali3dogl.h" +#include "gfx/gfxfilter_ogl.h" +#endif + +#if AGS_HAS_DIRECT3D +#include "platform/windows/gfx/ali3dd3d.h" +#include "gfx/gfxfilter_d3d.h" +#endif + +#include "main/main_allegro.h" + +namespace AGS +{ +namespace Engine +{ + +void GetGfxDriverFactoryNames(StringV &ids) +{ +#if AGS_HAS_DIRECT3D + ids.push_back("D3D9"); +#endif +#if AGS_HAS_OPENGL + ids.push_back("OGL"); +#endif + ids.push_back("Software"); +} + +IGfxDriverFactory *GetGfxDriverFactory(const String id) +{ +#if AGS_HAS_DIRECT3D + if (id.CompareNoCase("D3D9") == 0) + return D3D::D3DGraphicsFactory::GetFactory(); +#endif +#if AGS_HAS_OPENGL + if (id.CompareNoCase("OGL") == 0) + return OGL::OGLGraphicsFactory::GetFactory(); +#endif + if (id.CompareNoCase("Software") == 0) + return ALSW::ALSWGraphicsFactory::GetFactory(); + set_allegro_error("No graphics factory with such id: %s", id.GetCStr()); + return nullptr; +} + +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/gfx/gfxdriverfactory.h b/engines/ags/engine/gfx/gfxdriverfactory.h new file mode 100644 index 00000000000..fac4b36bf22 --- /dev/null +++ b/engines/ags/engine/gfx/gfxdriverfactory.h @@ -0,0 +1,77 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Graphics driver factory interface +// +// Graphics factory is supposed to be singleton. Factory implementation must +// guarantee that it may be created and destroyed any number of times during +// program execution. +// +//============================================================================= + +#ifndef __AGS_EE_GFX__GFXDRIVERFACTORY_H +#define __AGS_EE_GFX__GFXDRIVERFACTORY_H + +#include +#include "util/string.h" +#include "util/string_types.h" + +namespace AGS +{ +namespace Engine +{ + +using Common::String; +using Common::StringV; +class IGraphicsDriver; +class IGfxFilter; +struct GfxFilterInfo; +typedef std::shared_ptr PGfxFilter; + + +class IGfxDriverFactory +{ +public: + virtual ~IGfxDriverFactory() = default; + + // Shutdown graphics factory and deallocate any resources it owns; + // graphics factory will be unusable after calling this function. + virtual void Shutdown() = 0; + // Get graphics driver associated with this factory; creates one if + // it does not exist. + virtual IGraphicsDriver * GetDriver() = 0; + // Destroy graphics driver associated with this factory; does nothing + // if one was not created yet, + virtual void DestroyDriver() = 0; + + // Get number of supported filters + virtual size_t GetFilterCount() const = 0; + // Get filter description + virtual const GfxFilterInfo *GetFilterInfo(size_t index) const = 0; + // Get ID of the default filter + virtual String GetDefaultFilterID() const = 0; + + // Assign specified filter to graphics driver + virtual PGfxFilter SetFilter(const String &id, String &filter_error) = 0; +}; + +// Query the available graphics factory names +void GetGfxDriverFactoryNames(StringV &ids); +// Acquire the graphics factory singleton object by its id +IGfxDriverFactory *GetGfxDriverFactory(const String id); + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__GFXDRIVERFACTORY_H diff --git a/engines/ags/engine/gfx/gfxdriverfactorybase.h b/engines/ags/engine/gfx/gfxdriverfactorybase.h new file mode 100644 index 00000000000..c85b2afbb2b --- /dev/null +++ b/engines/ags/engine/gfx/gfxdriverfactorybase.h @@ -0,0 +1,110 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Base implementation of IGfxDriverFactory +// +// GfxDriverFactoryBase is a template implementation of basic driver factory +// functionality, such as creating and destruction of graphics driver, and +// managing graphic filters. +// +//============================================================================= + +#ifndef __AGS_EE_GFX__GFXDRIVERFACTORYBASE_H +#define __AGS_EE_GFX__GFXDRIVERFACTORYBASE_H + +#include +#include "gfx/gfxdriverfactory.h" +#include "gfx/gfxfilter.h" + +namespace AGS +{ +namespace Engine +{ + +template +class GfxDriverFactoryBase : public IGfxDriverFactory +{ +protected: + ~GfxDriverFactoryBase() override + { + delete _driver; + } + +public: + void Shutdown() override + { + delete this; + } + + IGraphicsDriver *GetDriver() override + { + if (!_driver) + _driver = EnsureDriverCreated(); + return _driver; + } + + void DestroyDriver() override + { + delete _driver; + _driver = nullptr; + } + + PGfxFilter SetFilter(const String &id, String &filter_error) override + { + TGfxDriverClass *driver = EnsureDriverCreated(); + if (!driver) + { + filter_error = "Graphics driver was not created"; + return PGfxFilter(); + } + + const int color_depth = driver->GetDisplayMode().ColorDepth; + if (color_depth == 0) + { + filter_error = "Graphics mode is not set"; + return PGfxFilter(); + } + + std::shared_ptr filter(CreateFilter(id)); + if (!filter) + { + filter_error = "Filter does not exist"; + return PGfxFilter(); + } + + if (!filter->Initialize(color_depth, filter_error)) + { + return PGfxFilter(); + } + + driver->SetGraphicsFilter(filter); + return filter; + } + +protected: + GfxDriverFactoryBase() + : _driver(nullptr) + { + } + + virtual TGfxDriverClass *EnsureDriverCreated() = 0; + virtual TGfxFilterClass *CreateFilter(const String &id) = 0; + + TGfxDriverClass *_driver; +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__GFXDRIVERFACTORYBASE_H diff --git a/engines/ags/engine/gfx/gfxfilter.h b/engines/ags/engine/gfx/gfxfilter.h new file mode 100644 index 00000000000..12d37bdfe8b --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter.h @@ -0,0 +1,71 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Graphics filter interface +// +//============================================================================= + +#ifndef __AGS_EE_GFX__GFXFILTER_H +#define __AGS_EE_GFX__GFXFILTER_H + +#include +#include "util/geometry.h" +#include "util/string.h" + +namespace AGS +{ +namespace Engine +{ + +using Common::String; + +struct GfxFilterInfo +{ + String Id; + String Name; + int MinScale; + int MaxScale; + + GfxFilterInfo() + {} + GfxFilterInfo(String id, String name, int min_scale = 0, int max_scale = 0) + : Id(id) + , Name(name) + , MinScale(min_scale) + , MaxScale(max_scale) + {} +}; + +class IGfxFilter +{ +public: + virtual ~IGfxFilter() = default; + + virtual const GfxFilterInfo &GetInfo() const = 0; + + // Init filter for the specified color depth + virtual bool Initialize(const int color_depth, String &err_str) = 0; + virtual void UnInitialize() = 0; + // Try to set rendering translation; returns actual supported destination rect + virtual Rect SetTranslation(const Size src_size, const Rect dst_rect) = 0; + // Get defined destination rect for this filter + virtual Rect GetDestination() const = 0; +}; + +typedef std::shared_ptr PGfxFilter; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__GFXFILTER_H diff --git a/engines/ags/engine/gfx/gfxfilter_aad3d.cpp b/engines/ags/engine/gfx/gfxfilter_aad3d.cpp new file mode 100644 index 00000000000..48da032ae4f --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_aad3d.cpp @@ -0,0 +1,53 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" +#include "stdio.h" +#include "gfx/gfxfilter_aad3d.h" + +#if AGS_PLATFORM_OS_WINDOWS +#include +#endif + +namespace AGS +{ +namespace Engine +{ +namespace D3D +{ + +const GfxFilterInfo AAD3DGfxFilter::FilterInfo = GfxFilterInfo("Linear", "Linear interpolation"); + +const GfxFilterInfo &AAD3DGfxFilter::GetInfo() const +{ + return FilterInfo; +} + +void AAD3DGfxFilter::SetSamplerStateForStandardSprite(void *direct3ddevice9) +{ +#if AGS_PLATFORM_OS_WINDOWS + IDirect3DDevice9* d3d9 = ((IDirect3DDevice9*)direct3ddevice9); + d3d9->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + d3d9->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); +#endif +} + +bool AAD3DGfxFilter::NeedToColourEdgeLines() +{ + return true; +} + +} // namespace D3D +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/gfx/gfxfilter_aad3d.h b/engines/ags/engine/gfx/gfxfilter_aad3d.h new file mode 100644 index 00000000000..1e990799f92 --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_aad3d.h @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Anti-aliased D3D filter +// +//============================================================================= + +#ifndef __AGS_EE_GFX__AAD3DGFXFILTER_H +#define __AGS_EE_GFX__AAD3DGFXFILTER_H + +#include "gfx/gfxfilter_d3d.h" + +namespace AGS +{ +namespace Engine +{ +namespace D3D +{ + +class AAD3DGfxFilter : public D3DGfxFilter +{ +public: + const GfxFilterInfo &GetInfo() const override; + + void SetSamplerStateForStandardSprite(void *direct3ddevice9) override; + bool NeedToColourEdgeLines() override; + + static const GfxFilterInfo FilterInfo; +}; + +} // namespace D3D +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__AAD3DGFXFILTER_H diff --git a/engines/ags/engine/gfx/gfxfilter_aaogl.cpp b/engines/ags/engine/gfx/gfxfilter_aaogl.cpp new file mode 100644 index 00000000000..8b2fc409646 --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_aaogl.cpp @@ -0,0 +1,51 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS || AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_LINUX + +#include "gfx/gfxfilter_aaogl.h" +#include "ogl_headers.h" + +namespace AGS +{ +namespace Engine +{ +namespace OGL +{ + +const GfxFilterInfo AAOGLGfxFilter::FilterInfo = GfxFilterInfo("Linear", "Linear interpolation"); + +bool AAOGLGfxFilter::UseLinearFiltering() const +{ + return true; +} + +void AAOGLGfxFilter::SetFilteringForStandardSprite() +{ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); +} + +const GfxFilterInfo &AAOGLGfxFilter::GetInfo() const +{ + return FilterInfo; +} + +} // namespace OGL +} // namespace Engine +} // namespace AGS + +#endif // only on Windows, Android and iOS diff --git a/engines/ags/engine/gfx/gfxfilter_aaogl.h b/engines/ags/engine/gfx/gfxfilter_aaogl.h new file mode 100644 index 00000000000..064c5ba4c25 --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_aaogl.h @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Anti-aliased OGL filter +// +//============================================================================= + +#ifndef __AGS_EE_GFX__AAOGLGFXFILTER_H +#define __AGS_EE_GFX__AAOGLGFXFILTER_H + +#include "gfx/gfxfilter_ogl.h" + +namespace AGS +{ +namespace Engine +{ +namespace OGL +{ + +class AAOGLGfxFilter : public OGLGfxFilter +{ +public: + const GfxFilterInfo &GetInfo() const override; + + bool UseLinearFiltering() const override; + void SetFilteringForStandardSprite() override; + + static const GfxFilterInfo FilterInfo; +}; + +} // namespace OGL +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__AAOGLGFXFILTER_H diff --git a/engines/ags/engine/gfx/gfxfilter_allegro.cpp b/engines/ags/engine/gfx/gfxfilter_allegro.cpp new file mode 100644 index 00000000000..274163c1e8c --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_allegro.cpp @@ -0,0 +1,172 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "gfx/gfxfilter_allegro.h" + +namespace AGS +{ +namespace Engine +{ +namespace ALSW +{ + +using namespace Common; + +const GfxFilterInfo AllegroGfxFilter::FilterInfo = GfxFilterInfo("StdScale", "Nearest-neighbour"); + +AllegroGfxFilter::AllegroGfxFilter() + : realScreen(nullptr) + , virtualScreen(nullptr) + , realScreenSizedBuffer(nullptr) + , lastBlitFrom(nullptr) + , lastBlitX(0) + , lastBlitY(0) +{ +} + +const GfxFilterInfo &AllegroGfxFilter::GetInfo() const +{ + return FilterInfo; +} + +Bitmap* AllegroGfxFilter::InitVirtualScreen(Bitmap *screen, const Size src_size, const Rect dst_rect) +{ + ShutdownAndReturnRealScreen(); + + realScreen = screen; + SetTranslation(src_size, dst_rect); + + if (src_size == dst_rect.GetSize() && dst_rect.Top == 0 && dst_rect.Left == 0) + { + // Speed up software rendering if no scaling is performed + realScreenSizedBuffer = nullptr; + virtualScreen = realScreen; + } + else + { + realScreenSizedBuffer = BitmapHelper::CreateBitmap(screen->GetWidth(), screen->GetHeight(), screen->GetColorDepth()); + virtualScreen = BitmapHelper::CreateBitmap(src_size.Width, src_size.Height, screen->GetColorDepth()); + } + return virtualScreen; +} + +Bitmap *AllegroGfxFilter::ShutdownAndReturnRealScreen() +{ + if (virtualScreen != realScreen) + delete virtualScreen; + delete realScreenSizedBuffer; + virtualScreen = nullptr; + realScreenSizedBuffer = nullptr; + Bitmap *real_scr = realScreen; + realScreen = nullptr; + return real_scr; +} + +void AllegroGfxFilter::RenderScreen(Bitmap *toRender, int x, int y) { + + if (toRender != realScreen) + { + x = _scaling.X.ScalePt(x); + y = _scaling.Y.ScalePt(y); + const int width = _scaling.X.ScaleDistance(toRender->GetWidth()); + const int height = _scaling.Y.ScaleDistance(toRender->GetHeight()); + Bitmap *render_src = PreRenderPass(toRender); + if (render_src->GetSize() == _dstRect.GetSize()) + realScreen->Blit(render_src, 0, 0, x, y, width, height); + else + { + realScreen->StretchBlt(render_src, RectWH(x, y, width, height)); + } + } + lastBlitFrom = toRender; + lastBlitX = x; + lastBlitY = y; +} + +void AllegroGfxFilter::RenderScreenFlipped(Bitmap *toRender, int x, int y, GlobalFlipType flipType) { + + if (toRender == virtualScreen) + return; + + switch (flipType) + { + case kFlip_Horizontal: + virtualScreen->FlipBlt(toRender, 0, 0, Common::kBitmap_HFlip); + break; + case kFlip_Vertical: + virtualScreen->FlipBlt(toRender, 0, 0, Common::kBitmap_VFlip); + break; + case kFlip_Both: + virtualScreen->FlipBlt(toRender, 0, 0, Common::kBitmap_HVFlip); + break; + default: + virtualScreen->Blit(toRender, 0, 0); + break; + } + + RenderScreen(virtualScreen, x, y); +} + +void AllegroGfxFilter::ClearRect(int x1, int y1, int x2, int y2, int color) +{ + if (!realScreen) return; + Rect r = _scaling.ScaleRange(Rect(x1, y1, x2, y2)); + realScreen->FillRect(r, color); +} + +void AllegroGfxFilter::GetCopyOfScreenIntoBitmap(Bitmap *copyBitmap) +{ + GetCopyOfScreenIntoBitmap(copyBitmap, true); +} + +void AllegroGfxFilter::GetCopyOfScreenIntoBitmap(Bitmap *copyBitmap, bool copy_with_yoffset) +{ + if (copyBitmap == realScreen) + return; + + if (!copy_with_yoffset) + { + if (copyBitmap->GetSize() == _dstRect.GetSize()) + copyBitmap->Blit(realScreen, _dstRect.Left, _dstRect.Top, 0, 0, _dstRect.GetWidth(), _dstRect.GetHeight()); + else + { + // Can't stretch_blit from Video Memory to normal memory, + // so copy the screen to a buffer first. + realScreenSizedBuffer->Blit(realScreen, 0, 0); + copyBitmap->StretchBlt(realScreenSizedBuffer, + _dstRect, + RectWH(0, 0, copyBitmap->GetWidth(), copyBitmap->GetHeight())); + } + } + else if (!lastBlitFrom) + copyBitmap->Fill(0); + else if (copyBitmap->GetSize() == _dstRect.GetSize()) + copyBitmap->Blit(realScreen, lastBlitX, lastBlitY, 0, 0, copyBitmap->GetWidth(), copyBitmap->GetHeight()); + else + { + copyBitmap->StretchBlt(lastBlitFrom, + RectWH(0, 0, lastBlitFrom->GetWidth(), lastBlitFrom->GetHeight()), + RectWH(0, 0, copyBitmap->GetWidth(), copyBitmap->GetHeight())); + } +} + +Bitmap *AllegroGfxFilter::PreRenderPass(Bitmap *toRender) +{ + // do nothing by default + return toRender; +} + +} // namespace ALSW +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/gfx/gfxfilter_allegro.h b/engines/ags/engine/gfx/gfxfilter_allegro.h new file mode 100644 index 00000000000..53f52c4faed --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_allegro.h @@ -0,0 +1,71 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Standard software scaling filter +// +//============================================================================= + +#ifndef __AGS_EE_GFX__ALLEGROGFXFILTER_H +#define __AGS_EE_GFX__ALLEGROGFXFILTER_H + +#include "gfx/bitmap.h" +#include "gfx/gfxfilter_scaling.h" +#include "gfx/gfxdefines.h" + +namespace AGS +{ +namespace Engine +{ +namespace ALSW +{ + +using Common::Bitmap; + +class AllegroGfxFilter : public ScalingGfxFilter +{ +public: + AllegroGfxFilter(); + + const GfxFilterInfo &GetInfo() const override; + + virtual Bitmap *InitVirtualScreen(Bitmap *screen, const Size src_size, const Rect dst_rect); + virtual Bitmap *ShutdownAndReturnRealScreen(); + virtual void RenderScreen(Bitmap *toRender, int x, int y); + virtual void RenderScreenFlipped(Bitmap *toRender, int x, int y, GlobalFlipType flipType); + virtual void ClearRect(int x1, int y1, int x2, int y2, int color); + virtual void GetCopyOfScreenIntoBitmap(Bitmap *copyBitmap); + virtual void GetCopyOfScreenIntoBitmap(Bitmap *copyBitmap, bool copy_with_yoffset); + + static const GfxFilterInfo FilterInfo; + +protected: + virtual Bitmap *PreRenderPass(Bitmap *toRender); + + // pointer to real screen bitmap + Bitmap *realScreen; + // bitmap the size of game resolution + Bitmap *virtualScreen; + // buffer for making a copy of video memory before stretching + // for screen capture + Bitmap *realScreenSizedBuffer; + Bitmap *lastBlitFrom; + int lastBlitX; + int lastBlitY; +}; + +} // namespace ALSW +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__ALLEGROGFXFILTER_H diff --git a/engines/ags/engine/gfx/gfxfilter_d3d.cpp b/engines/ags/engine/gfx/gfxfilter_d3d.cpp new file mode 100644 index 00000000000..7e9ac6fd522 --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_d3d.cpp @@ -0,0 +1,51 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" +#include "gfx/gfxfilter_d3d.h" +#if AGS_PLATFORM_OS_WINDOWS +#include +#endif + +namespace AGS +{ +namespace Engine +{ +namespace D3D +{ + +const GfxFilterInfo D3DGfxFilter::FilterInfo = GfxFilterInfo("StdScale", "Nearest-neighbour"); + +const GfxFilterInfo &D3DGfxFilter::GetInfo() const +{ + return FilterInfo; +} + +void D3DGfxFilter::SetSamplerStateForStandardSprite(void *direct3ddevice9) +{ +#if AGS_PLATFORM_OS_WINDOWS + IDirect3DDevice9* d3d9 = ((IDirect3DDevice9*)direct3ddevice9); + d3d9->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT); + d3d9->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT); +#endif +} + +bool D3DGfxFilter::NeedToColourEdgeLines() +{ + return false; +} + +} // namespace D3D +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/gfx/gfxfilter_d3d.h b/engines/ags/engine/gfx/gfxfilter_d3d.h new file mode 100644 index 00000000000..dee22e5c775 --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_d3d.h @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Standard 3D-accelerated filter +// +//============================================================================= + +#ifndef __AGS_EE_GFX__D3DGFXFILTER_H +#define __AGS_EE_GFX__D3DGFXFILTER_H + +#include "gfx/gfxfilter_scaling.h" + +namespace AGS +{ +namespace Engine +{ +namespace D3D +{ + +class D3DGfxFilter : public ScalingGfxFilter +{ +public: + const GfxFilterInfo &GetInfo() const override; + + virtual void SetSamplerStateForStandardSprite(void *direct3ddevice9); + virtual bool NeedToColourEdgeLines(); + + static const GfxFilterInfo FilterInfo; +}; + +} // namespace D3D +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__D3DGFXFILTER_H diff --git a/engines/ags/engine/gfx/gfxfilter_hqx.cpp b/engines/ags/engine/gfx/gfxfilter_hqx.cpp new file mode 100644 index 00000000000..6b7f843ba66 --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_hqx.cpp @@ -0,0 +1,92 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "gfx/bitmap.h" +#include "gfx/gfxfilter_hqx.h" +#include "gfx/hq2x3x.h" + +namespace AGS +{ +namespace Engine +{ +namespace ALSW +{ + +using namespace Common; + +const GfxFilterInfo HqxGfxFilter::FilterInfo = GfxFilterInfo("Hqx", "Hqx (High Quality)", 2, 3); + +HqxGfxFilter::HqxGfxFilter() + : _pfnHqx(nullptr) + , _hqxScalingBuffer(nullptr) +{ +} + +HqxGfxFilter::~HqxGfxFilter() +{ + delete _hqxScalingBuffer; +} + +const GfxFilterInfo &HqxGfxFilter::GetInfo() const +{ + return FilterInfo; +} + +bool HqxGfxFilter::Initialize(const int color_depth, String &err_str) +{ + if (color_depth < 32) + { + err_str = "Only supports 32-bit colour games"; + return false; + } + return AllegroGfxFilter::Initialize(color_depth, err_str); +} + +Bitmap* HqxGfxFilter::InitVirtualScreen(Bitmap *screen, const Size src_size, const Rect dst_rect) +{ + Bitmap *virtual_screen = AllegroGfxFilter::InitVirtualScreen(screen, src_size, dst_rect); + + // Choose used algorithm depending on minimal required integer scaling + int min_scaling = Math::Min(dst_rect.GetWidth() / src_size.Width, dst_rect.GetHeight() / src_size.Height); + min_scaling = Math::Clamp(min_scaling, 2, 3); + if (min_scaling == 2) + _pfnHqx = hq2x_32; + else + _pfnHqx = hq3x_32; + _hqxScalingBuffer = BitmapHelper::CreateBitmap(src_size.Width * min_scaling, src_size.Height * min_scaling); + + InitLUTs(); + return virtual_screen; +} + +Bitmap *HqxGfxFilter::ShutdownAndReturnRealScreen() +{ + Bitmap *real_screen = AllegroGfxFilter::ShutdownAndReturnRealScreen(); + delete _hqxScalingBuffer; + _hqxScalingBuffer = nullptr; + return real_screen; +} + +Bitmap *HqxGfxFilter::PreRenderPass(Bitmap *toRender) +{ + _hqxScalingBuffer->Acquire(); + _pfnHqx(toRender->GetDataForWriting(), _hqxScalingBuffer->GetDataForWriting(), + toRender->GetWidth(), toRender->GetHeight(), _hqxScalingBuffer->GetLineLength()); + _hqxScalingBuffer->Release(); + return _hqxScalingBuffer; +} + +} // namespace ALSW +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/gfx/gfxfilter_hqx.h b/engines/ags/engine/gfx/gfxfilter_hqx.h new file mode 100644 index 00000000000..d4a0f0de61a --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_hqx.h @@ -0,0 +1,58 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// High quality x2 scaling filter +// +//============================================================================= + +#ifndef __AGS_EE_GFX__HQ2XGFXFILTER_H +#define __AGS_EE_GFX__HQ2XGFXFILTER_H + +#include "gfx/gfxfilter_allegro.h" + +namespace AGS +{ +namespace Engine +{ +namespace ALSW +{ + +class HqxGfxFilter : public AllegroGfxFilter +{ +public: + HqxGfxFilter(); + ~HqxGfxFilter() override; + + const GfxFilterInfo &GetInfo() const override; + + bool Initialize(const int color_depth, String &err_str) override; + Bitmap *InitVirtualScreen(Bitmap *screen, const Size src_size, const Rect dst_rect) override; + Bitmap *ShutdownAndReturnRealScreen() override; + + static const GfxFilterInfo FilterInfo; + +protected: + Bitmap *PreRenderPass(Bitmap *toRender) override; + + typedef void (*PfnHqx)(unsigned char *in, unsigned char *out, int src_w, int src_h, int bpl); + + PfnHqx _pfnHqx; + Bitmap *_hqxScalingBuffer; +}; + +} // namespace ALSW +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__HQ2XGFXFILTER_H diff --git a/engines/ags/engine/gfx/gfxfilter_ogl.cpp b/engines/ags/engine/gfx/gfxfilter_ogl.cpp new file mode 100644 index 00000000000..e6cb85777a7 --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_ogl.cpp @@ -0,0 +1,51 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS || AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_LINUX + +#include "gfx/gfxfilter_ogl.h" +#include "ogl_headers.h" + +namespace AGS +{ +namespace Engine +{ +namespace OGL +{ + +const GfxFilterInfo OGLGfxFilter::FilterInfo = GfxFilterInfo("StdScale", "Nearest-neighbour"); + +bool OGLGfxFilter::UseLinearFiltering() const +{ + return false; +} + +void OGLGfxFilter::SetFilteringForStandardSprite() +{ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); +} + +const GfxFilterInfo &OGLGfxFilter::GetInfo() const +{ + return FilterInfo; +} + +} // namespace OGL +} // namespace Engine +} // namespace AGS + +#endif // only on Windows, Android and iOS diff --git a/engines/ags/engine/gfx/gfxfilter_ogl.h b/engines/ags/engine/gfx/gfxfilter_ogl.h new file mode 100644 index 00000000000..d863fa42f8f --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_ogl.h @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Dummy OpenGL filter; does nothing useful at the moment +// +//============================================================================= + +#ifndef __AGS_EE_GFX__OGLGFXFILTER_H +#define __AGS_EE_GFX__OGLGFXFILTER_H + +#include "gfx/gfxfilter_scaling.h" + +namespace AGS +{ +namespace Engine +{ +namespace OGL +{ + +class OGLGfxFilter : public ScalingGfxFilter +{ +public: + const GfxFilterInfo &GetInfo() const override; + + virtual bool UseLinearFiltering() const; + virtual void SetFilteringForStandardSprite(); + + static const GfxFilterInfo FilterInfo; +}; + +} // namespace D3D +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__OGLGFXFILTER_H diff --git a/engines/ags/engine/gfx/gfxfilter_scaling.cpp b/engines/ags/engine/gfx/gfxfilter_scaling.cpp new file mode 100644 index 00000000000..50e1834460e --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_scaling.cpp @@ -0,0 +1,47 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "gfx/gfxfilter_scaling.h" + +namespace AGS +{ +namespace Engine +{ + +bool ScalingGfxFilter::Initialize(const int color_depth, String &err_str) +{ + // succeed by default + return true; +} + +void ScalingGfxFilter::UnInitialize() +{ + // do nothing by default +} + +Rect ScalingGfxFilter::SetTranslation(const Size src_size, const Rect dst_rect) +{ + // do not restrict scaling by default + _dstRect = dst_rect; + _scaling.Init(src_size, dst_rect); + return _dstRect; +} + +Rect ScalingGfxFilter::GetDestination() const +{ + return _dstRect; +} + +} // namespace Engine +} // namespace AGS diff --git a/engines/ags/engine/gfx/gfxfilter_scaling.h b/engines/ags/engine/gfx/gfxfilter_scaling.h new file mode 100644 index 00000000000..80290400320 --- /dev/null +++ b/engines/ags/engine/gfx/gfxfilter_scaling.h @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Base class for graphic filter which provides virtual screen scaling +// +//============================================================================= + +#ifndef __AGS_EE_GFX__SCALINGGFXFILTER_H +#define __AGS_EE_GFX__SCALINGGFXFILTER_H + +#include "gfx/gfxfilter.h" +#include "util/scaling.h" + +namespace AGS +{ +namespace Engine +{ + +class ScalingGfxFilter : public IGfxFilter +{ +public: + bool Initialize(const int color_depth, String &err_str) override; + void UnInitialize() override; + Rect SetTranslation(const Size src_size, const Rect dst_rect) override; + Rect GetDestination() const override; + +protected: + Rect _dstRect; + PlaneScaling _scaling; +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__SCALINGGFXFILTER_H diff --git a/engines/ags/engine/gfx/gfxmodelist.h b/engines/ags/engine/gfx/gfxmodelist.h new file mode 100644 index 00000000000..281c5c06ddd --- /dev/null +++ b/engines/ags/engine/gfx/gfxmodelist.h @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Supported graphics mode interface +// +//============================================================================= +#ifndef __AGS_EE_GFX__GFXMODELIST_H +#define __AGS_EE_GFX__GFXMODELIST_H + +#include "core/types.h" +#include "gfx/gfxdefines.h" + +namespace AGS +{ +namespace Engine +{ + +class IGfxModeList +{ +public: + virtual ~IGfxModeList() = default; + virtual int GetModeCount() const = 0; + virtual bool GetMode(int index, DisplayMode &mode) const = 0; +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__GFXMODELIST_H diff --git a/engines/ags/engine/gfx/graphicsdriver.h b/engines/ags/engine/gfx/graphicsdriver.h new file mode 100644 index 00000000000..f2d4e0d4759 --- /dev/null +++ b/engines/ags/engine/gfx/graphicsdriver.h @@ -0,0 +1,190 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Graphics driver interface +// +//============================================================================= + +#ifndef __AGS_EE_GFX__GRAPHICSDRIVER_H +#define __AGS_EE_GFX__GRAPHICSDRIVER_H + +#include +#include "gfx/gfxdefines.h" +#include "gfx/gfxmodelist.h" +#include "util/geometry.h" + +namespace AGS +{ + +namespace Common +{ + class Bitmap; + typedef std::shared_ptr PBitmap; +} + +namespace Engine +{ + +// Forward declaration +class IDriverDependantBitmap; +class IGfxFilter; +typedef std::shared_ptr PGfxFilter; +using Common::PBitmap; + +enum TintMethod +{ + TintReColourise = 0, + TintSpecifyMaximum = 1 +}; + +enum VideoSkipType +{ + VideoSkipNone = 0, + VideoSkipEscape = 1, + VideoSkipAnyKey = 2, + VideoSkipKeyOrMouse = 3 +}; + +// Sprite transformation +// TODO: combine with stretch parameters in the IDriverDependantBitmap? +struct SpriteTransform +{ + // Translate + int X, Y; + float ScaleX, ScaleY; + float Rotate; // angle, in radians + + SpriteTransform() + : X(0), Y(0), ScaleX(1.f), ScaleY(1.f), Rotate(0.f) {} + + SpriteTransform(int x, int y, float scalex = 1.0f, float scaley = 1.0f, float rotate = 0.0f) + : X(x), Y(y), ScaleX(scalex), ScaleY(scaley), Rotate(rotate) {} +}; + +typedef void (*GFXDRV_CLIENTCALLBACK)(); +typedef bool (*GFXDRV_CLIENTCALLBACKXY)(int x, int y); +typedef void (*GFXDRV_CLIENTCALLBACKINITGFX)(void *data); +// Called if the rendering surface was resized by the external code (library). +// Mainly for Android and iOS ports; they are currently written in such way that +// the actual rendering surface size is redefined after IGraphicsDriver initialization. +typedef void (*GFXDRV_CLIENTCALLBACKSURFACEUPDATE)(); + +class IGraphicsDriver +{ +public: + virtual const char*GetDriverName() = 0; + virtual const char*GetDriverID() = 0; + virtual void SetTintMethod(TintMethod method) = 0; + // Initialize given display mode + virtual bool SetDisplayMode(const DisplayMode &mode, volatile int *loopTimer) = 0; + // Gets if a graphics mode was initialized + virtual bool IsModeSet() const = 0; + // Set the size of the native image size + virtual bool SetNativeSize(const Size &src_size) = 0; + virtual bool IsNativeSizeValid() const = 0; + // Set game render frame and translation + virtual bool SetRenderFrame(const Rect &dst_rect) = 0; + virtual bool IsRenderFrameValid() const = 0; + // Report which color depth options are best suited for the given native color depth + virtual int GetDisplayDepthForNativeDepth(int native_color_depth) const = 0; + virtual IGfxModeList *GetSupportedModeList(int color_depth) = 0; + virtual bool IsModeSupported(const DisplayMode &mode) = 0; + virtual DisplayMode GetDisplayMode() const = 0; + virtual PGfxFilter GetGraphicsFilter() const = 0; + virtual Size GetNativeSize() const = 0; + virtual Rect GetRenderDestination() const = 0; + virtual void SetCallbackForPolling(GFXDRV_CLIENTCALLBACK callback) = 0; + // TODO: get rid of draw screen callback at some point when all fade functions are more or less grouped in one + virtual void SetCallbackToDrawScreen(GFXDRV_CLIENTCALLBACK callback, GFXDRV_CLIENTCALLBACK post_callback) = 0; + virtual void SetCallbackOnInit(GFXDRV_CLIENTCALLBACKINITGFX callback) = 0; + virtual void SetCallbackOnSurfaceUpdate(GFXDRV_CLIENTCALLBACKSURFACEUPDATE) = 0; + // The NullSprite callback is called in the main render loop when a + // null sprite is encountered. You can use this to hook into the rendering + // process. + virtual void SetCallbackForNullSprite(GFXDRV_CLIENTCALLBACKXY callback) = 0; + // Clears the screen rectangle. The coordinates are expected in the **native game resolution**. + virtual void ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse) = 0; + // Gets closest recommended bitmap format (currently - only color depth) for the given original format. + // Engine needs to have game bitmaps brought to the certain range of formats, easing conversion into the video bitmaps. + virtual int GetCompatibleBitmapFormat(int color_depth) = 0; + virtual IDriverDependantBitmap* CreateDDBFromBitmap(Common::Bitmap *bitmap, bool hasAlpha, bool opaque = false) = 0; + virtual void UpdateDDBFromBitmap(IDriverDependantBitmap* bitmapToUpdate, Common::Bitmap *bitmap, bool hasAlpha) = 0; + virtual void DestroyDDB(IDriverDependantBitmap* bitmap) = 0; + + // Prepares next sprite batch, a list of sprites with defined viewport and optional + // global model transformation; all subsequent calls to DrawSprite will be adding + // sprites to this batch's list. + virtual void BeginSpriteBatch(const Rect &viewport, const SpriteTransform &transform, + const Point offset = Point(), GlobalFlipType flip = kFlip_None, PBitmap surface = nullptr) = 0; + // Adds sprite to the active batch + virtual void DrawSprite(int x, int y, IDriverDependantBitmap* bitmap) = 0; + // Adds fade overlay fx to the active batch + virtual void SetScreenFade(int red, int green, int blue) = 0; + // Adds tint overlay fx to the active batch + // TODO: redesign this to allow various post-fx per sprite batch? + virtual void SetScreenTint(int red, int green, int blue) = 0; + // Clears all sprite batches, resets batch counter + virtual void ClearDrawLists() = 0; + virtual void RenderToBackBuffer() = 0; + virtual void Render() = 0; + // Renders with additional final offset and flip + // TODO: leftover from old code, solely for software renderer; remove when + // software mode either discarded or scene node graph properly implemented. + virtual void Render(int xoff, int yoff, GlobalFlipType flip) = 0; + // Copies contents of the game screen into bitmap using simple blit or pixel copy. + // Bitmap must be of supported size and pixel format. If it's not the method will + // fail and optionally write wanted destination format into 'want_fmt' pointer. + virtual bool GetCopyOfScreenIntoBitmap(Common::Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt = nullptr) = 0; + virtual void EnableVsyncBeforeRender(bool enabled) = 0; + virtual void Vsync() = 0; + // Enables or disables rendering mode that draws sprite list directly into + // the final resolution, as opposed to drawing to native-resolution buffer + // and scaling to final frame. The effect may be that sprites that are + // drawn with additional fractional scaling will appear more detailed than + // the rest of the game. The effect is stronger for the low-res games being + // rendered in the high-res mode. + virtual void RenderSpritesAtScreenResolution(bool enabled, int supersampling = 1) = 0; + // TODO: move fade-in/out/boxout functions out of the graphics driver!! make everything render through + // main drawing procedure. Since currently it does not - we need to init our own sprite batch + // internally to let it set up correct viewport settings instead of relying on a chance. + // Runs fade-out animation in a blocking manner. + virtual void FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) = 0; + // Runs fade-in animation in a blocking manner. + virtual void FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue) = 0; + // Runs box-out animation in a blocking manner. + virtual void BoxOutEffect(bool blackingOut, int speed, int delay) = 0; + virtual bool PlayVideo(const char *filename, bool useAVISound, VideoSkipType skipType, bool stretchToFullScreen) { return false; } + virtual void UseSmoothScaling(bool enabled) = 0; + virtual bool SupportsGammaControl() = 0; + virtual void SetGamma(int newGamma) = 0; + // Returns the virtual screen. Will return NULL if renderer does not support memory backbuffer. + // In normal case you should use GetStageBackBuffer() instead. + virtual Common::Bitmap* GetMemoryBackBuffer() = 0; + // Sets custom backbuffer bitmap to render to. + // Passing NULL pointer will tell renderer to switch back to its original virtual screen. + // Note that only software renderer supports this. + virtual void SetMemoryBackBuffer(Common::Bitmap *backBuffer) = 0; + // Returns memory backbuffer for the current rendering stage (or base virtual screen if called outside of render pass). + // All renderers should support this. + virtual Common::Bitmap* GetStageBackBuffer() = 0; + virtual bool RequiresFullRedrawEachFrame() = 0; + virtual bool HasAcceleratedTransform() = 0; + virtual bool UsesMemoryBackBuffer() = 0; + virtual ~IGraphicsDriver() = default; +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__GRAPHICSDRIVER_H diff --git a/engines/ags/engine/gfx/hq2x3x.h b/engines/ags/engine/gfx/hq2x3x.h new file mode 100644 index 00000000000..3560d168910 --- /dev/null +++ b/engines/ags/engine/gfx/hq2x3x.h @@ -0,0 +1,30 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_HQ2X3X_H +#define __AC_HQ2X3X_H + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_ANDROID +void InitLUTs(){} +void hq2x_32( unsigned char * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ){} +void hq3x_32( unsigned char * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ){} +#else +void InitLUTs(); +void hq2x_32( unsigned char * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ); +void hq3x_32( unsigned char * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ); +#endif + +#endif // __AC_HQ2X3X_H \ No newline at end of file diff --git a/engines/ags/engine/gfx/ogl_headers.h b/engines/ags/engine/gfx/ogl_headers.h new file mode 100644 index 00000000000..5cf20136c5d --- /dev/null +++ b/engines/ags/engine/gfx/ogl_headers.h @@ -0,0 +1,78 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// OpenGL includes and definitions for various platforms +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS +#include +#include +#include + +#include "glad/glad.h" +#include "glad/glad_wgl.h" + +#elif AGS_PLATFORM_OS_LINUX +#include +#include +#include + +#include "glad/glad.h" +#include "glad/glad_glx.h" + +#elif AGS_PLATFORM_OS_ANDROID + +#include +#include + +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES +#endif + +// TODO: we probably should not use GLExt since we use GLES2 +#include + +#define HDC void* +#define HGLRC void* +#define HWND void* +#define HINSTANCE void* + +#elif AGS_PLATFORM_OS_IOS + +#include +#include + +#ifndef GL_GLEXT_PROTOTYPES +#define GL_GLEXT_PROTOTYPES +#endif + +#include + +#define HDC void* +#define HGLRC void* +#define HWND void* +#define HINSTANCE void* + +#else + +#error "opengl: unsupported platform" + +#endif + +#ifndef GLAPI +#define GLAD_GL_VERSION_2_0 (0) +#endif diff --git a/engines/ags/engine/gui/animatingguibutton.cpp b/engines/ags/engine/gui/animatingguibutton.cpp new file mode 100644 index 00000000000..fb370df53b5 --- /dev/null +++ b/engines/ags/engine/gui/animatingguibutton.cpp @@ -0,0 +1,44 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "gui/animatingguibutton.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +void AnimatingGUIButton::ReadFromFile(Stream *in) +{ + buttonid = in->ReadInt16(); + ongui = in->ReadInt16(); + onguibut = in->ReadInt16(); + view = in->ReadInt16(); + loop = in->ReadInt16(); + frame = in->ReadInt16(); + speed = in->ReadInt16(); + repeat = in->ReadInt16(); + wait = in->ReadInt16(); +} + +void AnimatingGUIButton::WriteToFile(Stream *out) +{ + out->WriteInt16(buttonid); + out->WriteInt16(ongui); + out->WriteInt16(onguibut); + out->WriteInt16(view); + out->WriteInt16(loop); + out->WriteInt16(frame); + out->WriteInt16(speed); + out->WriteInt16(repeat); + out->WriteInt16(wait); +} diff --git a/engines/ags/engine/gui/animatingguibutton.h b/engines/ags/engine/gui/animatingguibutton.h new file mode 100644 index 00000000000..7cf5fc7b1c3 --- /dev/null +++ b/engines/ags/engine/gui/animatingguibutton.h @@ -0,0 +1,38 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_GUI__ANIMATINGGUIBUTTON_H +#define __AGS_EE_GUI__ANIMATINGGUIBUTTON_H + +#include "ac/runtime_defines.h" + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +struct AnimatingGUIButton { + // index into guibuts array, GUI, button + short buttonid, ongui, onguibut; + // current animation status + short view, loop, frame; + short speed, repeat, wait; + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); +}; + +#endif // __AGS_EE_GUI__ANIMATINGGUIBUTTON_H diff --git a/engines/ags/engine/gui/cscidialog.cpp b/engines/ags/engine/gui/cscidialog.cpp new file mode 100644 index 00000000000..c190bf3257e --- /dev/null +++ b/engines/ags/engine/gui/cscidialog.cpp @@ -0,0 +1,315 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "util/wgt2allg.h" +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/gamesetup.h" +#include "ac/gamestate.h" +#include "ac/gui.h" +#include "ac/keycode.h" +#include "ac/mouse.h" +#include "ac/sys_events.h" +#include "ac/runtime_defines.h" +#include "font/fonts.h" +#include "gui/cscidialog.h" +#include "gui/guidialog.h" +#include "gui/guimain.h" +#include "gui/mycontrols.h" +#include "main/game_run.h" +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" +#include "media/audio/audio_system.h" +#include "platform/base/agsplatformdriver.h" +#include "ac/timer.h" + +using AGS::Common::Bitmap; +namespace BitmapHelper = AGS::Common::BitmapHelper; + +extern char ignore_bounds; // from mousew32 +extern IGraphicsDriver *gfxDriver; +extern GameSetup usetup; + + +//----------------------------------------------------------------------------- +// DIALOG SYSTEM STUFF below + +int windowbackgroundcolor = COL254, pushbuttondarkcolor = COL255; +int pushbuttonlightcolor = COL253; +int topwindowhandle = -1; +int cbuttfont; + +int acdialog_font; + +int smcode = 0; + +#define MAXCONTROLS 20 +#define MAXSCREENWINDOWS 5 +NewControl *vobjs[MAXCONTROLS]; +OnScreenWindow oswi[MAXSCREENWINDOWS]; + +int controlid = 0; + + +//----------------------------------------------------------------------------- + +void __my_wbutt(Bitmap *ds, int x1, int y1, int x2, int y2) +{ + color_t draw_color = ds->GetCompatibleColor(COL254); //wsetcolor(15); + ds->FillRect(Rect(x1, y1, x2, y2), draw_color); + draw_color = ds->GetCompatibleColor(0); + ds->DrawRect(Rect(x1, y1, x2, y2), draw_color); +} + +//----------------------------------------------------------------------------- + +OnScreenWindow::OnScreenWindow() +{ + x = y = 0; + handle = -1; + oldtop = -1; +} + +int CSCIGetVersion() +{ + return 0x0100; +} + +int windowcount = 0, curswas = 0; +int win_x = 0, win_y = 0, win_width = 0, win_height = 0; +int CSCIDrawWindow(int xx, int yy, int wid, int hit) +{ + ignore_bounds++; + multiply_up(&xx, &yy, &wid, &hit); + int drawit = -1; + for (int aa = 0; aa < MAXSCREENWINDOWS; aa++) { + if (oswi[aa].handle < 0) { + drawit = aa; + break; + } + } + + if (drawit < 0) + quit("Too many windows created."); + + windowcount++; + // ags_domouse(DOMOUSE_DISABLE); + xx -= 2; + yy -= 2; + wid += 4; + hit += 4; + Bitmap *ds = prepare_gui_screen(xx, yy, wid, hit, true); + oswi[drawit].x = xx; + oswi[drawit].y = yy; + __my_wbutt(ds, 0, 0, wid - 1, hit - 1); // wbutt goes outside its area + // ags_domouse(DOMOUSE_ENABLE); + oswi[drawit].oldtop = topwindowhandle; + topwindowhandle = drawit; + oswi[drawit].handle = topwindowhandle; + win_x = xx; + win_y = yy; + win_width = wid; + win_height = hit; + return drawit; +} + +void CSCIEraseWindow(int handl) +{ + // ags_domouse(DOMOUSE_DISABLE); + ignore_bounds--; + topwindowhandle = oswi[handl].oldtop; + oswi[handl].handle = -1; + // ags_domouse(DOMOUSE_ENABLE); + windowcount--; + clear_gui_screen(); +} + +int CSCIWaitMessage(CSCIMessage * cscim) +{ + for (int uu = 0; uu < MAXCONTROLS; uu++) { + if (vobjs[uu] != nullptr) { + // ags_domouse(DOMOUSE_DISABLE); + vobjs[uu]->drawifneeded(); + // ags_domouse(DOMOUSE_ENABLE); + } + } + + prepare_gui_screen(win_x, win_y, win_width, win_height, true); + + while (1) { + update_audio_system_on_game_loop(); + refresh_gui_screen(); + + cscim->id = -1; + cscim->code = 0; + smcode = 0; + int keywas; + if (run_service_key_controls(keywas) && !play.IsIgnoringInput()) { + if (keywas == 13) { + cscim->id = finddefaultcontrol(CNF_DEFAULT); + cscim->code = CM_COMMAND; + } else if (keywas == 27) { + cscim->id = finddefaultcontrol(CNF_CANCEL); + cscim->code = CM_COMMAND; + } else if ((keywas < 32) && (keywas != 8)) ; + else if ((keywas >= 372) & (keywas <= 381) & (finddefaultcontrol(CNT_LISTBOX) >= 0)) + vobjs[finddefaultcontrol(CNT_LISTBOX)]->processmessage(CTB_KEYPRESS, keywas, 0); + else if (finddefaultcontrol(CNT_TEXTBOX) >= 0) + vobjs[finddefaultcontrol(CNT_TEXTBOX)]->processmessage(CTB_KEYPRESS, keywas, 0); + + if (cscim->id < 0) { + cscim->code = CM_KEYPRESS; + cscim->wParam = keywas; + } + } + + int mbut, mwheelz; + if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0 && !play.IsIgnoringInput()) { + if (checkcontrols()) { + cscim->id = controlid; + cscim->code = CM_COMMAND; + } + } + + if (smcode) { + cscim->code = smcode; + cscim->id = controlid; + } + + if (cscim->code > 0) + break; + + WaitForNextFrame(); + } + + return 0; +} + +int CSCICreateControl(int typeandflags, int xx, int yy, int wii, int hii, const char *title) +{ + multiply_up(&xx, &yy, &wii, &hii); + int usec = -1; + for (int hh = 1; hh < MAXCONTROLS; hh++) { + if (vobjs[hh] == nullptr) { + usec = hh; + break; + } + } + + if (usec < 0) + quit("Too many controls created"); + + int type = typeandflags & 0x00ff; // 256 control types + if (type == CNT_PUSHBUTTON) { + if (wii == -1) + wii = wgettextwidth(title, cbuttfont) + 20; + + vobjs[usec] = new MyPushButton(xx, yy, wii, hii, title); + + } else if (type == CNT_LISTBOX) { + vobjs[usec] = new MyListBox(xx, yy, wii, hii); + } else if (type == CNT_LABEL) { + vobjs[usec] = new MyLabel(xx, yy, wii, title); + } else if (type == CNT_TEXTBOX) { + vobjs[usec] = new MyTextBox(xx, yy, wii, title); + } else + quit("Unknown control type requested"); + + vobjs[usec]->typeandflags = typeandflags; + vobjs[usec]->wlevel = topwindowhandle; + // ags_domouse(DOMOUSE_DISABLE); + vobjs[usec]->draw( get_gui_screen() ); + // ags_domouse(DOMOUSE_ENABLE); + return usec; +} + +void CSCIDeleteControl(int haa) +{ + delete vobjs[haa]; + vobjs[haa] = nullptr; +} + +int CSCISendControlMessage(int haa, int mess, int wPar, long lPar) +{ + if (vobjs[haa] == nullptr) + return -1; + return vobjs[haa]->processmessage(mess, wPar, lPar); +} + +void multiply_up_to_game_res(int *x, int *y) +{ + x[0] = get_fixed_pixel_size(x[0]); + y[0] = get_fixed_pixel_size(y[0]); +} + +// TODO: this is silly, make a uniform formula +void multiply_up(int *x1, int *y1, int *x2, int *y2) +{ + multiply_up_to_game_res(x1, y1); + multiply_up_to_game_res(x2, y2); + + // adjust for 800x600 + if ((GetBaseWidth() == 400) || (GetBaseWidth() == 800)) { + x1[0] = (x1[0] * 5) / 4; + x2[0] = (x2[0] * 5) / 4; + y1[0] = (y1[0] * 3) / 2; + y2[0] = (y2[0] * 3) / 2; + } + else if (GetBaseWidth() == 1024) + { + x1[0] = (x1[0] * 16) / 10; + x2[0] = (x2[0] * 16) / 10; + y1[0] = (y1[0] * 384) / 200; + y2[0] = (y2[0] * 384) / 200; + } +} + +int checkcontrols() +{ + // NOTE: this is because old code was working with full game screen + const int mousex = ::mousex - win_x; + const int mousey = ::mousey - win_y; + + smcode = 0; + for (int kk = 0; kk < MAXCONTROLS; kk++) { + if (vobjs[kk] != nullptr) { + if (vobjs[kk]->mouseisinarea(mousex, mousey)) { + controlid = kk; + return vobjs[kk]->pressedon(mousex, mousey); + } + } + } + return 0; +} + +int finddefaultcontrol(int flagmask) +{ + for (int ff = 0; ff < MAXCONTROLS; ff++) { + if (vobjs[ff] == nullptr) + continue; + + if (vobjs[ff]->wlevel != topwindowhandle) + continue; + + if (vobjs[ff]->typeandflags & flagmask) + return ff; + } + + return -1; +} + +int GetBaseWidth () { + return play.GetUIViewport().GetWidth(); +} diff --git a/engines/ags/engine/gui/cscidialog.h b/engines/ags/engine/gui/cscidialog.h new file mode 100644 index 00000000000..17fbcb6bab4 --- /dev/null +++ b/engines/ags/engine/gui/cscidialog.h @@ -0,0 +1,36 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Legacy built-in GUI dialogs and controls. +// +//============================================================================= +#ifndef __AGS_EE_GUI__CSCIDIALOG_H +#define __AGS_EE_GUI__CSCIDIALOG_H + +#include "gui/guidialoginternaldefs.h" + +int CSCIGetVersion(); +int CSCIDrawWindow(int xx, int yy, int wid, int hit); +void CSCIEraseWindow(int handl); +int CSCIWaitMessage(CSCIMessage * cscim); +int CSCICreateControl(int typeandflags, int xx, int yy, int wii, int hii, const char *title); +void CSCIDeleteControl(int haa); +int CSCISendControlMessage(int haa, int mess, int wPar, long lPar); +void multiply_up_to_game_res(int *x, int *y); +void multiply_up(int *x1, int *y1, int *x2, int *y2); +int checkcontrols(); +int finddefaultcontrol(int flagmask); +int GetBaseWidth (); + +#endif // __AGS_EE_GUI__CSCIDIALOG_H diff --git a/engines/ags/engine/gui/gui_engine.cpp b/engines/ags/engine/gui/gui_engine.cpp new file mode 100644 index 00000000000..3cd5aad16ce --- /dev/null +++ b/engines/ags/engine/gui/gui_engine.cpp @@ -0,0 +1,170 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Implementation from acgui.h and acgui.cpp specific to Engine runtime +// +//============================================================================= + +// Headers, as they are in acgui.cpp +#pragma unmanaged +#include "ac/game_version.h" +#include "ac/system.h" +#include "font/fonts.h" +#include "gui/guimain.h" +#include "gui/guibutton.h" +#include "gui/guilabel.h" +#include "gui/guilistbox.h" +#include "gui/guitextbox.h" +#include +#include "ac/gamesetupstruct.h" +#include "ac/global_translation.h" +#include "ac/string.h" +#include "ac/spritecache.h" +#include "gfx/bitmap.h" +#include "gfx/blender.h" + +using namespace AGS::Common; + +// For engine these are defined in ac.cpp +extern int eip_guiobj; +extern void replace_macro_tokens(const char*, String&); + +// For engine these are defined in acfonts.cpp +extern void ensure_text_valid_for_font(char *, int); +// + +extern SpriteCache spriteset; // in ac_runningame +extern GameSetupStruct game; + +bool GUIMain::HasAlphaChannel() const +{ + if (this->BgImage > 0) + { + // alpha state depends on background image + return is_sprite_alpha(this->BgImage); + } + if (this->BgColor > 0) + { + // not alpha transparent if there is a background color + return false; + } + // transparent background, enable alpha blending + return game.GetColorDepth() >= 24 && + // transparent background have alpha channel only since 3.2.0; + // "classic" gui rendering mode historically had non-alpha transparent backgrounds + // (3.2.0 broke the compatibility, now we restore it) + loaded_game_file_version >= kGameVersion_320 && game.options[OPT_NEWGUIALPHA] != kGuiAlphaRender_Legacy; +} + +//============================================================================= +// Engine-specific implementation split out of acgui.h +//============================================================================= + +void check_font(int *fontnum) +{ + // do nothing +} + +//============================================================================= +// Engine-specific implementation split out of acgui.cpp +//============================================================================= + +int get_adjusted_spritewidth(int spr) +{ + return spriteset[spr]->GetWidth(); +} + +int get_adjusted_spriteheight(int spr) +{ + return spriteset[spr]->GetHeight(); +} + +bool is_sprite_alpha(int spr) +{ + return ((game.SpriteInfos[spr].Flags & SPF_ALPHACHANNEL) != 0); +} + +void set_eip_guiobj(int eip) +{ + eip_guiobj = eip; +} + +int get_eip_guiobj() +{ + return eip_guiobj; +} + +bool outlineGuiObjects = false; + +namespace AGS +{ +namespace Common +{ + +bool GUIObject::IsClickable() const +{ + return (Flags & kGUICtrl_Clickable) != 0; +} + +void GUILabel::PrepareTextToDraw() +{ + replace_macro_tokens(Flags & kGUICtrl_Translated ? String(get_translation(Text)) : Text, _textToDraw); +} + +size_t GUILabel::SplitLinesForDrawing(SplitLines &lines) +{ + // Use the engine's word wrap tool, to have hebrew-style writing and other features + return break_up_text_into_lines(_textToDraw, lines, Width, Font); +} + +void GUITextBox::DrawTextBoxContents(Bitmap *ds, color_t text_color) +{ + wouttext_outline(ds, X + 1 + get_fixed_pixel_size(1), Y + 1 + get_fixed_pixel_size(1), Font, text_color, Text); + if (IsGUIEnabled(this)) + { + // draw a cursor + int draw_at_x = wgettextwidth(Text, Font) + X + 3; + int draw_at_y = Y + 1 + getfontheight(Font); + ds->DrawRect(Rect(draw_at_x, draw_at_y, draw_at_x + get_fixed_pixel_size(5), draw_at_y + (get_fixed_pixel_size(1) - 1)), text_color); + } +} + +void GUIListBox::DrawItemsFix() +{ + // do nothing +} + +void GUIListBox::DrawItemsUnfix() +{ + // do nothing +} + +void GUIListBox::PrepareTextToDraw(const String &text) +{ + if (Flags & kGUICtrl_Translated) + _textToDraw = get_translation(text); + else + _textToDraw = text; +} + +void GUIButton::PrepareTextToDraw() +{ + if (Flags & kGUICtrl_Translated) + _textToDraw = get_translation(_text); + else + _textToDraw = _text; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/engine/gui/guidialog.cpp b/engines/ags/engine/gui/guidialog.cpp new file mode 100644 index 00000000000..7baa314387b --- /dev/null +++ b/engines/ags/engine/gui/guidialog.cpp @@ -0,0 +1,533 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "gui/guidialog.h" + +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/game.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "gui/cscidialog.h" +#include //isdigit() +#include "gfx/bitmap.h" +#include "gfx/graphicsdriver.h" +#include "debug/debug_log.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern IGraphicsDriver *gfxDriver; +extern GameSetup usetup; +extern GameSetupStruct game; + +namespace { + +// TODO: store drawing surface inside old gui classes instead +int windowPosX, windowPosY, windowPosWidth, windowPosHeight; +Bitmap *windowBuffer; +IDriverDependantBitmap *dialogDDB; + +#undef MAXSAVEGAMES +#define MAXSAVEGAMES 20 +DisplayProperties dispp; +char *lpTemp, *lpTemp2; +char bufTemp[260], buffer2[260]; +int numsaves = 0, toomanygames; +int filenumbers[MAXSAVEGAMES]; +unsigned long filedates[MAXSAVEGAMES]; + +CSCIMessage smes; + +char buff[200]; +int myscrnwid = 320, myscrnhit = 200; + +} + +char *get_gui_dialog_buffer() +{ + return buffer2; +} + +// +// TODO: rewrite the whole thing to work inside the main game update and render loop! +// + +Bitmap *prepare_gui_screen(int x, int y, int width, int height, bool opaque) +{ + windowPosX = x; + windowPosY = y; + windowPosWidth = width; + windowPosHeight = height; + if (windowBuffer) + { + windowBuffer = recycle_bitmap(windowBuffer, windowBuffer->GetColorDepth(), windowPosWidth, windowPosHeight, !opaque); + } + else + { + windowBuffer = BitmapHelper::CreateBitmap(windowPosWidth, windowPosHeight, game.GetColorDepth()); + windowBuffer = ReplaceBitmapWithSupportedFormat(windowBuffer); + } + dialogDDB = recycle_ddb_bitmap(dialogDDB, windowBuffer, false, opaque); + return windowBuffer; +} + +Bitmap *get_gui_screen() +{ + return windowBuffer; +} + +void clear_gui_screen() +{ + if (dialogDDB) + gfxDriver->DestroyDDB(dialogDDB); + dialogDDB = nullptr; + delete windowBuffer; + windowBuffer = nullptr; +} + +void refresh_gui_screen() +{ + gfxDriver->UpdateDDBFromBitmap(dialogDDB, windowBuffer, false); + render_graphics(dialogDDB, windowPosX, windowPosY); +} + +int loadgamedialog() +{ + const int wnd_width = 200; + const int wnd_height = 120; + const int boxleft = myscrnwid / 2 - wnd_width / 2; + const int boxtop = myscrnhit / 2 - wnd_height / 2; + const int buttonhit = usetup.textheight + 5; + + int handl = CSCIDrawWindow(boxleft, boxtop, wnd_width, wnd_height); + int ctrlok = + CSCICreateControl(CNT_PUSHBUTTON | CNF_DEFAULT, 135, 5, 60, 10, get_global_message(MSG_RESTORE)); + int ctrlcancel = + CSCICreateControl(CNT_PUSHBUTTON | CNF_CANCEL, 135, 5 + buttonhit, 60, 10, + get_global_message(MSG_CANCEL)); + int ctrllist = CSCICreateControl(CNT_LISTBOX, 10, 30, 120, 80, nullptr); + int ctrltex1 = CSCICreateControl(CNT_LABEL, 10, 5, 120, 0, get_global_message(MSG_SELECTLOAD)); + CSCISendControlMessage(ctrllist, CLB_CLEAR, 0, 0); + + preparesavegamelist(ctrllist); + CSCIMessage mes; + lpTemp = nullptr; + int toret = -1; + while (1) { + CSCIWaitMessage(&mes); //printf("mess: %d, id %d ",mes.code,mes.id); + if (mes.code == CM_COMMAND) { + if (mes.id == ctrlok) { + int cursel = CSCISendControlMessage(ctrllist, CLB_GETCURSEL, 0, 0); + if ((cursel >= numsaves) | (cursel < 0)) + lpTemp = nullptr; + else { + toret = filenumbers[cursel]; + String path = get_save_game_path(toret); + strcpy(bufTemp, path); + lpTemp = &bufTemp[0]; + } + } else if (mes.id == ctrlcancel) { + lpTemp = nullptr; + } + + break; + } + } + + CSCIDeleteControl(ctrltex1); + CSCIDeleteControl(ctrllist); + CSCIDeleteControl(ctrlok); + CSCIDeleteControl(ctrlcancel); + CSCIEraseWindow(handl); + return toret; +} + +int savegamedialog() +{ + char okbuttontext[50]; + strcpy(okbuttontext, get_global_message(MSG_SAVEBUTTON)); + char labeltext[200]; + strcpy(labeltext, get_global_message(MSG_SAVEDIALOG)); + const int wnd_width = 200; + const int wnd_height = 120; + const int boxleft = myscrnwid / 2 - wnd_width / 2; + const int boxtop = myscrnhit / 2 - wnd_height / 2; + const int buttonhit = usetup.textheight + 5; + int labeltop = 5; + + int handl = CSCIDrawWindow(boxleft, boxtop, wnd_width, wnd_height); + int ctrlcancel = + CSCICreateControl(CNT_PUSHBUTTON | CNF_CANCEL, 135, 5 + buttonhit, 60, 10, + get_global_message(MSG_CANCEL)); + int ctrllist = CSCICreateControl(CNT_LISTBOX, 10, 40, 120, 80, nullptr); + int ctrltbox = 0; + + CSCISendControlMessage(ctrllist, CLB_CLEAR, 0, 0); // clear the list box + preparesavegamelist(ctrllist); + if (toomanygames) { + strcpy(okbuttontext, get_global_message(MSG_REPLACE)); + strcpy(labeltext, get_global_message(MSG_MUSTREPLACE)); + labeltop = 2; + } else + ctrltbox = CSCICreateControl(CNT_TEXTBOX, 10, 29, 120, 0, nullptr); + + int ctrlok = CSCICreateControl(CNT_PUSHBUTTON | CNF_DEFAULT, 135, 5, 60, 10, okbuttontext); + int ctrltex1 = CSCICreateControl(CNT_LABEL, 10, labeltop, 120, 0, labeltext); + CSCIMessage mes; + + lpTemp = nullptr; + if (numsaves > 0) + CSCISendControlMessage(ctrllist, CLB_GETTEXT, 0, (long)&buffer2[0]); + else + buffer2[0] = 0; + + CSCISendControlMessage(ctrltbox, CTB_SETTEXT, 0, (long)&buffer2[0]); + + int toret = -1; + while (1) { + CSCIWaitMessage(&mes); //printf("mess: %d, id %d ",mes.code,mes.id); + if (mes.code == CM_COMMAND) { + if (mes.id == ctrlok) { + int cursell = CSCISendControlMessage(ctrllist, CLB_GETCURSEL, 0, 0); + CSCISendControlMessage(ctrltbox, CTB_GETTEXT, 0, (long)&buffer2[0]); + + if (numsaves > 0) + CSCISendControlMessage(ctrllist, CLB_GETTEXT, cursell, (long)&bufTemp[0]); + else + strcpy(bufTemp, "_NOSAVEGAMENAME"); + + if (toomanygames) { + int nwhand = CSCIDrawWindow(boxleft + 5, boxtop + 20, 190, 65); + int lbl1 = + CSCICreateControl(CNT_LABEL, 15, 5, 160, 0, get_global_message(MSG_REPLACEWITH1)); + int lbl2 = CSCICreateControl(CNT_LABEL, 25, 14, 160, 0, bufTemp); + int lbl3 = + CSCICreateControl(CNT_LABEL, 15, 25, 160, 0, get_global_message(MSG_REPLACEWITH2)); + int txt1 = CSCICreateControl(CNT_TEXTBOX, 15, 35, 160, 0, bufTemp); + int btnOk = + CSCICreateControl(CNT_PUSHBUTTON | CNF_DEFAULT, 25, 50, 60, 10, + get_global_message(MSG_REPLACE)); + int btnCancel = + CSCICreateControl(CNT_PUSHBUTTON | CNF_CANCEL, 95, 50, 60, 10, + get_global_message(MSG_CANCEL)); + + CSCIMessage cmes; + do { + CSCIWaitMessage(&cmes); + } while (cmes.code != CM_COMMAND); + + CSCISendControlMessage(txt1, CTB_GETTEXT, 0, (long)&buffer2[0]); + CSCIDeleteControl(btnCancel); + CSCIDeleteControl(btnOk); + CSCIDeleteControl(txt1); + CSCIDeleteControl(lbl3); + CSCIDeleteControl(lbl2); + CSCIDeleteControl(lbl1); + CSCIEraseWindow(nwhand); + bufTemp[0] = 0; + + if (cmes.id == btnCancel) { + lpTemp = nullptr; + break; + } else + toret = filenumbers[cursell]; + + } + else if (strcmp(buffer2, bufTemp) != 0) { // create a new game (description different) + int highestnum = 0; + for (int pp = 0; pp < numsaves; pp++) { + if (filenumbers[pp] > highestnum) + highestnum = filenumbers[pp]; + } + + if (highestnum > 90) + quit("Save game directory overflow"); + + toret = highestnum + 1; + String path = get_save_game_path(toret); + strcpy(bufTemp, path); + } + else { + toret = filenumbers[cursell]; + bufTemp[0] = 0; + } + + if (bufTemp[0] == 0) + { + String path = get_save_game_path(toret); + strcpy(bufTemp, path); + } + + lpTemp = &bufTemp[0]; + lpTemp2 = &buffer2[0]; + } else if (mes.id == ctrlcancel) { + lpTemp = nullptr; + } + break; + } else if (mes.code == CM_SELCHANGE) { + int cursel = CSCISendControlMessage(ctrllist, CLB_GETCURSEL, 0, 0); + if (cursel >= 0) { + CSCISendControlMessage(ctrllist, CLB_GETTEXT, cursel, (long)&buffer2[0]); + CSCISendControlMessage(ctrltbox, CTB_SETTEXT, 0, (long)&buffer2[0]); + } + } + } + + CSCIDeleteControl(ctrltbox); + CSCIDeleteControl(ctrltex1); + CSCIDeleteControl(ctrllist); + CSCIDeleteControl(ctrlok); + CSCIDeleteControl(ctrlcancel); + CSCIEraseWindow(handl); + return toret; +} + +void preparesavegamelist(int ctrllist) +{ + numsaves = 0; + toomanygames = 0; + al_ffblk ffb; + int bufix = 0; + + String svg_dir = get_save_game_directory(); + String svg_suff = get_save_game_suffix(); + String searchPath = String::FromFormat("%s""agssave.*%s", svg_dir.GetCStr(), svg_suff.GetCStr()); + + int don = al_findfirst(searchPath, &ffb, -1); + while (!don) { + bufix = 0; + if (numsaves >= MAXSAVEGAMES) { + toomanygames = 1; + break; + } + + // only list games .000 to .099 (to allow higher slots for other purposes) + if (strstr(ffb.name, ".0") == nullptr) { + don = al_findnext(&ffb); + continue; + } + + const char *numberExtension = strstr(ffb.name, ".0") + 1; + int sgNumber = atoi(numberExtension); + + String thisGamePath = get_save_game_path(sgNumber); + + // get description + String description; + read_savedgame_description(thisGamePath, description); + + CSCISendControlMessage(ctrllist, CLB_ADDITEM, 0, (long)description.GetCStr()); + // Select the first item + CSCISendControlMessage(ctrllist, CLB_SETCURSEL, 0, 0); + filenumbers[numsaves] = sgNumber; + filedates[numsaves] = (long int)ffb.time; + numsaves++; + don = al_findnext(&ffb); + } + + al_findclose(&ffb); + if (numsaves >= MAXSAVEGAMES) + toomanygames = 1; + + for (int nn = 0; nn < numsaves - 1; nn++) { + for (int kk = 0; kk < numsaves - 1; kk++) { // Date order the games + if (filedates[kk] < filedates[kk + 1]) { // swap them round + CSCISendControlMessage(ctrllist, CLB_GETTEXT, kk, (long)&buff[0]); + CSCISendControlMessage(ctrllist, CLB_GETTEXT, kk + 1, (long)&buffer2[0]); + CSCISendControlMessage(ctrllist, CLB_SETTEXT, kk + 1, (long)&buff[0]); + CSCISendControlMessage(ctrllist, CLB_SETTEXT, kk, (long)&buffer2[0]); + int numtem = filenumbers[kk]; + filenumbers[kk] = filenumbers[kk + 1]; + filenumbers[kk + 1] = numtem; + long numted = filedates[kk]; + filedates[kk] = filedates[kk + 1]; + filedates[kk + 1] = numted; + } + } + } +} + +void enterstringwindow(const char *prompttext, char *stouse) +{ + const int wnd_width = 200; + const int wnd_height = 40; + const int boxleft = 60, boxtop = 80; + int wantCancel = 0; + if (prompttext[0] == '!') { + wantCancel = 1; + prompttext++; + } + + int handl = CSCIDrawWindow(boxleft, boxtop, wnd_width, wnd_height); + int ctrlok = CSCICreateControl(CNT_PUSHBUTTON | CNF_DEFAULT, 135, 5, 60, 10, "OK"); + int ctrlcancel = -1; + if (wantCancel) + ctrlcancel = CSCICreateControl(CNT_PUSHBUTTON | CNF_CANCEL, 135, 20, 60, 10, get_global_message(MSG_CANCEL)); + int ctrltbox = CSCICreateControl(CNT_TEXTBOX, 10, 29, 120, 0, nullptr); + int ctrltex1 = CSCICreateControl(CNT_LABEL, 10, 5, 120, 0, prompttext); + CSCIMessage mes; + + while (1) { + CSCIWaitMessage(&mes); + if (mes.code == CM_COMMAND) { + if (mes.id == ctrlcancel) + buffer2[0] = 0; + else + CSCISendControlMessage(ctrltbox, CTB_GETTEXT, 0, (long)&buffer2[0]); + break; + } + } + + CSCIDeleteControl(ctrltex1); + CSCIDeleteControl(ctrltbox); + CSCIDeleteControl(ctrlok); + if (wantCancel) + CSCIDeleteControl(ctrlcancel); + CSCIEraseWindow(handl); + strcpy(stouse, buffer2); +} + +int enternumberwindow(char *prompttext) +{ + char ourbuf[200]; + enterstringwindow(prompttext, ourbuf); + if (ourbuf[0] == 0) + return -9999; + return atoi(ourbuf); +} + +int roomSelectorWindow(int currentRoom, int numRooms, int*roomNumbers, char**roomNames) +{ + char labeltext[200]; + strcpy(labeltext, get_global_message(MSG_SAVEDIALOG)); + const int wnd_width = 240; + const int wnd_height = 160; + const int boxleft = myscrnwid / 2 - wnd_width / 2; + const int boxtop = myscrnhit / 2 - wnd_height / 2; + const int labeltop = 5; + + int handl = CSCIDrawWindow(boxleft, boxtop, wnd_width, wnd_height); + int ctrllist = CSCICreateControl(CNT_LISTBOX, 10, 40, 220, 100, nullptr); + int ctrlcancel = + CSCICreateControl(CNT_PUSHBUTTON | CNF_CANCEL, 80, 145, 60, 10, "Cancel"); + + CSCISendControlMessage(ctrllist, CLB_CLEAR, 0, 0); // clear the list box + for (int aa = 0; aa < numRooms; aa++) + { + sprintf(buff, "%3d %s", roomNumbers[aa], roomNames[aa]); + CSCISendControlMessage(ctrllist, CLB_ADDITEM, 0, (long)&buff[0]); + if (roomNumbers[aa] == currentRoom) + { + CSCISendControlMessage(ctrllist, CLB_SETCURSEL, aa, 0); + } + } + + int ctrlok = CSCICreateControl(CNT_PUSHBUTTON | CNF_DEFAULT, 10, 145, 60, 10, "OK"); + int ctrltex1 = CSCICreateControl(CNT_LABEL, 10, labeltop, 180, 0, "Choose which room to go to:"); + CSCIMessage mes; + + lpTemp = nullptr; + buffer2[0] = 0; + + int ctrltbox = CSCICreateControl(CNT_TEXTBOX, 10, 29, 120, 0, nullptr); + CSCISendControlMessage(ctrltbox, CTB_SETTEXT, 0, (long)&buffer2[0]); + + int toret = -1; + while (1) { + CSCIWaitMessage(&mes); //printf("mess: %d, id %d ",mes.code,mes.id); + if (mes.code == CM_COMMAND) + { + if (mes.id == ctrlok) + { + CSCISendControlMessage(ctrltbox, CTB_GETTEXT, 0, (long)&buffer2[0]); + if (isdigit(buffer2[0])) + { + toret = atoi(buffer2); + } + } + else if (mes.id == ctrlcancel) + { + } + break; + } + else if (mes.code == CM_SELCHANGE) + { + int cursel = CSCISendControlMessage(ctrllist, CLB_GETCURSEL, 0, 0); + if (cursel >= 0) + { + sprintf(buffer2, "%d", roomNumbers[cursel]); + CSCISendControlMessage(ctrltbox, CTB_SETTEXT, 0, (long)&buffer2[0]); + } + } + } + + CSCIDeleteControl(ctrltbox); + CSCIDeleteControl(ctrltex1); + CSCIDeleteControl(ctrllist); + CSCIDeleteControl(ctrlok); + CSCIDeleteControl(ctrlcancel); + CSCIEraseWindow(handl); + return toret; +} + +int myscimessagebox(const char *lpprompt, char *btn1, char *btn2) +{ + const int wnd_width = 240 - 80; + const int wnd_height = 120 - 80; + const int boxleft = 80; + const int boxtop = 80; + + int windl = CSCIDrawWindow(boxleft, boxtop, wnd_width, wnd_height); + int lbl1 = CSCICreateControl(CNT_LABEL, 10, 5, 150, 0, lpprompt); + int btflag = CNT_PUSHBUTTON; + + if (btn2 == nullptr) + btflag |= CNF_DEFAULT | CNF_CANCEL; + else + btflag |= CNF_DEFAULT; + + int btnQuit = CSCICreateControl(btflag, 10, 25, 60, 10, btn1); + int btnPlay = 0; + + if (btn2 != nullptr) + btnPlay = CSCICreateControl(CNT_PUSHBUTTON | CNF_CANCEL, 85, 25, 60, 10, btn2); + + smes.code = 0; + + do { + CSCIWaitMessage(&smes); + } while (smes.code != CM_COMMAND); + + if (btnPlay) + CSCIDeleteControl(btnPlay); + + CSCIDeleteControl(btnQuit); + CSCIDeleteControl(lbl1); + CSCIEraseWindow(windl); + + if (smes.id == btnQuit) + return 1; + + return 0; +} + +int quitdialog() +{ + char quitbut[50], playbut[50]; + strcpy(quitbut, get_global_message(MSG_QUITBUTTON)); + strcpy(playbut, get_global_message(MSG_PLAYBUTTON)); + return myscimessagebox(get_global_message(MSG_QUITDIALOG), quitbut, playbut); +} diff --git a/engines/ags/engine/gui/guidialog.h b/engines/ags/engine/gui/guidialog.h new file mode 100644 index 00000000000..752edde64d5 --- /dev/null +++ b/engines/ags/engine/gui/guidialog.h @@ -0,0 +1,44 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_GUI__GUIDIALOG_H +#define __AGS_EE_GUI__GUIDIALOG_H + +namespace AGS { namespace Common { class Bitmap; } } + +// Functions for handling hard-coded GUIs +// Prepares GUI bitmaps which will be passed to the renderer's draw chain +AGS::Common::Bitmap *prepare_gui_screen(int x, int y, int width, int height, bool opaque); +AGS::Common::Bitmap *get_gui_screen(); +// Deletes GUI bitmaps +void clear_gui_screen(); +// Draws virtual screen contents on the GUI bitmaps and assignes them to +// the renderer's draw chain +void refresh_gui_screen(); +int loadgamedialog(); +int savegamedialog(); +void preparesavegamelist(int ctrllist); +void enterstringwindow(const char *prompttext, char *stouse); +int enternumberwindow(char *prompttext); +int roomSelectorWindow(int currentRoom, int numRooms, int*roomNumbers, char**roomNames); +int myscimessagebox(const char *lpprompt, char *btn1, char *btn2); +int quitdialog(); + +// last string value in gui dialog. +char *get_gui_dialog_buffer(); + +#endif // __AGS_EE_GUI__GUIDIALOG_H diff --git a/engines/ags/engine/gui/guidialogdefines.h b/engines/ags/engine/gui/guidialogdefines.h new file mode 100644 index 00000000000..548c32ff1f7 --- /dev/null +++ b/engines/ags/engine/gui/guidialogdefines.h @@ -0,0 +1,111 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_GUI__GUIDIALOGDEFINES_H +#define __AGS_EE_GUI__GUIDIALOGDEFINES_H + +#define MSG_RESTORE 984 +#define MSG_CANCEL 985 // "Cancel" +#define MSG_SELECTLOAD 986 // "Select game to restore" +#define MSG_SAVEBUTTON 987 // "Save" +#define MSG_SAVEDIALOG 988 // "Save game name:" +#define MSG_REPLACE 989 // "Replace" +#define MSG_MUSTREPLACE 990 // "The folder is full. you must replace" +#define MSG_REPLACEWITH1 991 // "Replace:" +#define MSG_REPLACEWITH2 992 // "With:" +#define MSG_QUITBUTTON 993 // "Quit" +#define MSG_PLAYBUTTON 994 // "Play" +#define MSG_QUITDIALOG 995 // "Do you want to quit?" + +#include "ac/gamesetup.h" + +/*#define COL251 26 +#define COL252 28 +#define COL253 29 +#define COL254 27 +#define COL255 24*/ +#define COL253 15 +#define COL254 7 +#define COL255 8 + +// ========= DEFINES ======== +// Control types +#define CNT_PUSHBUTTON 0x001 +#define CNT_LISTBOX 0x002 +#define CNT_LABEL 0x003 +#define CNT_TEXTBOX 0x004 +// Control properties +#define CNF_DEFAULT 0x100 +#define CNF_CANCEL 0x200 + +// Dialog messages +#define CM_COMMAND 1 +#define CM_KEYPRESS 2 +#define CM_SELCHANGE 3 +// System messages +#define SM_SAVEGAME 100 +#define SM_LOADGAME 101 +#define SM_QUIT 102 +// System messages (to ADVEN) +#define SM_SETTRANSFERMEM 120 +#define SM_GETINIVALUE 121 +// System messages (to driver) +#define SM_QUERYQUIT 110 +#define SM_KEYPRESS 111 +#define SM_TIMER 112 +// ListBox messages +#define CLB_ADDITEM 1 +#define CLB_CLEAR 2 +#define CLB_GETCURSEL 3 +#define CLB_GETTEXT 4 +#define CLB_SETTEXT 5 +#define CLB_SETCURSEL 6 +// TextBox messages +#define CTB_GETTEXT 1 +#define CTB_SETTEXT 2 + +#define CTB_KEYPRESS 91 + +namespace AGS { namespace Common { class Bitmap; } } +using namespace AGS; // FIXME later + +// ========= STRUCTS ======== +struct DisplayProperties +{ + int width; + int height; + int colors; + int textheight; +}; + +struct CSCIMessage +{ + int code; + int id; + int wParam; +}; + +struct OnScreenWindow +{ + int x, y; + int handle; + int oldtop; + + OnScreenWindow(); +}; + +#endif // __AGS_EE_GUI__GUIDIALOGDEFINES_H \ No newline at end of file diff --git a/engines/ags/engine/gui/guidialoginternaldefs.h b/engines/ags/engine/gui/guidialoginternaldefs.h new file mode 100644 index 00000000000..53755d139a1 --- /dev/null +++ b/engines/ags/engine/gui/guidialoginternaldefs.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_GUI__GUIDIALOGINTERNALDEFS_H +#define __AGS_EE_GUI__GUIDIALOGINTERNALDEFS_H + +#include "gui/guidialogdefines.h" + +#define _export +#ifdef WINAPI +#undef WINAPI +#endif +#define WINAPI +extern int ags_misbuttondown (int but); +#define mbutrelease(X) (!ags_misbuttondown(X)) +#define TEXT_HT usetup.textheight + +#endif // __AGS_EE_GUI__GUIDIALOGINTERNALDEFS_H diff --git a/engines/ags/engine/gui/mycontrols.h b/engines/ags/engine/gui/mycontrols.h new file mode 100644 index 00000000000..89383e5c4b3 --- /dev/null +++ b/engines/ags/engine/gui/mycontrols.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Legacy built-in GUI dialogs and controls. +// +//============================================================================= +#ifndef __AGS_EE_GUI__MYCONTROLS_H +#define __AGS_EE_GUI__MYCONTROLS_H + +#include "gui/mylabel.h" +#include "gui/mylistbox.h" +#include "gui/mypushbutton.h" +#include "gui/mytextbox.h" + +#endif // __AGS_EE_GUI__MYCONTROLS_H diff --git a/engines/ags/engine/gui/mylabel.cpp b/engines/ags/engine/gui/mylabel.cpp new file mode 100644 index 00000000000..1ce06d8f752 --- /dev/null +++ b/engines/ags/engine/gui/mylabel.cpp @@ -0,0 +1,62 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/display.h" +#include "ac/gamesetup.h" +#include "ac/string.h" +#include "font/fonts.h" +#include "gui/guidefines.h" +#include "gui/mylabel.h" +#include "gui/guidialoginternaldefs.h" + +using namespace Common; + +extern GameSetup usetup; + +extern int acdialog_font; + +MyLabel::MyLabel(int xx, int yy, int wii, const char *tee) +{ + strncpy(text, tee, 150); + text[149] = 0; + x = xx; + y = yy; + wid = wii; + hit = TEXT_HT; +} + +void MyLabel::draw(Bitmap *ds) +{ + int cyp = y; + char *teptr = &text[0]; + color_t text_color = ds->GetCompatibleColor(0); + + if (break_up_text_into_lines(teptr, Lines, wid, acdialog_font) == 0) + return; + for (size_t ee = 0; ee < Lines.Count(); ee++) { + wouttext_outline(ds, x, cyp, acdialog_font, text_color, Lines[ee]); + cyp += TEXT_HT; + } +} + +int MyLabel::pressedon(int mousex, int mousey) +{ + return 0; +} + +int MyLabel::processmessage(int mcode, int wParam, long lParam) +{ + return -1; // doesn't support messages +} diff --git a/engines/ags/engine/gui/mylabel.h b/engines/ags/engine/gui/mylabel.h new file mode 100644 index 00000000000..c700790fa0e --- /dev/null +++ b/engines/ags/engine/gui/mylabel.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYLABEL_H +#define __AC_MYLABEL_H + +#include "gui/newcontrol.h" + +struct MyLabel:public NewControl +{ + char text[150]; + MyLabel(int xx, int yy, int wii, const char *tee); + + void draw(Common::Bitmap *ds) override; + + int pressedon(int mousex, int mousey) override; + + int processmessage(int mcode, int wParam, long lParam) override; +}; + +#endif // __AC_MYLABEL_H \ No newline at end of file diff --git a/engines/ags/engine/gui/mylistbox.cpp b/engines/ags/engine/gui/mylistbox.cpp new file mode 100644 index 00000000000..66bba59d663 --- /dev/null +++ b/engines/ags/engine/gui/mylistbox.cpp @@ -0,0 +1,198 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "util/wgt2allg.h" +#include "ac/common.h" +#include "ac/gamesetup.h" +#include "font/fonts.h" +#include "gfx/bitmap.h" +#include "gui/guidialog.h" +#include "gui/guidialoginternaldefs.h" +#include "gui/mylistbox.h" + +using AGS::Common::Bitmap; + +extern GameSetup usetup; +extern int numcurso, hotx, hoty; + +extern int windowbackgroundcolor; +extern int cbuttfont; +extern int smcode; + + MyListBox::MyListBox(int xx, int yy, int wii, int hii) + { + x = xx; + y = yy; + wid = wii; + hit = hii; + hit -= (hit - 4) % TEXT_HT; // resize to multiple of text height + numonscreen = (hit - 4) / TEXT_HT; + items = 0; + topitem = 0; + selected = -1; + memset(itemnames, 0, sizeof(itemnames)); + } + + void MyListBox::clearlist() + { + for (int kk = 0; kk < items; kk++) + free(itemnames[kk]); + + items = 0; + } + + MyListBox::~MyListBox() { + clearlist(); + } + + void MyListBox::draw(Bitmap *ds) + { + color_t draw_color = ds->GetCompatibleColor(windowbackgroundcolor); + ds->FillRect(Rect(x, y, x + wid, y + hit), draw_color); + draw_color = ds->GetCompatibleColor(0); + ds->DrawRect(Rect(x, y, x + wid, y + hit), draw_color); + + int widwas = wid; + wid -= ARROWWIDTH; + ds->DrawLine(Line(x + wid, y, x + wid, y + hit), draw_color); // draw the up/down arrows + ds->DrawLine(Line(x + wid, y + hit / 2, x + widwas, y + hit / 2), draw_color); + + int xmidd = x + wid + (widwas - wid) / 2; + if (topitem < 1) + draw_color = ds->GetCompatibleColor(7); + + ds->DrawLine(Line(xmidd, y + 2, xmidd, y + 10), draw_color); // up arrow + ds->DrawLine(Line(xmidd - 1, y + 3, xmidd + 1, y + 3), draw_color); + ds->DrawLine(Line(xmidd - 2, y + 4, xmidd + 2, y + 4), draw_color); + draw_color = ds->GetCompatibleColor(0); + if (topitem + numonscreen >= items) + draw_color = ds->GetCompatibleColor(7); + + ds->DrawLine(Line(xmidd, y + hit - 10, xmidd, y + hit - 3), draw_color); // down arrow + ds->DrawLine(Line(xmidd - 1, y + hit - 4, xmidd + 1, y + hit - 4), draw_color); + ds->DrawLine(Line(xmidd - 2, y + hit - 5, xmidd + 2, y + hit - 5), draw_color); + draw_color = ds->GetCompatibleColor(0); + + for (int tt = 0; tt < numonscreen; tt++) { + int inum = tt + topitem; + if (inum >= items) + break; + + int thisypos = y + 2 + tt * TEXT_HT; + color_t text_color; + if (inum == selected) { + draw_color = ds->GetCompatibleColor(0); + ds->FillRect(Rect(x, thisypos, x + wid, thisypos + TEXT_HT - 1), draw_color); + text_color = ds->GetCompatibleColor(7); + } + else text_color = ds->GetCompatibleColor(0); + + wouttextxy(ds, x + 2, thisypos, cbuttfont, text_color, itemnames[inum]); + } + wid = widwas; + } + + int MyListBox::pressedon(int mousex, int mousey) + { + if (mousex > x + wid - ARROWWIDTH) { + if ((mousey - y < hit / 2) & (topitem > 0)) + topitem--; + else if ((mousey - y > hit / 2) & (topitem + numonscreen < items)) + topitem++; + + } else { + selected = ((mousey - y) - 2) / TEXT_HT + topitem; + if (selected >= items) + selected = items - 1; + + } + +// ags_domouse(DOMOUSE_DISABLE); + draw(get_gui_screen()); + // ags_domouse(DOMOUSE_ENABLE); + smcode = CM_SELCHANGE; + return 0; + } + + void MyListBox::additem(char *texx) + { + if (items >= MAXLISTITEM) + quit("!CSCIUSER16: Too many items added to listbox"); + itemnames[items] = (char *)malloc(strlen(texx) + 1); + strcpy(itemnames[items], texx); + items++; + needredraw = 1; + } + + int MyListBox::processmessage(int mcode, int wParam, long lParam) + { + if (mcode == CLB_ADDITEM) { + additem((char *)lParam); + } else if (mcode == CLB_CLEAR) + clearlist(); + else if (mcode == CLB_GETCURSEL) + return selected; + else if (mcode == CLB_SETCURSEL) + { + selected = wParam; + + if ((selected < topitem) && (selected >= 0)) + topitem = selected; + + if (topitem + numonscreen <= selected) + topitem = (selected + 1) - numonscreen; + } + else if (mcode == CLB_GETTEXT) + strcpy((char *)lParam, itemnames[wParam]); + else if (mcode == CLB_SETTEXT) { + if (wParam < items) + free(itemnames[wParam]); + + char *newstri = (char *)lParam; + itemnames[wParam] = (char *)malloc(strlen(newstri) + 2); + strcpy(itemnames[wParam], newstri); + + } else if (mcode == CTB_KEYPRESS) { + if ((wParam == 380) && (selected < items - 1)) + selected++; + + if ((wParam == 372) && (selected > 0)) + selected--; + + if (wParam == 373) + selected -= (numonscreen - 1); + + if (wParam == 381) + selected += (numonscreen - 1); + + if ((selected < 0) && (items > 0)) + selected = 0; + + if (selected >= items) + selected = items - 1; + + if ((selected < topitem) & (selected >= 0)) + topitem = selected; + + if (topitem + numonscreen <= selected) + topitem = (selected + 1) - numonscreen; + + drawandmouse(); + smcode = CM_SELCHANGE; + } else + return -1; + + return 0; + } diff --git a/engines/ags/engine/gui/mylistbox.h b/engines/ags/engine/gui/mylistbox.h new file mode 100644 index 00000000000..4ce657bcab6 --- /dev/null +++ b/engines/ags/engine/gui/mylistbox.h @@ -0,0 +1,37 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYLISTBOX_H +#define __AC_MYLISTBOX_H + +#include "gui/newcontrol.h" + +#define MAXLISTITEM 300 +#define ARROWWIDTH 8 + +struct MyListBox:public NewControl +{ + int items, topitem, numonscreen, selected; + char *itemnames[MAXLISTITEM]; + MyListBox(int xx, int yy, int wii, int hii); + void clearlist(); + ~MyListBox() override; + + void draw(Common::Bitmap *ds) override; + int pressedon(int mousex, int mousey) override; + void additem(char *texx); + int processmessage(int mcode, int wParam, long lParam) override; +}; + +#endif // __AC_MYLISTBOX_H \ No newline at end of file diff --git a/engines/ags/engine/gui/mypushbutton.cpp b/engines/ags/engine/gui/mypushbutton.cpp new file mode 100644 index 00000000000..449c6ac8a8b --- /dev/null +++ b/engines/ags/engine/gui/mypushbutton.cpp @@ -0,0 +1,106 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "util/wgt2allg.h" +#include "ac/common.h" +#include "ac/mouse.h" +#include "font/fonts.h" +#include "gui/mypushbutton.h" +#include "gui/guidialog.h" +#include "gui/guidialoginternaldefs.h" +#include "main/game_run.h" +#include "gfx/bitmap.h" +#include "platform/base/agsplatformdriver.h" +#include "ac/timer.h" + +using AGS::Common::Bitmap; + +extern int windowbackgroundcolor, pushbuttondarkcolor; +extern int pushbuttonlightcolor; +extern int cbuttfont; + +MyPushButton::MyPushButton(int xx, int yy, int wi, int hi, const char *tex) +{ //wlevel=2; + x = xx; + y = yy; + wid = wi; + hit = hi + 1; //hit=hi; + state = 0; + strncpy(text, tex, 50); + text[49] = 0; +}; + +void MyPushButton::draw(Bitmap *ds) +{ + color_t text_color = ds->GetCompatibleColor(0); + color_t draw_color = ds->GetCompatibleColor(COL254); + ds->FillRect(Rect(x, y, x + wid, y + hit), draw_color); + if (state == 0) + draw_color = ds->GetCompatibleColor(pushbuttondarkcolor); + else + draw_color = ds->GetCompatibleColor(pushbuttonlightcolor); + + ds->DrawRect(Rect(x, y, x + wid, y + hit), draw_color); + if (state == 0) + draw_color = ds->GetCompatibleColor(pushbuttonlightcolor); + else + draw_color = ds->GetCompatibleColor(pushbuttondarkcolor); + + ds->DrawLine(Line(x, y, x + wid - 1, y), draw_color); + ds->DrawLine(Line(x, y, x, y + hit - 1), draw_color); + wouttextxy(ds, x + (wid / 2 - wgettextwidth(text, cbuttfont) / 2), y + 2, cbuttfont, text_color, text); + if (typeandflags & CNF_DEFAULT) + draw_color = ds->GetCompatibleColor(0); + else + draw_color = ds->GetCompatibleColor(windowbackgroundcolor); + + ds->DrawRect(Rect(x - 1, y - 1, x + wid + 1, y + hit + 1), draw_color); +} + +//extern const int LEFT; // in mousew32 + +int MyPushButton::pressedon(int mousex, int mousey) +{ + int wasstat; + while (mbutrelease(LEFT) == 0) { + + wasstat = state; + state = mouseisinarea(mousex, mousey); + // stop mp3 skipping if button held down + update_polled_stuff_if_runtime(); + if (wasstat != state) { + // ags_domouse(DOMOUSE_DISABLE); + draw(get_gui_screen()); + //ags_domouse(DOMOUSE_ENABLE); + } + + // ags_domouse(DOMOUSE_UPDATE); + + refresh_gui_screen(); + + WaitForNextFrame(); + } + wasstat = state; + state = 0; + // ags_domouse(DOMOUSE_DISABLE); + draw(get_gui_screen()); + // ags_domouse(DOMOUSE_ENABLE); + return wasstat; +} + +int MyPushButton::processmessage(int mcode, int wParam, long lParam) +{ + return -1; // doesn't support messages +} \ No newline at end of file diff --git a/engines/ags/engine/gui/mypushbutton.h b/engines/ags/engine/gui/mypushbutton.h new file mode 100644 index 00000000000..14bca4de80e --- /dev/null +++ b/engines/ags/engine/gui/mypushbutton.h @@ -0,0 +1,29 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_PUSHBUTTON_H +#define __AC_PUSHBUTTON_H + +#include "gui/newcontrol.h" + +struct MyPushButton:public NewControl +{ + char text[50]; + MyPushButton(int xx, int yy, int wi, int hi, const char *tex); + void draw(Common::Bitmap *ds) override; + int pressedon(int mousex, int mousey) override; + int processmessage(int mcode, int wParam, long lParam) override; +}; + +#endif // __AC_PUSHBUTTON_H \ No newline at end of file diff --git a/engines/ags/engine/gui/mytextbox.cpp b/engines/ags/engine/gui/mytextbox.cpp new file mode 100644 index 00000000000..e6a5ee30b46 --- /dev/null +++ b/engines/ags/engine/gui/mytextbox.cpp @@ -0,0 +1,88 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "util/wgt2allg.h" +#include "font/fonts.h" +#include "gui/mytextbox.h" +#include "gui/guidialoginternaldefs.h" +#include "gfx/bitmap.h" + +using AGS::Common::Bitmap; + +extern GameSetup usetup; + +extern int windowbackgroundcolor; +extern int cbuttfont; + +MyTextBox::MyTextBox(int xx, int yy, int wii, const char *tee) +{ + x = xx; + y = yy; + wid = wii; + if (tee != nullptr) + strcpy(text, tee); + else + text[0] = 0; + + hit = TEXT_HT + 1; +} + +void MyTextBox::draw(Bitmap *ds) +{ + color_t draw_color = ds->GetCompatibleColor(windowbackgroundcolor); + ds->FillRect(Rect(x, y, x + wid, y + hit), draw_color); + draw_color = ds->GetCompatibleColor(0); + ds->DrawRect(Rect(x, y, x + wid, y + hit), draw_color); + color_t text_color = ds->GetCompatibleColor(0); + wouttextxy(ds, x + 2, y + 1, cbuttfont, text_color, text); + + char tbu[2] = "_"; + wouttextxy(ds, x + 2 + wgettextwidth(text, cbuttfont), y + 1, cbuttfont, text_color, tbu); +} + +int MyTextBox::pressedon(int mousex, int mousey) +{ + return 0; +} + +int MyTextBox::processmessage(int mcode, int wParam, long lParam) +{ + if (mcode == CTB_SETTEXT) { + strcpy(text, (char *)lParam); + needredraw = 1; + } else if (mcode == CTB_GETTEXT) + strcpy((char *)lParam, text); + else if (mcode == CTB_KEYPRESS) { + if (wParam == 8) { + if (text[0] != 0) + text[strlen(text) - 1] = 0; + + drawandmouse(); + } else if (strlen(text) >= TEXTBOX_MAXLEN - 1) + ; + else if (wgettextwidth(text, cbuttfont) >= wid - 5) + ; + else if (wParam > 127) + ; // font only has 128 chars + else { + text[strlen(text) + 1] = 0; + text[strlen(text)] = wParam; + drawandmouse(); + } + } else + return -1; + + return 0; +} diff --git a/engines/ags/engine/gui/mytextbox.h b/engines/ags/engine/gui/mytextbox.h new file mode 100644 index 00000000000..264c2bcc32f --- /dev/null +++ b/engines/ags/engine/gui/mytextbox.h @@ -0,0 +1,30 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYTEXTBOX_H +#define __AC_MYTEXTBOX_H + +#include "gui/newcontrol.h" + +#define TEXTBOX_MAXLEN 49 +struct MyTextBox:public NewControl +{ + char text[TEXTBOX_MAXLEN + 1]; + MyTextBox(int xx, int yy, int wii, const char *tee); + void draw(Common::Bitmap *ds) override; + int pressedon(int mousex, int mousey) override; + int processmessage(int mcode, int wParam, long lParam) override; +}; + +#endif // __AC_MYTEXTBOX_H \ No newline at end of file diff --git a/engines/ags/engine/gui/newcontrol.cpp b/engines/ags/engine/gui/newcontrol.cpp new file mode 100644 index 00000000000..29e683aa965 --- /dev/null +++ b/engines/ags/engine/gui/newcontrol.cpp @@ -0,0 +1,67 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "gui/newcontrol.h" +#include "gui/guidialog.h" +#include "gui/guidialoginternaldefs.h" + +extern int topwindowhandle; + +NewControl::NewControl(int xx, int yy, int wi, int hi) +{ + x = xx; + y = yy; + wid = wi; + hit = hi; + state = 0; + typeandflags = 0; + wlevel = 0; + visible = 1; + enabled = 1; + needredraw = 1; +}; +NewControl::NewControl() { + x = y = wid = hit = 0; + state = 0; + typeandflags = 0; + wlevel = 0; + visible = 1; + enabled = 1; + needredraw = 1; +} +int NewControl::mouseisinarea(int mousex, int mousey) +{ + if (topwindowhandle != wlevel) + return 0; + + if ((mousex > x) & (mousex < x + wid) & (mousey > y) & (mousey < y + hit)) + return 1; + + return 0; +} +void NewControl::drawifneeded() +{ + if (topwindowhandle != wlevel) + return; + if (needredraw) { + needredraw = 0; + draw(get_gui_screen()); + } +} +void NewControl::drawandmouse() +{ + // ags_domouse(DOMOUSE_DISABLE); + draw(get_gui_screen()); + // ags_domouse(DOMOUSE_ENABLE); +} diff --git a/engines/ags/engine/gui/newcontrol.h b/engines/ags/engine/gui/newcontrol.h new file mode 100644 index 00000000000..e308bff881a --- /dev/null +++ b/engines/ags/engine/gui/newcontrol.h @@ -0,0 +1,42 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_GUI__NEWCONTROL_H +#define __AGS_EE_GUI__NEWCONTROL_H + +#include "gfx/bitmap.h" + +using namespace AGS; // FIXME later + +struct NewControl +{ + int x, y, wid, hit, state, typeandflags, wlevel; + char visible, enabled; // not implemented + char needredraw; + virtual void draw(Common::Bitmap *ds) = 0; + virtual int pressedon(int mousex, int mousey) = 0; + virtual int processmessage(int, int, long) = 0; + + NewControl(int xx, int yy, int wi, int hi); + NewControl(); + virtual ~NewControl() = default; + int mouseisinarea(int mousex, int mousey); + void drawifneeded(); + void drawandmouse(); +}; + +#endif // __AGS_EE_GUI__NEWCONTROL_H \ No newline at end of file diff --git a/engines/ags/engine/main/config.cpp b/engines/ags/engine/main/config.cpp new file mode 100644 index 00000000000..558d67ca923 --- /dev/null +++ b/engines/ags/engine/main/config.cpp @@ -0,0 +1,672 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +// +// Game configuration +// +#include // toupper + +#include "core/platform.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_translation.h" +#include "ac/path_helper.h" +#include "ac/spritecache.h" +#include "ac/system.h" +#include "debug/debugger.h" +#include "debug/debug_log.h" +#include "main/mainheader.h" +#include "main/config.h" +#include "platform/base/agsplatformdriver.h" +#include "util/directory.h" +#include "util/ini_util.h" +#include "util/textstreamreader.h" +#include "util/path.h" +#include "util/string_utils.h" +#include "media/audio/audio_system.h" + + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern GameSetup usetup; +extern SpriteCache spriteset; +extern int force_window; +extern GameState play; + +// Filename of the default config file, the one found in the game installation +const String DefaultConfigFileName = "acsetup.cfg"; + +// Replace the filename part of complete path WASGV with INIFIL +// TODO: get rid of this and use proper lib path function instead +void INIgetdirec(char *wasgv, const char *inifil) { + int u = strlen(wasgv) - 1; + + for (u = strlen(wasgv) - 1; u >= 0; u--) { + if ((wasgv[u] == '\\') || (wasgv[u] == '/')) { + memcpy(&wasgv[u + 1], inifil, strlen(inifil) + 1); + break; + } + } + + if (u <= 0) { + // no slashes - either the path is just "f:acwin.exe" + if (strchr(wasgv, ':') != nullptr) + memcpy(strchr(wasgv, ':') + 1, inifil, strlen(inifil) + 1); + // or it's just "acwin.exe" (unlikely) + else + strcpy(wasgv, inifil); + } + +} + +bool INIreaditem(const ConfigTree &cfg, const String §n, const String &item, String &value) +{ + ConfigNode sec_it = cfg.find(sectn); + if (sec_it != cfg.end()) + { + StrStrOIter item_it = sec_it->second.find(item); + if (item_it != sec_it->second.end()) + { + value = item_it->second; + return true; + } + } + return false; +} + +int INIreadint(const ConfigTree &cfg, const String §n, const String &item, int def_value) +{ + String str; + if (!INIreaditem(cfg, sectn, item, str)) + return def_value; + + return atoi(str); +} + +float INIreadfloat(const ConfigTree &cfg, const String §n, const String &item, float def_value) +{ + String str; + if (!INIreaditem(cfg, sectn, item, str)) + return def_value; + + return atof(str); +} + +String INIreadstring(const ConfigTree &cfg, const String §n, const String &item, const String &def_value) +{ + String str; + if (!INIreaditem(cfg, sectn, item, str)) + return def_value; + return str; +} + +void INIwriteint(ConfigTree &cfg, const String §n, const String &item, int value) +{ + cfg[sectn][item] = StrUtil::IntToString(value); +} + +void INIwritestring(ConfigTree &cfg, const String §n, const String &item, const String &value) +{ + cfg[sectn][item] = value; +} + +void parse_scaling_option(const String &scaling_option, FrameScaleDefinition &scale_def, int &scale_factor) +{ + const char *game_scale_options[kNumFrameScaleDef - 1] = { "max_round", "stretch", "proportional" }; + scale_def = kFrame_IntScale; + for (int i = 0; i < kNumFrameScaleDef - 1; ++i) + { + if (scaling_option.CompareNoCase(game_scale_options[i]) == 0) + { + scale_def = (FrameScaleDefinition)(i + 1); + break; + } + } + + if (scale_def == kFrame_IntScale) + scale_factor = StrUtil::StringToInt(scaling_option); + else + scale_factor = 0; +} + +void parse_scaling_option(const String &scaling_option, GameFrameSetup &frame_setup) +{ + parse_scaling_option(scaling_option, frame_setup.ScaleDef, frame_setup.ScaleFactor); +} + +// Parses legacy filter ID and converts it into current scaling options +bool parse_legacy_frame_config(const String &scaling_option, String &filter_id, GameFrameSetup &frame) +{ + struct + { + String LegacyName; + String CurrentName; + int Scaling; + } legacy_filters[6] = { {"none", "none", -1}, {"max", "StdScale", 0}, {"StdScale", "StdScale", -1}, + {"AAx", "Linear", -1}, {"Hq2x", "Hqx", 2}, {"Hq3x", "Hqx", 3} }; + + for (int i = 0; i < 6; i++) + { + if (scaling_option.CompareLeftNoCase(legacy_filters[i].LegacyName) == 0) + { + filter_id = legacy_filters[i].CurrentName; + frame.ScaleDef = legacy_filters[i].Scaling == 0 ? kFrame_MaxRound : kFrame_IntScale; + frame.ScaleFactor = legacy_filters[i].Scaling >= 0 ? legacy_filters[i].Scaling : + scaling_option.Mid(legacy_filters[i].LegacyName.GetLength()).ToInt(); + return true; + } + } + return false; +} + +String make_scaling_option(FrameScaleDefinition scale_def, int scale_factor) +{ + switch (scale_def) + { + case kFrame_MaxRound: + return "max_round"; + case kFrame_MaxStretch: + return "stretch"; + case kFrame_MaxProportional: + return "proportional"; + } + return String::FromFormat("%d", scale_factor); +} + +String make_scaling_option(const GameFrameSetup &frame_setup) +{ + return make_scaling_option(frame_setup.ScaleDef, frame_setup.ScaleFactor); +} + +uint32_t convert_scaling_to_fp(int scale_factor) +{ + if (scale_factor >= 0) + return scale_factor <<= kShift; + else + return kUnit / abs(scale_factor); +} + +int convert_fp_to_scaling(uint32_t scaling) +{ + if (scaling == 0) + return 0; + return scaling >= kUnit ? (scaling >> kShift) : -kUnit / (int32_t)scaling; +} + +AlIDStr AlIDToChars(int al_id) +{ + if (al_id == 0) + return AlIDStr {{ 'N', 'O', 'N', 'E', '\0' }}; + else if (al_id == -1) + return AlIDStr {{ 'A', 'U', 'T', 'O', '\0' }}; + else + return AlIDStr {{ + static_cast((al_id >> 24) & 0xFF), + static_cast((al_id >> 16) & 0xFF), + static_cast((al_id >> 8) & 0xFF), + static_cast((al_id) & 0xFF), + '\0' + }}; +} + +AlIDStr AlIDToChars(const String &s) +{ + AlIDStr id_str; + size_t i = 0; + for (; i < s.GetLength(); ++i) + id_str.s[i] = toupper(s[i]); + for (; i < 4; ++i) + id_str.s[i] = ' '; + id_str.s[4] = 0; + return id_str; +} + +int StringToAlID(const char *cstr) +{ + return (int)(AL_ID(cstr[0u], cstr[1u], cstr[2u], cstr[3u])); +} + +// Parses a config string which may hold plain driver's ID or 4-char ID packed +// as a 32-bit integer. +int parse_driverid(const String &id) +{ + int asint; + if (StrUtil::StringToInt(id, asint, 0) == StrUtil::kNoError) + return asint; + if (id.GetLength() > 4) + return -1; // autodetect + if (id.CompareNoCase("AUTO") == 0) + return -1; // autodetect + if (id.CompareNoCase("NONE") == 0) + return 0; // no driver + return StringToAlID(AlIDToChars(id).s); +} + +// Reads driver ID from config, where it may be represented as string or number +int read_driverid(const ConfigTree &cfg, const String §n, const String &item, int def_value) +{ + String s = INIreadstring(cfg, sectn, item); + if (s.IsEmpty()) + return def_value; + return parse_driverid(s); +} + +void write_driverid(ConfigTree &cfg, const String §n, const String &item, int value) +{ + INIwritestring(cfg, sectn, item, AlIDToChars(value).s); +} + +void graphics_mode_get_defaults(bool windowed, ScreenSizeSetup &scsz_setup, GameFrameSetup &frame_setup) +{ + scsz_setup.Size = Size(); + if (windowed) + { + // For the windowed we define mode by the scaled game. + scsz_setup.SizeDef = kScreenDef_ByGameScaling; + scsz_setup.MatchDeviceRatio = false; + frame_setup = usetup.Screen.WinGameFrame; + } + else + { + // For the fullscreen we set current desktop resolution, which + // corresponds to most comfortable fullscreen mode for the driver. + scsz_setup.SizeDef = kScreenDef_MaxDisplay; + scsz_setup.MatchDeviceRatio = true; + frame_setup = usetup.Screen.FsGameFrame; + } +} + +String find_default_cfg_file(const char *alt_cfg_file) +{ + // Try current directory for config first; else try exe dir + String filename = String::FromFormat("%s/%s", Directory::GetCurrentDirectory().GetCStr(), DefaultConfigFileName.GetCStr()); + if (!Common::File::TestReadFile(filename)) + { + char conffilebuf[512]; + strcpy(conffilebuf, alt_cfg_file); + fix_filename_case(conffilebuf); + fix_filename_slashes(conffilebuf); + INIgetdirec(conffilebuf, DefaultConfigFileName); + filename = conffilebuf; + } + return filename; +} + +String find_user_global_cfg_file() +{ + String parent_dir = PathOrCurDir(platform->GetUserGlobalConfigDirectory()); + return String::FromFormat("%s/%s", parent_dir.GetCStr(), DefaultConfigFileName.GetCStr()); +} + +String find_user_cfg_file() +{ + String parent_dir = MakeSpecialSubDir(PathOrCurDir(platform->GetUserConfigDirectory())); + return String::FromFormat("%s/%s", parent_dir.GetCStr(), DefaultConfigFileName.GetCStr()); +} + +void config_defaults() +{ +#if AGS_PLATFORM_OS_WINDOWS + usetup.Screen.DriverID = "D3D9"; +#else + usetup.Screen.DriverID = "OGL"; +#endif +#if AGS_PLATFORM_OS_WINDOWS + usetup.digicard = DIGI_DIRECTAMX(0); +#endif + usetup.midicard = MIDI_AUTODETECT; + usetup.translation = ""; +} + +void read_game_data_location(const ConfigTree &cfg) +{ + usetup.data_files_dir = INIreadstring(cfg, "misc", "datadir", usetup.data_files_dir); + if (!usetup.data_files_dir.IsEmpty()) + { + // strip any trailing slash + // TODO: move this to Path namespace later + AGS::Common::Path::FixupPath(usetup.data_files_dir); +#if AGS_PLATFORM_OS_WINDOWS + // if the path is just x:\ don't strip the slash + if (!(usetup.data_files_dir.GetLength() == 3 && usetup.data_files_dir[1u] == ':')) + { + usetup.data_files_dir.TrimRight('/'); + } +#else + usetup.data_files_dir.TrimRight('/'); +#endif + } + usetup.main_data_filename = INIreadstring(cfg, "misc", "datafile", usetup.main_data_filename); +} + +void read_legacy_audio_config(const ConfigTree &cfg) +{ +#if AGS_PLATFORM_OS_WINDOWS + int idx = INIreadint(cfg, "sound", "digiwinindx", -1); + if (idx == 0) + idx = DIGI_DIRECTAMX(0); + else if (idx == 1) + idx = DIGI_WAVOUTID(0); + else if (idx == 2) + idx = DIGI_NONE; + else if (idx == 3) + idx = DIGI_DIRECTX(0); + else + idx = DIGI_AUTODETECT; + usetup.digicard = idx; + + idx = INIreadint(cfg, "sound", "midiwinindx", -1); + if (idx == 1) + idx = MIDI_NONE; + else if (idx == 2) + idx = MIDI_WIN32MAPPER; + else + idx = MIDI_AUTODETECT; + usetup.midicard = idx; +#endif +} + +void read_legacy_graphics_config(const ConfigTree &cfg) +{ + usetup.Screen.DisplayMode.Windowed = INIreadint(cfg, "misc", "windowed") > 0; + usetup.Screen.DriverID = INIreadstring(cfg, "misc", "gfxdriver", usetup.Screen.DriverID); + + { + String legacy_filter = INIreadstring(cfg, "misc", "gfxfilter"); + if (!legacy_filter.IsEmpty()) + { + // NOTE: legacy scaling config is applied only to windowed setting + if (usetup.Screen.DisplayMode.Windowed) + usetup.Screen.DisplayMode.ScreenSize.SizeDef = kScreenDef_ByGameScaling; + parse_legacy_frame_config(legacy_filter, usetup.Screen.Filter.ID, usetup.Screen.WinGameFrame); + + // AGS 3.2.1 and 3.3.0 aspect ratio preferences + if (!usetup.Screen.DisplayMode.Windowed) + { + usetup.Screen.DisplayMode.ScreenSize.MatchDeviceRatio = + (INIreadint(cfg, "misc", "sideborders") > 0 || INIreadint(cfg, "misc", "forceletterbox") > 0 || + INIreadint(cfg, "misc", "prefer_sideborders") > 0 || INIreadint(cfg, "misc", "prefer_letterbox") > 0); + } + } + + // AGS 3.4.0 - 3.4.1-rc uniform scaling option + String uniform_frame_scale = INIreadstring(cfg, "graphics", "game_scale"); + if (!uniform_frame_scale.IsEmpty()) + { + GameFrameSetup frame_setup; + parse_scaling_option(uniform_frame_scale, frame_setup); + usetup.Screen.FsGameFrame = frame_setup; + usetup.Screen.WinGameFrame = frame_setup; + } + } + + usetup.Screen.DisplayMode.RefreshRate = INIreadint(cfg, "misc", "refresh"); +} + +// Variables used for mobile port configs +extern int psp_gfx_renderer; +extern int psp_gfx_scaling; +extern int psp_gfx_super_sampling; +extern int psp_gfx_smoothing; +extern int psp_gfx_smooth_sprites; +extern int psp_audio_enabled; +extern int psp_midi_enabled; +extern char psp_translation[]; + +void override_config_ext(ConfigTree &cfg) +{ + // Mobile ports always run in fullscreen mode +#if AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS + INIwriteint(cfg, "graphics", "windowed", 0); +#endif + + // psp_gfx_renderer - rendering mode + // * 0 - software renderer + // * 1 - hardware, render to screen + // * 2 - hardware, render to texture + if (psp_gfx_renderer == 0) + { + INIwritestring(cfg, "graphics", "driver", "Software"); + INIwriteint(cfg, "graphics", "render_at_screenres", 1); + } + else + { + INIwritestring(cfg, "graphics", "driver", "OGL"); + INIwriteint(cfg, "graphics", "render_at_screenres", psp_gfx_renderer == 1); + } + + // psp_gfx_scaling - scaling style: + // * 0 - no scaling + // * 1 - stretch and preserve aspect ratio + // * 2 - stretch to whole screen + if (psp_gfx_scaling == 0) + INIwritestring(cfg, "graphics", "game_scale_fs", "1"); + else if (psp_gfx_scaling == 1) + INIwritestring(cfg, "graphics", "game_scale_fs", "proportional"); + else + INIwritestring(cfg, "graphics", "game_scale_fs", "stretch"); + + // psp_gfx_smoothing - scaling filter: + // * 0 - nearest-neighbour + // * 1 - linear + if (psp_gfx_smoothing == 0) + INIwritestring(cfg, "graphics", "filter", "StdScale"); + else + INIwritestring(cfg, "graphics", "filter", "Linear"); + + // psp_gfx_super_sampling - enable super sampling + // * 0 - x1 + // * 1 - x2 + if (psp_gfx_renderer == 2) + INIwriteint(cfg, "graphics", "supersampling", psp_gfx_super_sampling + 1); + else + INIwriteint(cfg, "graphics", "supersampling", 0); + + INIwriteint(cfg, "misc", "antialias", psp_gfx_smooth_sprites != 0); + INIwritestring(cfg, "language", "translation", psp_translation); +} + +void apply_config(const ConfigTree &cfg) +{ + { + // Legacy settings has to be translated into new options; + // they must be read first, to let newer options override them, if ones are present + read_legacy_audio_config(cfg); + if (psp_audio_enabled) + { + usetup.digicard = read_driverid(cfg, "sound", "digiid", usetup.digicard); + if (psp_midi_enabled) + usetup.midicard = read_driverid(cfg, "sound", "midiid", usetup.midicard); + else + usetup.midicard = MIDI_NONE; + } + else + { + usetup.digicard = DIGI_NONE; + usetup.midicard = MIDI_NONE; + } + + psp_audio_multithreaded = INIreadint(cfg, "sound", "threaded", psp_audio_multithreaded); + + // Legacy graphics settings has to be translated into new options; + // they must be read first, to let newer options override them, if ones are present + read_legacy_graphics_config(cfg); + + // Graphics mode + usetup.Screen.DriverID = INIreadstring(cfg, "graphics", "driver", usetup.Screen.DriverID); + + usetup.Screen.DisplayMode.Windowed = INIreadint(cfg, "graphics", "windowed") > 0; + const char *screen_sz_def_options[kNumScreenDef] = { "explicit", "scaling", "max" }; + usetup.Screen.DisplayMode.ScreenSize.SizeDef = usetup.Screen.DisplayMode.Windowed ? kScreenDef_ByGameScaling : kScreenDef_MaxDisplay; + String screen_sz_def_str = INIreadstring(cfg, "graphics", "screen_def"); + for (int i = 0; i < kNumScreenDef; ++i) + { + if (screen_sz_def_str.CompareNoCase(screen_sz_def_options[i]) == 0) + { + usetup.Screen.DisplayMode.ScreenSize.SizeDef = (ScreenSizeDefinition)i; + break; + } + } + + usetup.Screen.DisplayMode.ScreenSize.Size = Size(INIreadint(cfg, "graphics", "screen_width"), + INIreadint(cfg, "graphics", "screen_height")); + usetup.Screen.DisplayMode.ScreenSize.MatchDeviceRatio = INIreadint(cfg, "graphics", "match_device_ratio", 1) != 0; + // TODO: move to config overrides (replace values during config load) +#if AGS_PLATFORM_OS_MACOS + usetup.Screen.Filter.ID = "none"; +#else + usetup.Screen.Filter.ID = INIreadstring(cfg, "graphics", "filter", "StdScale"); + parse_scaling_option(INIreadstring(cfg, "graphics", "game_scale_fs", "proportional"), usetup.Screen.FsGameFrame); + parse_scaling_option(INIreadstring(cfg, "graphics", "game_scale_win", "max_round"), usetup.Screen.WinGameFrame); +#endif + + usetup.Screen.DisplayMode.RefreshRate = INIreadint(cfg, "graphics", "refresh"); + usetup.Screen.DisplayMode.VSync = INIreadint(cfg, "graphics", "vsync") > 0; + usetup.RenderAtScreenRes = INIreadint(cfg, "graphics", "render_at_screenres") > 0; + usetup.Supersampling = INIreadint(cfg, "graphics", "supersampling", 1); + + usetup.enable_antialiasing = INIreadint(cfg, "misc", "antialias") > 0; + + // This option is backwards (usevox is 0 if no_speech_pack) + usetup.no_speech_pack = INIreadint(cfg, "sound", "usespeech", 1) == 0; + + usetup.user_data_dir = INIreadstring(cfg, "misc", "user_data_dir"); + usetup.shared_data_dir = INIreadstring(cfg, "misc", "shared_data_dir"); + + usetup.translation = INIreadstring(cfg, "language", "translation"); + + int cache_size_kb = INIreadint(cfg, "misc", "cachemax", DEFAULTCACHESIZE_KB); + if (cache_size_kb > 0) + spriteset.SetMaxCacheSize((size_t)cache_size_kb * 1024); + + usetup.mouse_auto_lock = INIreadint(cfg, "mouse", "auto_lock") > 0; + + usetup.mouse_speed = INIreadfloat(cfg, "mouse", "speed", 1.f); + if (usetup.mouse_speed <= 0.f) + usetup.mouse_speed = 1.f; + const char *mouse_ctrl_options[kNumMouseCtrlOptions] = { "never", "fullscreen", "always" }; + String mouse_str = INIreadstring(cfg, "mouse", "control_when", "fullscreen"); + for (int i = 0; i < kNumMouseCtrlOptions; ++i) + { + if (mouse_str.CompareNoCase(mouse_ctrl_options[i]) == 0) + { + usetup.mouse_ctrl_when = (MouseControlWhen)i; + break; + } + } + usetup.mouse_ctrl_enabled = INIreadint(cfg, "mouse", "control_enabled", 1) > 0; + const char *mouse_speed_options[kNumMouseSpeedDefs] = { "absolute", "current_display" }; + mouse_str = INIreadstring(cfg, "mouse", "speed_def", "current_display"); + for (int i = 0; i < kNumMouseSpeedDefs; ++i) + { + if (mouse_str.CompareNoCase(mouse_speed_options[i]) == 0) + { + usetup.mouse_speed_def = (MouseSpeedDef)i; + break; + } + } + + usetup.override_multitasking = INIreadint(cfg, "override", "multitasking", -1); + String override_os = INIreadstring(cfg, "override", "os"); + usetup.override_script_os = -1; + if (override_os.CompareNoCase("dos") == 0) + { + usetup.override_script_os = eOS_DOS; + } + else if (override_os.CompareNoCase("win") == 0) + { + usetup.override_script_os = eOS_Win; + } + else if (override_os.CompareNoCase("linux") == 0) + { + usetup.override_script_os = eOS_Linux; + } + else if (override_os.CompareNoCase("mac") == 0) + { + usetup.override_script_os = eOS_Mac; + } + usetup.override_upscale = INIreadint(cfg, "override", "upscale") > 0; + } + + // Apply logging configuration + apply_debug_config(cfg); +} + +void post_config() +{ + if (usetup.Screen.DriverID.IsEmpty() || usetup.Screen.DriverID.CompareNoCase("DX5") == 0) + usetup.Screen.DriverID = "Software"; + + // FIXME: this correction is needed at the moment because graphics driver + // implementation requires some filter to be created anyway + usetup.Screen.Filter.UserRequest = usetup.Screen.Filter.ID; + if (usetup.Screen.Filter.ID.IsEmpty() || usetup.Screen.Filter.ID.CompareNoCase("none") == 0) + { + usetup.Screen.Filter.ID = "StdScale"; + } + + if (!usetup.Screen.FsGameFrame.IsValid()) + usetup.Screen.FsGameFrame = GameFrameSetup(kFrame_MaxProportional); + if (!usetup.Screen.WinGameFrame.IsValid()) + usetup.Screen.WinGameFrame = GameFrameSetup(kFrame_MaxRound); + + // TODO: helper functions to remove slash in paths (or distinct path type) + if (usetup.user_data_dir.GetLast() == '/' || usetup.user_data_dir.GetLast() == '\\') + usetup.user_data_dir.ClipRight(1); + if (usetup.shared_data_dir.GetLast() == '/' || usetup.shared_data_dir.GetLast() == '\\') + usetup.shared_data_dir.ClipRight(1); +} + +void save_config_file() +{ + ConfigTree cfg; + + // Last display mode + // TODO: force_window check is a temporary workaround (see comment below) + if (force_window == 0) + { + bool is_windowed = System_GetWindowed() != 0; + cfg["graphics"]["windowed"] = String::FromFormat("%d", is_windowed ? 1 : 0); + // TODO: this is a hack, necessary because the original config system was designed when + // switching mode at runtime was not considered a possibility. + // Normally, two changes need to be done here: + // * the display setup needs to be reviewed and simplified a bit. + // * perhaps there should be two saved setups for fullscreen and windowed saved in memory + // (like ActiveDisplaySetting is saved currently), to know how the window size is defined + // in each modes (by explicit width/height values or from game scaling). + // This specifically *must* be done if there will be script API for modifying fullscreen + // resolution, or size of the window could be changed any way at runtime. + if (is_windowed != usetup.Screen.DisplayMode.Windowed) + { + if (is_windowed) + cfg["graphics"]["screen_def"] = "scaling"; + else + cfg["graphics"]["screen_def"] = "max"; + } + } + + // Other game options that could be changed at runtime + if (game.options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_UserDefined) + cfg["graphics"]["render_at_screenres"] = String::FromFormat("%d", usetup.RenderAtScreenRes ? 1 : 0); + cfg["mouse"]["control_enabled"] = String::FromFormat("%d", usetup.mouse_ctrl_enabled ? 1 : 0); + cfg["mouse"]["speed"] = String::FromFormat("%f", Mouse::GetSpeed()); + cfg["language"]["translation"] = usetup.translation; + + String cfg_file = find_user_cfg_file(); + if (!cfg_file.IsEmpty()) + IniUtil::Merge(cfg_file, cfg); +} diff --git a/engines/ags/engine/main/config.h b/engines/ags/engine/main/config.h new file mode 100644 index 00000000000..5398a148b21 --- /dev/null +++ b/engines/ags/engine/main/config.h @@ -0,0 +1,76 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__CONFIG_H +#define __AGS_EE_MAIN__CONFIG_H + +#include "main/graphics_mode.h" +#include "util/ini_util.h" + +using AGS::Common::String; +using AGS::Common::ConfigTree; + +// Set up default config settings +void config_defaults(); +// Find and default configuration file (usually located in the game installation directory) +String find_default_cfg_file(const char *alt_cfg_file); +// Find all-games user configuration file +String find_user_global_cfg_file(); +// Find and game-specific user configuration file (located into writable user directory) +String find_user_cfg_file(); +// Read optional data file name and location from config +void read_game_data_location(const AGS::Common::ConfigTree &cfg); +// Apply overriding values from the external config (e.g. for mobile ports) +void override_config_ext(ConfigTree &cfg); +// Setup game using final config tree +void apply_config(const ConfigTree &cfg); +// Fixup game setup parameters +void post_config(); + +void save_config_file(); + +void parse_scaling_option(const String &scaling_option, FrameScaleDefinition &scale_def, int &scale_factor); +void parse_scaling_option(const String &scaling_option, GameFrameSetup &frame_setup); +String make_scaling_option(FrameScaleDefinition scale_def, int scale_factor = 0); +String make_scaling_option(const GameFrameSetup &frame_setup); +uint32_t convert_scaling_to_fp(int scale_factor); +int convert_fp_to_scaling(uint32_t scaling); +// Fill in setup structs with default settings for the given mode (windowed or fullscreen) +void graphics_mode_get_defaults(bool windowed, ScreenSizeSetup &scsz_setup, GameFrameSetup &frame_setup); + +typedef struct { char s[5]; } AlIDStr; +// Converts Allegro driver ID type to 4-char string +AlIDStr AlIDToChars(int al_id); +AlIDStr AlIDToChars(const String &s); +// Converts C-string into Allegro's driver ID; string must be at least 4 character long +int StringToAlID(const char *cstr); +// Reads driver ID from config, where it may be represented as string or number +int read_driverid(const ConfigTree &cfg, const String §n, const String &item, int def_value); +// Writes driver ID to config +void write_driverid(ConfigTree &cfg, const String §n, const String &item, int value); + + +bool INIreaditem(const ConfigTree &cfg, const String §n, const String &item, String &value); +int INIreadint(const ConfigTree &cfg, const String §n, const String &item, int def_value = 0); +float INIreadfloat(const ConfigTree &cfg, const String §n, const String &item, float def_value = 0.f); +String INIreadstring(const ConfigTree &cfg, const String §n, const String &item, const String &def_value = ""); +void INIwriteint(ConfigTree &cfg, const String §n, const String &item, int value); +void INIwritestring(ConfigTree &cfg, const String §n, const String &item, const String &value); +void INIwriteint(ConfigTree &cfg, const String §n, const String &item, int value); + + +#endif // __AGS_EE_MAIN__CONFIG_H diff --git a/engines/ags/engine/main/engine.cpp b/engines/ags/engine/main/engine.cpp new file mode 100644 index 00000000000..4532275d40b --- /dev/null +++ b/engines/ags/engine/main/engine.cpp @@ -0,0 +1,1677 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +// +// Engine initialization +// + +#include "core/platform.h" + +#include +#if AGS_PLATFORM_OS_WINDOWS +#include // _spawnl +#endif + +#include "main/mainheader.h" +#include "ac/asset_helper.h" +#include "ac/common.h" +#include "ac/character.h" +#include "ac/characterextras.h" +#include "ac/characterinfo.h" +#include "ac/draw.h" +#include "ac/game.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_character.h" +#include "ac/global_game.h" +#include "ac/gui.h" +#include "ac/lipsync.h" +#include "ac/objectcache.h" +#include "ac/path_helper.h" +#include "ac/sys_events.h" +#include "ac/roomstatus.h" +#include "ac/speech.h" +#include "ac/spritecache.h" +#include "ac/translation.h" +#include "ac/viewframe.h" +#include "ac/dynobj/scriptobject.h" +#include "ac/dynobj/scriptsystem.h" +#include "core/assetmanager.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "debug/out.h" +#include "font/agsfontrenderer.h" +#include "font/fonts.h" +#include "gfx/graphicsdriver.h" +#include "gfx/gfxdriverfactory.h" +#include "gfx/ddb.h" +#include "main/config.h" +#include "main/game_file.h" +#include "main/game_start.h" +#include "main/engine.h" +#include "main/engine_setup.h" +#include "main/graphics_mode.h" +#include "main/main.h" +#include "main/main_allegro.h" +#include "media/audio/audio_system.h" +#include "platform/util/pe.h" +#include "util/directory.h" +#include "util/error.h" +#include "util/misc.h" +#include "util/path.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern char check_dynamic_sprites_at_exit; +extern int our_eip; +extern volatile char want_exit, abort_engine; +extern bool justRunSetup; +extern GameSetup usetup; +extern GameSetupStruct game; +extern int proper_exit; +extern char pexbuf[STD_BUFFER_SIZE]; +extern SpriteCache spriteset; +extern ObjectCache objcache[MAX_ROOM_OBJECTS]; +extern ScriptObject scrObj[MAX_ROOM_OBJECTS]; +extern ViewStruct*views; +extern int displayed_room; +extern int eip_guinum; +extern int eip_guiobj; +extern SpeechLipSyncLine *splipsync; +extern int numLipLines, curLipLine, curLipLinePhoneme; +extern ScriptSystem scsystem; +extern IGraphicsDriver *gfxDriver; +extern Bitmap **actsps; +extern color palette[256]; +extern CharacterExtras *charextra; +extern CharacterInfo*playerchar; +extern Bitmap **guibg; +extern IDriverDependantBitmap **guibgbmp; + +ResourcePaths ResPaths; + +t_engine_pre_init_callback engine_pre_init_callback = nullptr; + +#define ALLEGRO_KEYBOARD_HANDLER + +bool engine_init_allegro() +{ + Debug::Printf(kDbgMsg_Info, "Initializing allegro"); + + our_eip = -199; + // Initialize allegro + set_uformat(U_ASCII); + if (install_allegro(SYSTEM_AUTODETECT, &errno, atexit)) + { + const char *al_err = get_allegro_error(); + const char *user_hint = platform->GetAllegroFailUserHint(); + platform->DisplayAlert("Unable to initialize Allegro system driver.\n%s\n\n%s", + al_err[0] ? al_err : "Allegro library provided no further information on the problem.", + user_hint); + return false; + } + return true; +} + +void engine_setup_allegro() +{ + // Setup allegro using constructed config string + const char *al_config_data = "[mouse]\n" + "mouse_accel_factor = 0\n"; + override_config_data(al_config_data, ustrsize(al_config_data)); +} + +void winclosehook() { + want_exit = 1; + abort_engine = 1; + check_dynamic_sprites_at_exit = 0; +} + +void engine_setup_window() +{ + Debug::Printf(kDbgMsg_Info, "Setting up window"); + + our_eip = -198; + set_window_title("Adventure Game Studio"); + set_close_button_callback (winclosehook); + our_eip = -197; + + platform->SetGameWindowIcon(); +} + +// Starts up setup application, if capable. +// Returns TRUE if should continue running the game, otherwise FALSE. +bool engine_run_setup(const String &exe_path, ConfigTree &cfg, int &app_res) +{ + app_res = EXIT_NORMAL; +#if AGS_PLATFORM_OS_WINDOWS + { + String cfg_file = find_user_cfg_file(); + if (cfg_file.IsEmpty()) + { + app_res = EXIT_ERROR; + return false; + } + + Debug::Printf(kDbgMsg_Info, "Running Setup"); + + ConfigTree cfg_out; + SetupReturnValue res = platform->RunSetup(cfg, cfg_out); + if (res != kSetup_Cancel) + { + if (!IniUtil::Merge(cfg_file, cfg_out)) + { + platform->DisplayAlert("Unable to write to the configuration file (error code 0x%08X).\n%s", + platform->GetLastSystemError(), platform->GetDiskWriteAccessTroubleshootingText()); + } + } + if (res != kSetup_RunGame) + return false; + + // TODO: investigate if the full program restart may (should) be avoided + + // Just re-reading the config file seems to cause a caching + // problem on Win9x, so let's restart the process. + allegro_exit(); + char quotedpath[MAX_PATH]; + snprintf(quotedpath, MAX_PATH, "\"%s\"", exe_path.GetCStr()); + _spawnl (_P_OVERLAY, exe_path, quotedpath, NULL); + } +#endif + return true; +} + +void engine_force_window() +{ + // Force to run in a window, override the config file + // TODO: actually overwrite config tree instead + if (force_window == 1) + { + usetup.Screen.DisplayMode.Windowed = true; + usetup.Screen.DisplayMode.ScreenSize.SizeDef = kScreenDef_ByGameScaling; + } + else if (force_window == 2) + { + usetup.Screen.DisplayMode.Windowed = false; + usetup.Screen.DisplayMode.ScreenSize.SizeDef = kScreenDef_MaxDisplay; + } +} + +String find_game_data_in_directory(const String &path) +{ + al_ffblk ff; + String test_file; + String first_nonstd_fn; + String pattern = path; + pattern.Append("/*"); + + if (al_findfirst(pattern, &ff, FA_ALL & ~(FA_DIREC)) != 0) + return ""; + // Select first found data file; files with standart names (*.ags) have + // higher priority over files with custom names. + do + { + test_file = ff.name; + // Add a bit of sanity and do not parse contents of the 10k-files-large + // digital sound libraries. + // NOTE: we could certainly benefit from any kind of flag in file lib + // that would tell us this is the main lib without extra parsing. + if (test_file.CompareRightNoCase(".vox") == 0) + continue; + + // *.ags is a standart cross-platform file pattern for AGS games, + // ac2game.dat is a legacy file name for very old games, + // *.exe is a MS Win executable; it is included to this case because + // users often run AGS ports with Windows versions of games. + bool is_std_name = test_file.CompareRightNoCase(".ags") == 0 || + test_file.CompareNoCase("ac2game.dat") == 0 || + test_file.CompareRightNoCase(".exe") == 0; + if (is_std_name || first_nonstd_fn.IsEmpty()) + { + test_file.Format("%s/%s", path.GetCStr(), ff.name); + if (IsMainGameLibrary(test_file)) + { + if (is_std_name) + { + al_findclose(&ff); + return test_file; + } + else + first_nonstd_fn = test_file; + } + } + } + while(al_findnext(&ff) == 0); + al_findclose(&ff); + return first_nonstd_fn; +} + +bool search_for_game_data_file(String &filename, String &search_path) +{ + Debug::Printf("Looking for the game data file"); + // 1. From command line argument, treated as a directory + if (!cmdGameDataPath.IsEmpty()) + { + // set from cmd arg (do any conversions if needed) + filename = cmdGameDataPath; + if (!filename.IsEmpty() && Path::IsDirectory(filename)) + { + search_path = filename; + filename = find_game_data_in_directory(search_path); + } + } + // 2.2. Search in the provided data dir + else if (!usetup.data_files_dir.IsEmpty()) + { + search_path = usetup.data_files_dir; + filename = find_game_data_in_directory(search_path); + } + // 3. Look in known locations + else + { + // 3.1. Look for attachment in the running executable + // + // this will use argument zero, the executable's name + filename = GetPathFromCmdArg(0); + if (filename.IsEmpty() || !Common::AssetManager::IsDataFile(filename)) + { + // 3.2 Look in current directory + search_path = Directory::GetCurrentDirectory(); + filename = find_game_data_in_directory(search_path); + if (filename.IsEmpty()) + { + // 3.3 Look in executable's directory (if it's different from current dir) + if (Path::ComparePaths(appDirectory, search_path)) + { + search_path = appDirectory; + filename = find_game_data_in_directory(search_path); + } + } + } + } + + // Finally, store game file's absolute path, or report error + if (filename.IsEmpty()) + { + Debug::Printf(kDbgMsg_Error, "Game data file could not be found. Search path used: '%s'", search_path.GetCStr()); + return false; + } + filename = Path::MakeAbsolutePath(filename); + Debug::Printf(kDbgMsg_Info, "Located game data file: %s", filename.GetCStr()); + return true; +} + +// Try to initialize main game package found at the given path +bool engine_try_init_gamedata(String gamepak_path) +{ + // Search for an available game package in the known locations + AssetError err = AssetManager::SetDataFile(gamepak_path); + if (err != kAssetNoError) + { + platform->DisplayAlert("ERROR: The game data is missing, is of unsupported format or corrupt.\nFile: '%s'", gamepak_path.GetCStr()); + return false; + } + return true; +} + +void engine_init_fonts() +{ + Debug::Printf(kDbgMsg_Info, "Initializing TTF renderer"); + + init_font_renderer(); +} + +void engine_init_mouse() +{ + int res = minstalled(); + if (res < 0) + Debug::Printf(kDbgMsg_Info, "Initializing mouse: failed"); + else + Debug::Printf(kDbgMsg_Info, "Initializing mouse: number of buttons reported is %d", res); + Mouse::SetSpeed(usetup.mouse_speed); +} + +void engine_locate_speech_pak() +{ + play.want_speech=-2; + + if (!usetup.no_speech_pack) { + String speech_file = "speech.vox"; + String speech_filepath = find_assetlib(speech_file); + if (!speech_filepath.IsEmpty()) { + Debug::Printf("Initializing speech vox"); + if (AssetManager::SetDataFile(speech_filepath)!=Common::kAssetNoError) { + platform->DisplayAlert("Unable to read voice pack, file could be corrupted or of unknown format.\nSpeech voice-over will be disabled."); + AssetManager::SetDataFile(ResPaths.GamePak.Path); // switch back to the main data pack + return; + } + // TODO: why is this read right here??? move this to InitGameState! + Stream *speechsync = AssetManager::OpenAsset("syncdata.dat"); + if (speechsync != nullptr) { + // this game has voice lip sync + int lipsync_fmt = speechsync->ReadInt32(); + if (lipsync_fmt != 4) + { + Debug::Printf(kDbgMsg_Info, "Unknown speech lip sync format (%d).\nLip sync disabled.", lipsync_fmt); + } + else { + numLipLines = speechsync->ReadInt32(); + splipsync = (SpeechLipSyncLine*)malloc (sizeof(SpeechLipSyncLine) * numLipLines); + for (int ee = 0; ee < numLipLines; ee++) + { + splipsync[ee].numPhonemes = speechsync->ReadInt16(); + speechsync->Read(splipsync[ee].filename, 14); + splipsync[ee].endtimeoffs = (int*)malloc(splipsync[ee].numPhonemes * sizeof(int)); + speechsync->ReadArrayOfInt32(splipsync[ee].endtimeoffs, splipsync[ee].numPhonemes); + splipsync[ee].frame = (short*)malloc(splipsync[ee].numPhonemes * sizeof(short)); + speechsync->ReadArrayOfInt16(splipsync[ee].frame, splipsync[ee].numPhonemes); + } + } + delete speechsync; + } + AssetManager::SetDataFile(ResPaths.GamePak.Path); // switch back to the main data pack + Debug::Printf(kDbgMsg_Info, "Voice pack found and initialized."); + play.want_speech=1; + } + else if (Path::ComparePaths(ResPaths.DataDir, get_voice_install_dir()) != 0) + { + // If we have custom voice directory set, we will enable voice-over even if speech.vox does not exist + Debug::Printf(kDbgMsg_Info, "Voice pack was not found, but voice installation directory is defined: enabling voice-over."); + play.want_speech=1; + } + ResPaths.SpeechPak.Name = speech_file; + ResPaths.SpeechPak.Path = speech_filepath; + } +} + +void engine_locate_audio_pak() +{ + play.separate_music_lib = 0; + String music_file = game.GetAudioVOXName(); + String music_filepath = find_assetlib(music_file); + if (!music_filepath.IsEmpty()) + { + if (AssetManager::SetDataFile(music_filepath) == kAssetNoError) + { + AssetManager::SetDataFile(ResPaths.GamePak.Path); + Debug::Printf(kDbgMsg_Info, "%s found and initialized.", music_file.GetCStr()); + play.separate_music_lib = 1; + ResPaths.AudioPak.Name = music_file; + ResPaths.AudioPak.Path = music_filepath; + } + else + { + platform->DisplayAlert("Unable to initialize digital audio pack '%s', file could be corrupt or of unsupported format.", + music_file.GetCStr()); + } + } +} + +void engine_init_keyboard() +{ +#ifdef ALLEGRO_KEYBOARD_HANDLER + Debug::Printf(kDbgMsg_Info, "Initializing keyboard"); + + install_keyboard(); +#endif +#if AGS_PLATFORM_OS_LINUX + setlocale(LC_NUMERIC, "C"); // needed in X platform because install keyboard affects locale of printfs +#endif +} + +void engine_init_timer() +{ + Debug::Printf(kDbgMsg_Info, "Install timer"); + + skipMissedTicks(); +} + +bool try_install_sound(int digi_id, int midi_id, String *p_err_msg = nullptr) +{ + Debug::Printf(kDbgMsg_Info, "Trying to init: digital driver ID: '%s' (0x%x), MIDI driver ID: '%s' (0x%x)", + AlIDToChars(digi_id).s, digi_id, AlIDToChars(midi_id).s, midi_id); + + if (install_sound(digi_id, midi_id, nullptr) == 0) + return true; + // Allegro does not let you try digital and MIDI drivers separately, + // and does not indicate which driver failed by return value. + // Therefore we try to guess. + if (p_err_msg) + *p_err_msg = get_allegro_error(); + if (midi_id != MIDI_NONE) + { + Debug::Printf(kDbgMsg_Error, "Failed to init one of the drivers; Error: '%s'.\nWill try to start without MIDI", get_allegro_error()); + if (install_sound(digi_id, MIDI_NONE, nullptr) == 0) + return true; + } + if (digi_id != DIGI_NONE) + { + Debug::Printf(kDbgMsg_Error, "Failed to init one of the drivers; Error: '%s'.\nWill try to start without DIGI", get_allegro_error()); + if (install_sound(DIGI_NONE, midi_id, nullptr) == 0) + return true; + } + Debug::Printf(kDbgMsg_Error, "Failed to init sound drivers. Error: %s", get_allegro_error()); + return false; +} + +// Attempts to predict a digital driver Allegro would chose, and get its maximal voices +std::pair autodetect_driver(_DRIVER_INFO *driver_list, int (*detect_audio_driver)(int), const char *type) +{ + for (int i = 0; driver_list[i].driver; ++i) + { + if (driver_list[i].autodetect) + { + int voices = detect_audio_driver(driver_list[i].id); + if (voices != 0) + return std::make_pair(driver_list[i].id, voices); + Debug::Printf(kDbgMsg_Warn, "Failed to detect %s driver %s; Error: '%s'.", + type, AlIDToChars(driver_list[i].id).s, get_allegro_error()); + } + } + return std::make_pair(0, 0); +} + +// Decides which audio driver to request from Allegro. +// Returns a pair of audio card ID and max available voices. +std::pair decide_audiodriver(int try_id, _DRIVER_INFO *driver_list, + int(*detect_audio_driver)(int), int &al_drv_id, const char *type) +{ + if (try_id == 0) // no driver + return std::make_pair(0, 0); + al_drv_id = 0; // the driver id will be set by library if one was found + if (try_id > 0) + { + int voices = detect_audio_driver(try_id); + if (al_drv_id == try_id && voices != 0) // found and detected + return std::make_pair(try_id, voices); + if (voices == 0) // found in list but detect failed + Debug::Printf(kDbgMsg_Error, "Failed to detect %s driver %s; Error: '%s'.", type, AlIDToChars(try_id).s, get_allegro_error()); + else // not found at all + Debug::Printf(kDbgMsg_Error, "Unknown %s driver: %s, will try to find suitable one.", type, AlIDToChars(try_id).s); + } + return autodetect_driver(driver_list, detect_audio_driver, type); +} + +void engine_init_audio() +{ + Debug::Printf("Initializing sound drivers"); + int digi_id = usetup.digicard; + int midi_id = usetup.midicard; + int digi_voices = -1; + int midi_voices = -1; + // MOD player would need certain minimal number of voices + // TODO: find out if this is still relevant? + if (usetup.mod_player) + digi_voices = NUM_DIGI_VOICES; + + Debug::Printf(kDbgMsg_Info, "Sound settings: digital driver ID: '%s' (0x%x), MIDI driver ID: '%s' (0x%x)", + AlIDToChars(digi_id).s, digi_id, AlIDToChars(midi_id).s, midi_id); + + // First try if drivers are supported, and switch to autodetect if explicit option failed + _DRIVER_INFO *digi_drivers = system_driver->digi_drivers ? system_driver->digi_drivers() : _digi_driver_list; + std::pair digi_drv = decide_audiodriver(digi_id, digi_drivers, detect_digi_driver, digi_card, "digital"); + _DRIVER_INFO *midi_drivers = system_driver->midi_drivers ? system_driver->midi_drivers() : _midi_driver_list; + std::pair midi_drv = decide_audiodriver(midi_id, midi_drivers, detect_midi_driver, midi_card, "MIDI"); + + // Now, knowing which drivers we suppose to install, decide on which voices we reserve + digi_id = digi_drv.first; + midi_id = midi_drv.first; + const int max_digi_voices = digi_drv.second; + const int max_midi_voices = midi_drv.second; + if (digi_voices > max_digi_voices) + digi_voices = max_digi_voices; + // NOTE: we do not specify number of MIDI voices, so don't have to calculate available here + + reserve_voices(digi_voices, midi_voices); + // maybe this line will solve the sound volume? [??? wth is this] + set_volume_per_voice(1); + + String err_msg; + bool sound_res = try_install_sound(digi_id, midi_id, &err_msg); + if (!sound_res) + { + Debug::Printf(kDbgMsg_Error, "Everything failed, disabling sound."); + reserve_voices(0, 0); + install_sound(DIGI_NONE, MIDI_NONE, nullptr); + } + // Only display a warning if they wanted a sound card + const bool digi_failed = usetup.digicard != DIGI_NONE && digi_card == DIGI_NONE; + const bool midi_failed = usetup.midicard != MIDI_NONE && midi_card == MIDI_NONE; + if (digi_failed || midi_failed) + { + platform->DisplayAlert("Warning: cannot enable %s.\nProblem: %s.\n\nYou may supress this message by disabling %s in the game setup.", + (digi_failed && midi_failed ? "game audio" : (digi_failed ? "digital audio" : "MIDI audio") ), + (err_msg.IsEmpty() ? "No compatible drivers found in the system" : err_msg.GetCStr()), + (digi_failed && midi_failed ? "sound" : (digi_failed ? "digital sound" : "MIDI sound") )); + } + + usetup.digicard = digi_card; + usetup.midicard = midi_card; + + Debug::Printf(kDbgMsg_Info, "Installed digital driver ID: '%s' (0x%x), MIDI driver ID: '%s' (0x%x)", + AlIDToChars(digi_card).s, digi_card, AlIDToChars(midi_card).s, midi_card); + + if (digi_card == DIGI_NONE) + { + // disable speech and music if no digital sound + // therefore the MIDI soundtrack will be used if present, + // and the voice mode should not go to Voice Only + play.want_speech = -2; + play.separate_music_lib = 0; + } + if (usetup.mod_player && digi_driver->voices < NUM_DIGI_VOICES) + { + // disable MOD player if there's not enough digital voices + // TODO: find out if this is still relevant? + usetup.mod_player = 0; + } + +#if AGS_PLATFORM_OS_WINDOWS + if (digi_card == DIGI_DIRECTX(0)) + { + // DirectX mixer seems to buffer an extra sample itself + use_extra_sound_offset = 1; + } +#endif +} + +void engine_init_debug() +{ + //set_volume(255,-1); + if ((debug_flags & (~DBG_DEBUGMODE)) >0) { + platform->DisplayAlert("Engine debugging enabled.\n" + "\nNOTE: You have selected to enable one or more engine debugging options.\n" + "These options cause many parts of the game to behave abnormally, and you\n" + "may not see the game as you are used to it. The point is to test whether\n" + "the engine passes a point where it is crashing on you normally.\n" + "[Debug flags enabled: 0x%02X]",debug_flags); + } +} + +void atexit_handler() { + if (proper_exit==0) { + platform->DisplayAlert("Error: the program has exited without requesting it.\n" + "Program pointer: %+03d (write this number down), ACI version %s\n" + "If you see a list of numbers above, please write them down and contact\n" + "developers. Otherwise, note down any other information displayed.", + our_eip, EngineVersion.LongString.GetCStr()); + } +} + +void engine_init_exit_handler() +{ + Debug::Printf(kDbgMsg_Info, "Install exit handler"); + + atexit(atexit_handler); +} + +void engine_init_rand() +{ + play.randseed = time(nullptr); + srand (play.randseed); +} + +void engine_init_pathfinder() +{ + init_pathfinder(loaded_game_file_version); +} + +void engine_pre_init_gfx() +{ + //Debug::Printf("Initialize gfx"); + + //platform->InitialiseAbufAtStartup(); +} + +int engine_load_game_data() +{ + Debug::Printf("Load game data"); + our_eip=-17; + HError err = load_game_file(); + if (!err) + { + proper_exit=1; + platform->FinishedUsingGraphicsMode(); + display_game_file_error(err); + return EXIT_ERROR; + } + return 0; +} + +int engine_check_register_game() +{ + if (justRegisterGame) + { + platform->RegisterGameWithGameExplorer(); + proper_exit = 1; + return EXIT_NORMAL; + } + + if (justUnRegisterGame) + { + platform->UnRegisterGameWithGameExplorer(); + proper_exit = 1; + return EXIT_NORMAL; + } + + return 0; +} + +void engine_init_title() +{ + our_eip=-91; + set_window_title(game.gamename); + Debug::Printf(kDbgMsg_Info, "Game title: '%s'", game.gamename); +} + +void engine_init_directories() +{ + Debug::Printf(kDbgMsg_Info, "Data directory: %s", usetup.data_files_dir.GetCStr()); + if (!usetup.install_dir.IsEmpty()) + Debug::Printf(kDbgMsg_Info, "Optional install directory: %s", usetup.install_dir.GetCStr()); + if (!usetup.install_audio_dir.IsEmpty()) + Debug::Printf(kDbgMsg_Info, "Optional audio directory: %s", usetup.install_audio_dir.GetCStr()); + if (!usetup.install_voice_dir.IsEmpty()) + Debug::Printf(kDbgMsg_Info, "Optional voice-over directory: %s", usetup.install_voice_dir.GetCStr()); + if (!usetup.user_data_dir.IsEmpty()) + Debug::Printf(kDbgMsg_Info, "User data directory: %s", usetup.user_data_dir.GetCStr()); + if (!usetup.shared_data_dir.IsEmpty()) + Debug::Printf(kDbgMsg_Info, "Shared data directory: %s", usetup.shared_data_dir.GetCStr()); + + ResPaths.DataDir = usetup.data_files_dir; + ResPaths.GamePak.Path = usetup.main_data_filepath; + ResPaths.GamePak.Name = get_filename(usetup.main_data_filepath); + + set_install_dir(usetup.install_dir, usetup.install_audio_dir, usetup.install_voice_dir); + if (!usetup.install_dir.IsEmpty()) + { + // running in debugger: don't redirect to the game exe folder (_Debug) + // TODO: find out why we need to do this (and do we?) + ResPaths.DataDir = "."; + } + + // if end-user specified custom save path, use it + bool res = false; + if (!usetup.user_data_dir.IsEmpty()) + { + res = SetCustomSaveParent(usetup.user_data_dir); + if (!res) + { + Debug::Printf(kDbgMsg_Warn, "WARNING: custom user save path failed, using default system paths"); + res = false; + } + } + // if there is no custom path, or if custom path failed, use default system path + if (!res) + { + char newDirBuffer[MAX_PATH]; + sprintf(newDirBuffer, "%s/%s", UserSavedgamesRootToken.GetCStr(), game.saveGameFolderName); + SetSaveGameDirectoryPath(newDirBuffer); + } +} + +#if AGS_PLATFORM_OS_ANDROID +extern char android_base_directory[256]; +#endif // AGS_PLATFORM_OS_ANDROID + +int check_write_access() { + + if (platform->GetDiskFreeSpaceMB() < 2) + return 0; + + our_eip = -1895; + + // The Save Game Dir is the only place that we should write to + String svg_dir = get_save_game_directory(); + String tempPath = String::FromFormat("%s""tmptest.tmp", svg_dir.GetCStr()); + Stream *temp_s = Common::File::CreateFile(tempPath); + if (!temp_s) + // TODO: move this somewhere else (Android platform driver init?) +#if AGS_PLATFORM_OS_ANDROID + { + put_backslash(android_base_directory); + tempPath.Format("%s""tmptest.tmp", android_base_directory); + temp_s = Common::File::CreateFile(tempPath); + if (temp_s == NULL) return 0; + else SetCustomSaveParent(android_base_directory); + } +#else + return 0; +#endif // AGS_PLATFORM_OS_ANDROID + + our_eip = -1896; + + temp_s->Write("just to test the drive free space", 30); + delete temp_s; + + our_eip = -1897; + + if (::remove(tempPath)) + return 0; + + return 1; +} + +int engine_check_disk_space() +{ + Debug::Printf(kDbgMsg_Info, "Checking for disk space"); + + if (check_write_access()==0) { + platform->DisplayAlert("Unable to write in the savegame directory.\n%s", platform->GetDiskWriteAccessTroubleshootingText()); + proper_exit = 1; + return EXIT_ERROR; + } + + return 0; +} + +int engine_check_font_was_loaded() +{ + if (!font_first_renderer_loaded()) + { + platform->DisplayAlert("No game fonts found. At least one font is required to run the game."); + proper_exit = 1; + return EXIT_ERROR; + } + + return 0; +} + +void engine_init_modxm_player() +{ +#ifndef PSP_NO_MOD_PLAYBACK + if (game.options[OPT_NOMODMUSIC]) + usetup.mod_player = 0; + + if (usetup.mod_player) { + Debug::Printf(kDbgMsg_Info, "Initializing MOD/XM player"); + + if (init_mod_player(NUM_MOD_DIGI_VOICES) < 0) { + platform->DisplayAlert("Warning: install_mod: MOD player failed to initialize."); + usetup.mod_player=0; + } + } +#else + usetup.mod_player = 0; + Debug::Printf(kDbgMsg_Info, "Compiled without MOD/XM player"); +#endif +} + +// Do the preload graphic if available +void show_preload() +{ + color temppal[256]; + Bitmap *splashsc = BitmapHelper::CreateRawBitmapOwner( load_pcx("preload.pcx",temppal) ); + if (splashsc != nullptr) + { + Debug::Printf("Displaying preload image"); + if (splashsc->GetColorDepth() == 8) + set_palette_range(temppal, 0, 255, 0); + if (gfxDriver->UsesMemoryBackBuffer()) + gfxDriver->GetMemoryBackBuffer()->Clear(); + + const Rect &view = play.GetMainViewport(); + Bitmap *tsc = BitmapHelper::CreateBitmapCopy(splashsc, game.GetColorDepth()); + if (!gfxDriver->HasAcceleratedTransform() && view.GetSize() != tsc->GetSize()) + { + Bitmap *stretched = new Bitmap(view.GetWidth(), view.GetHeight(), tsc->GetColorDepth()); + stretched->StretchBlt(tsc, RectWH(0, 0, view.GetWidth(), view.GetHeight())); + delete tsc; + tsc = stretched; + } + IDriverDependantBitmap *ddb = gfxDriver->CreateDDBFromBitmap(tsc, false, true); + ddb->SetStretch(view.GetWidth(), view.GetHeight()); + gfxDriver->ClearDrawLists(); + gfxDriver->DrawSprite(0, 0, ddb); + render_to_screen(); + gfxDriver->DestroyDDB(ddb); + delete splashsc; + delete tsc; + platform->Delay(500); + } +} + +int engine_init_sprites() +{ + Debug::Printf(kDbgMsg_Info, "Initialize sprites"); + + HError err = spriteset.InitFile(SpriteCache::DefaultSpriteFileName, SpriteCache::DefaultSpriteIndexName); + if (!err) + { + platform->FinishedUsingGraphicsMode(); + allegro_exit(); + proper_exit=1; + platform->DisplayAlert("Could not load sprite set file %s\n%s", + SpriteCache::DefaultSpriteFileName.GetCStr(), + err->FullMessage().GetCStr()); + return EXIT_ERROR; + } + + return 0; +} + +void engine_init_game_settings() +{ + our_eip=-7; + Debug::Printf("Initialize game settings"); + + int ee; + + for (ee = 0; ee < MAX_ROOM_OBJECTS + game.numcharacters; ee++) + actsps[ee] = nullptr; + + for (ee=0;ee<256;ee++) { + if (game.paluses[ee]!=PAL_BACKGROUND) + palette[ee]=game.defpal[ee]; + } + + for (ee = 0; ee < game.numcursors; ee++) + { + // The cursor graphics are assigned to mousecurs[] and so cannot + // be removed from memory + if (game.mcurs[ee].pic >= 0) + spriteset.Precache(game.mcurs[ee].pic); + + // just in case they typed an invalid view number in the editor + if (game.mcurs[ee].view >= game.numviews) + game.mcurs[ee].view = -1; + + if (game.mcurs[ee].view >= 0) + precache_view (game.mcurs[ee].view); + } + // may as well preload the character gfx + if (playerchar->view >= 0) + precache_view (playerchar->view); + + for (ee = 0; ee < MAX_ROOM_OBJECTS; ee++) + objcache[ee].image = nullptr; + + /* dummygui.guiId = -1; + dummyguicontrol.guin = -1; + dummyguicontrol.objn = -1;*/ + + our_eip=-6; + // game.chars[0].talkview=4; + //init_language_text(game.langcodes[0]); + + for (ee = 0; ee < MAX_ROOM_OBJECTS; ee++) { + scrObj[ee].id = ee; + // 64 bit: Using the id instead + // scrObj[ee].obj = NULL; + } + + for (ee=0;ee= 0) { + // set initial loop to 0 + game.chars[ee].loop = 0; + // or to 1 if they don't have up/down frames + if (views[game.chars[ee].view].loops[0].numFrames < 1) + game.chars[ee].loop = 1; + } + charextra[ee].process_idle_this_time = 0; + charextra[ee].invorder_count = 0; + charextra[ee].slow_move_counter = 0; + charextra[ee].animwait = 0; + } + // multiply up gui positions + guibg = (Bitmap **)malloc(sizeof(Bitmap *) * game.numgui); + guibgbmp = (IDriverDependantBitmap**)malloc(sizeof(IDriverDependantBitmap*) * game.numgui); + for (ee=0;eeinv[ee]=1; + else playerchar->inv[ee]=0; + } + play.score=0; + play.sierra_inv_color=7; + // copy the value set by the editor + if (game.options[OPT_GLOBALTALKANIMSPD] >= 0) + { + play.talkanim_speed = game.options[OPT_GLOBALTALKANIMSPD]; + game.options[OPT_GLOBALTALKANIMSPD] = 1; + } + else + { + play.talkanim_speed = -game.options[OPT_GLOBALTALKANIMSPD] - 1; + game.options[OPT_GLOBALTALKANIMSPD] = 0; + } + play.inv_item_wid = 40; + play.inv_item_hit = 22; + play.messagetime=-1; + play.disabled_user_interface=0; + play.gscript_timer=-1; + play.debug_mode=game.options[OPT_DEBUGMODE]; + play.inv_top=0; + play.inv_numdisp=0; + play.obsolete_inv_numorder=0; + play.text_speed=15; + play.text_min_display_time_ms = 1000; + play.ignore_user_input_after_text_timeout_ms = 500; + play.ClearIgnoreInput(); + play.lipsync_speed = 15; + play.close_mouth_speech_time = 10; + play.disable_antialiasing = 0; + play.rtint_enabled = false; + play.rtint_level = 0; + play.rtint_light = 0; + play.text_speed_modifier = 0; + play.text_align = kHAlignLeft; + // Make the default alignment to the right with right-to-left text + if (game.options[OPT_RIGHTLEFTWRITE]) + play.text_align = kHAlignRight; + + play.speech_bubble_width = get_fixed_pixel_size(100); + play.bg_frame=0; + play.bg_frame_locked=0; + play.bg_anim_delay=0; + play.anim_background_speed = 0; + play.silent_midi = 0; + play.current_music_repeating = 0; + play.skip_until_char_stops = -1; + play.get_loc_name_last_time = -1; + play.get_loc_name_save_cursor = -1; + play.restore_cursor_mode_to = -1; + play.restore_cursor_image_to = -1; + play.ground_level_areas_disabled = 0; + play.next_screen_transition = -1; + play.temporarily_turned_off_character = -1; + play.inv_backwards_compatibility = 0; + play.gamma_adjustment = 100; + play.do_once_tokens.resize(0); + play.music_queue_size = 0; + play.shakesc_length = 0; + play.wait_counter=0; + play.key_skip_wait = SKIP_NONE; + play.cur_music_number=-1; + play.music_repeat=1; + play.music_master_volume=100 + LegacyMusicMasterVolumeAdjustment; + play.digital_master_volume = 100; + play.screen_flipped=0; + play.cant_skip_speech = user_to_internal_skip_speech((SkipSpeechStyle)game.options[OPT_NOSKIPTEXT]); + play.sound_volume = 255; + play.speech_volume = 255; + play.normal_font = 0; + play.speech_font = 1; + play.speech_text_shadow = 16; + play.screen_tint = -1; + play.bad_parsed_word[0] = 0; + play.swap_portrait_side = 0; + play.swap_portrait_lastchar = -1; + play.swap_portrait_lastlastchar = -1; + play.in_conversation = 0; + play.skip_display = 3; + play.no_multiloop_repeat = 0; + play.in_cutscene = 0; + play.fast_forward = 0; + play.totalscore = game.totalscore; + play.roomscript_finished = 0; + play.no_textbg_when_voice = 0; + play.max_dialogoption_width = get_fixed_pixel_size(180); + play.no_hicolor_fadein = 0; + play.bgspeech_game_speed = 0; + play.bgspeech_stay_on_display = 0; + play.unfactor_speech_from_textlength = 0; + play.mp3_loop_before_end = 70; + play.speech_music_drop = 60; + play.room_changes = 0; + play.check_interaction_only = 0; + play.replay_hotkey_unused = -1; // StartRecording: not supported. + play.dialog_options_x = 0; + play.dialog_options_y = 0; + play.min_dialogoption_width = 0; + play.disable_dialog_parser = 0; + play.ambient_sounds_persist = 0; + play.screen_is_faded_out = 0; + play.player_on_region = 0; + play.top_bar_backcolor = 8; + play.top_bar_textcolor = 16; + play.top_bar_bordercolor = 8; + play.top_bar_borderwidth = 1; + play.top_bar_ypos = 25; + play.top_bar_font = -1; + play.screenshot_width = 160; + play.screenshot_height = 100; + play.speech_text_align = kHAlignCenter; + play.auto_use_walkto_points = 1; + play.inventory_greys_out = 0; + play.skip_speech_specific_key = 0; + play.abort_key = 324; // Alt+X + play.fade_to_red = 0; + play.fade_to_green = 0; + play.fade_to_blue = 0; + play.show_single_dialog_option = 0; + play.keep_screen_during_instant_transition = 0; + play.read_dialog_option_colour = -1; + play.speech_portrait_placement = 0; + play.speech_portrait_x = 0; + play.speech_portrait_y = 0; + play.speech_display_post_time_ms = 0; + play.dialog_options_highlight_color = DIALOG_OPTIONS_HIGHLIGHT_COLOR_DEFAULT; + play.speech_has_voice = false; + play.speech_voice_blocking = false; + play.speech_in_post_state = false; + play.narrator_speech = game.playercharacter; + play.crossfading_out_channel = 0; + play.speech_textwindow_gui = game.options[OPT_TWCUSTOM]; + if (play.speech_textwindow_gui == 0) + play.speech_textwindow_gui = -1; + strcpy(play.game_name, game.gamename); + play.lastParserEntry[0] = 0; + play.follow_change_room_timer = 150; + for (ee = 0; ee < MAX_ROOM_BGFRAMES; ee++) + play.raw_modified[ee] = 0; + play.game_speed_modifier = 0; + if (debug_flags & DBG_DEBUGMODE) + play.debug_mode = 1; + gui_disabled_style = convert_gui_disabled_style(game.options[OPT_DISABLEOFF]); + play.shake_screen_yoff = 0; + + memset(&play.walkable_areas_on[0],1,MAX_WALK_AREAS+1); + memset(&play.script_timers[0],0,MAX_TIMERS * sizeof(int)); + memset(&play.default_audio_type_volumes[0], -1, MAX_AUDIO_TYPES * sizeof(int)); + + // reset graphical script vars (they're still used by some games) + for (ee = 0; ee < MAXGLOBALVARS; ee++) + play.globalvars[ee] = 0; + + for (ee = 0; ee < MAXGLOBALSTRINGS; ee++) + play.globalstrings[ee][0] = 0; + + if (!usetup.translation.IsEmpty()) + init_translation (usetup.translation, "", true); + + update_invorder(); + displayed_room = -10; + + currentcursor=0; + our_eip=-4; + mousey=100; // stop icon bar popping up + + // We use same variable to read config and be used at runtime for now, + // so update it here with regards to game design option + usetup.RenderAtScreenRes = + (game.options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_UserDefined && usetup.RenderAtScreenRes) || + game.options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_Enabled; +} + +void engine_setup_scsystem_auxiliary() +{ + // ScriptSystem::aci_version is only 10 chars long + strncpy(scsystem.aci_version, EngineVersion.LongString, 10); + if (usetup.override_script_os >= 0) + { + scsystem.os = usetup.override_script_os; + } + else + { + scsystem.os = platform->GetSystemOSID(); + } +} + +void engine_update_mp3_thread() +{ + update_mp3_thread(); + platform->Delay(50); +} + +void engine_start_multithreaded_audio() +{ + // PSP: Initialize the sound cache. + clear_sound_cache(); + + // Create sound update thread. This is a workaround for sound stuttering. + if (psp_audio_multithreaded) + { + if (!audioThread.CreateAndStart(engine_update_mp3_thread, true)) + { + Debug::Printf(kDbgMsg_Info, "Failed to start audio thread, audio will be processed on the main thread"); + psp_audio_multithreaded = 0; + } + else + { + Debug::Printf(kDbgMsg_Info, "Audio thread started"); + } + } + else + { + Debug::Printf(kDbgMsg_Info, "Audio is processed on the main thread"); + } +} + +void engine_prepare_to_start_game() +{ + Debug::Printf("Prepare to start game"); + + engine_setup_scsystem_auxiliary(); + engine_start_multithreaded_audio(); + +#if AGS_PLATFORM_OS_ANDROID + if (psp_load_latest_savegame) + selectLatestSavegame(); +#endif +} + +// TODO: move to test unit +Bitmap *test_allegro_bitmap; +IDriverDependantBitmap *test_allegro_ddb; +void allegro_bitmap_test_init() +{ + test_allegro_bitmap = nullptr; + // Switched the test off for now + //test_allegro_bitmap = AllegroBitmap::CreateBitmap(320,200,32); +} + +// Only allow searching around for game data on desktop systems; +// otherwise use explicit argument either from program wrapper, command-line +// or read from default config. +#if AGS_PLATFORM_OS_WINDOWS || AGS_PLATFORM_OS_LINUX || AGS_PLATFORM_OS_MACOS + #define AGS_SEARCH_FOR_GAME_ON_LAUNCH +#endif + +// Define location of the game data either using direct settings or searching +// for the available resource packs in common locations +HError define_gamedata_location_checkall(const String &exe_path) +{ + // First try if they provided a startup option + if (!cmdGameDataPath.IsEmpty()) + { + // If not a valid path - bail out + if (!Path::IsFileOrDir(cmdGameDataPath)) + return new Error(String::FromFormat("Defined game location is not a valid path.\nPath: '%s'", cmdGameDataPath.GetCStr())); + // Switch working dir to this path to be able to look for config and other assets there + Directory::SetCurrentDirectory(Path::GetDirectoryPath(cmdGameDataPath)); + // If it's a file, then keep it and proceed + if (Path::IsFile(cmdGameDataPath)) + { + usetup.main_data_filepath = cmdGameDataPath; + return HError::None(); + } + } + // Read game data location from the default config file. + // This is an optional setting that may instruct which game file to use as a primary asset library. + ConfigTree cfg; + String def_cfg_file = find_default_cfg_file(exe_path); + IniUtil::Read(def_cfg_file, cfg); + read_game_data_location(cfg); + if (!usetup.main_data_filename.IsEmpty()) + return HError::None(); + +#if defined (AGS_SEARCH_FOR_GAME_ON_LAUNCH) + // No direct filepath provided, search in common locations. + String path, search_path; + if (search_for_game_data_file(path, search_path)) + { + usetup.main_data_filepath = path; + return HError::None(); + } + return new Error("Engine was not able to find any compatible game data.", + search_path.IsEmpty() ? String() : String::FromFormat("Searched in: %s", search_path.GetCStr())); +#else + return new Error("The game location was not defined by startup settings."); +#endif +} + +// Define location of the game data +bool define_gamedata_location(const String &exe_path) +{ + HError err = define_gamedata_location_checkall(exe_path); + if (!err) + { + platform->DisplayAlert("ERROR: Unable to determine game data.\n%s", err->FullMessage().GetCStr()); + main_print_help(); + return false; + } + + // On success: set all the necessary path and filename settings, + // derive missing ones from available. + if (usetup.main_data_filename.IsEmpty()) + { + usetup.main_data_filename = get_filename(usetup.main_data_filepath); + } + else if (usetup.main_data_filepath.IsEmpty()) + { + if (usetup.data_files_dir.IsEmpty() || !is_relative_filename(usetup.main_data_filename)) + usetup.main_data_filepath = usetup.main_data_filename; + else + usetup.main_data_filepath = Path::ConcatPaths(usetup.data_files_dir, usetup.main_data_filename); + } + if (usetup.data_files_dir.IsEmpty()) + usetup.data_files_dir = Path::GetDirectoryPath(usetup.main_data_filepath); + return true; +} + +// Find and preload main game data +bool engine_init_gamedata(const String &exe_path) +{ + Debug::Printf(kDbgMsg_Info, "Initializing game data"); + if (!define_gamedata_location(exe_path)) + return false; + if (!engine_try_init_gamedata(usetup.main_data_filepath)) + return false; + + // Pre-load game name and savegame folder names from data file + // TODO: research if that is possible to avoid this step and just + // read the full head game data at this point. This might require + // further changes of the order of initialization. + HError err = preload_game_data(); + if (!err) + { + display_game_file_error(err); + return false; + } + return true; +} + +void engine_read_config(const String &exe_path, ConfigTree &cfg) +{ + // Read default configuration file + String def_cfg_file = find_default_cfg_file(exe_path); + IniUtil::Read(def_cfg_file, cfg); + + // Disabled on Windows because people were afraid that this config could be mistakenly + // created by some installer and screw up their games. Until any kind of solution is found. + String user_global_cfg_file; +#if ! AGS_PLATFORM_OS_WINDOWS + // Read user global configuration file + user_global_cfg_file = find_user_global_cfg_file(); + if (Path::ComparePaths(user_global_cfg_file, def_cfg_file) != 0) + IniUtil::Read(user_global_cfg_file, cfg); +#endif + + // Read user configuration file + String user_cfg_file = find_user_cfg_file(); + if (Path::ComparePaths(user_cfg_file, def_cfg_file) != 0 && + Path::ComparePaths(user_cfg_file, user_global_cfg_file) != 0) + IniUtil::Read(user_cfg_file, cfg); + + // Apply overriding options from mobile port settings + // TODO: normally, those should be instead stored in the same config file in a uniform way + // NOTE: the variable is historically called "ignore" but we use it in "override" meaning here + if (psp_ignore_acsetup_cfg_file) + override_config_ext(cfg); +} + +// Gathers settings from all available sources into single ConfigTree +void engine_prepare_config(ConfigTree &cfg, const String &exe_path, const ConfigTree &startup_opts) +{ + Debug::Printf(kDbgMsg_Info, "Setting up game configuration"); + // Read configuration files + engine_read_config(exe_path, cfg); + // Merge startup options in + for (const auto §n : startup_opts) + for (const auto &opt : sectn.second) + cfg[sectn.first][opt.first] = opt.second; + + // Add "meta" config settings to let setup application(s) + // display correct properties to the user + INIwriteint(cfg, "misc", "defaultres", game.GetResolutionType()); + INIwriteint(cfg, "misc", "letterbox", game.options[OPT_LETTERBOX]); + INIwriteint(cfg, "misc", "game_width", game.GetDefaultRes().Width); + INIwriteint(cfg, "misc", "game_height", game.GetDefaultRes().Height); + INIwriteint(cfg, "misc", "gamecolordepth", game.color_depth * 8); + if (game.options[OPT_RENDERATSCREENRES] != kRenderAtScreenRes_UserDefined) + { + // force enabled/disabled + INIwriteint(cfg, "graphics", "render_at_screenres", game.options[OPT_RENDERATSCREENRES] == kRenderAtScreenRes_Enabled); + INIwriteint(cfg, "disabled", "render_at_screenres", 1); + } +} + +// Applies configuration to the running game +void engine_set_config(const ConfigTree cfg) +{ + config_defaults(); + apply_config(cfg); + post_config(); +} + +// +// --tell command support: printing engine/game info by request +// +extern std::set tellInfoKeys; +static bool print_info_needs_game(const std::set &keys) +{ + return keys.count("all") > 0 || keys.count("config") > 0 || keys.count("configpath") > 0 || + keys.count("data") > 0; +} + +static void engine_print_info(const std::set &keys, const String &exe_path, ConfigTree *user_cfg) +{ + const bool all = keys.count("all") > 0; + ConfigTree data; + if (all || keys.count("engine") > 0) + { + data["engine"]["name"] = get_engine_name(); + data["engine"]["version"] = get_engine_version(); + } + if (all || keys.count("graphicdriver") > 0) + { + StringV drv; + AGS::Engine::GetGfxDriverFactoryNames(drv); + for (size_t i = 0; i < drv.size(); ++i) + { + data["graphicdriver"][String::FromFormat("%u", i)] = drv[i]; + } + } + if (all || keys.count("configpath") > 0) + { + String def_cfg_file = find_default_cfg_file(exe_path); + String gl_cfg_file = find_user_global_cfg_file(); + String user_cfg_file = find_user_cfg_file(); + data["config-path"]["default"] = def_cfg_file; + data["config-path"]["global"] = gl_cfg_file; + data["config-path"]["user"] = user_cfg_file; + } + if ((all || keys.count("config") > 0) && user_cfg) + { + for (const auto §n : *user_cfg) + { + String cfg_sectn = String::FromFormat("config@%s", sectn.first.GetCStr()); + for (const auto &opt : sectn.second) + data[cfg_sectn][opt.first] = opt.second; + } + } + if (all || keys.count("data") > 0) + { + data["data"]["gamename"] = game.gamename; + data["data"]["version"] = String::FromFormat("%d", loaded_game_file_version); + data["data"]["compiledwith"] = game.compiled_with; + data["data"]["basepack"] = usetup.main_data_filepath; + } + String full; + IniUtil::WriteToString(full, data); + platform->WriteStdOut("%s", full.GetCStr()); +} + +// Custom resource search callback for Allegro's system driver. +// It helps us direct Allegro to our game data location, because it won't know. +static int al_find_resource(char *dest, const char* resource, int dest_size) +{ + String path = Path::ConcatPaths(get_install_dir(), resource); + if (File::TestReadFile(path)) + { + snprintf(dest, dest_size, "%s", path.GetCStr()); + return 0; + } + return -1; +} + +// TODO: this function is still a big mess, engine/system-related initialization +// is mixed with game-related data adjustments. Divide it in parts, move game +// data init into either InitGameState() or other game method as appropriate. +int initialize_engine(const ConfigTree &startup_opts) +{ + if (engine_pre_init_callback) { + engine_pre_init_callback(); + } + + //----------------------------------------------------- + // Install backend + if (!engine_init_allegro()) + return EXIT_ERROR; + + //----------------------------------------------------- + // Locate game data and assemble game config + const String exe_path = global_argv[0]; + if (justTellInfo && !print_info_needs_game(tellInfoKeys)) + { + engine_print_info(tellInfoKeys, exe_path, nullptr); + return EXIT_NORMAL; + } + + if (!engine_init_gamedata(exe_path)) + return EXIT_ERROR; + ConfigTree cfg; + engine_prepare_config(cfg, exe_path, startup_opts); + if (justTellInfo) + { + engine_print_info(tellInfoKeys, exe_path, &cfg); + return EXIT_NORMAL; + } + // Test if need to run built-in setup program (where available) + if (justRunSetup) + { + int res; + if (!engine_run_setup(exe_path, cfg, res)) + return res; + } + // Set up game options from user config + engine_set_config(cfg); + engine_setup_allegro(); + engine_force_window(); + + our_eip = -190; + + //----------------------------------------------------- + // Init data paths and other directories, locate general data files + engine_init_directories(); + + our_eip = -191; + + engine_locate_speech_pak(); + + our_eip = -192; + + engine_locate_audio_pak(); + + our_eip = -193; + + // Assign custom find resource callback for limited Allegro operations + system_driver->find_resource = al_find_resource; + + //----------------------------------------------------- + // Begin setting up systems + engine_setup_window(); + + our_eip = -194; + + engine_init_fonts(); + + our_eip = -195; + + engine_init_keyboard(); + + our_eip = -196; + + engine_init_mouse(); + + our_eip = -197; + + engine_init_timer(); + + our_eip = -198; + + engine_init_audio(); + + our_eip = -199; + + engine_init_debug(); + + our_eip = -10; + + engine_init_exit_handler(); + + engine_init_rand(); + + engine_init_pathfinder(); + + set_game_speed(40); + + our_eip=-20; + our_eip=-19; + + int res = engine_load_game_data(); + if (res != 0) + return res; + + res = engine_check_register_game(); + if (res != 0) + return res; + + engine_init_title(); + + our_eip = -189; + + res = engine_check_disk_space(); + if (res != 0) + return res; + + // Make sure that at least one font was loaded in the process of loading + // the game data. + // TODO: Fold this check into engine_load_game_data() + res = engine_check_font_was_loaded(); + if (res != 0) + return res; + + our_eip = -179; + + engine_init_modxm_player(); + + engine_init_resolution_settings(game.GetGameRes()); + + // Attempt to initialize graphics mode + if (!engine_try_set_gfxmode_any(usetup.Screen)) + return EXIT_ERROR; + + SetMultitasking(0); + + // [ER] 2014-03-13 + // Hide the system cursor via allegro + show_os_cursor(MOUSE_CURSOR_NONE); + + show_preload(); + + res = engine_init_sprites(); + if (res != 0) + return res; + + engine_init_game_settings(); + + engine_prepare_to_start_game(); + + allegro_bitmap_test_init(); + + initialize_start_and_play_game(override_start_room, loadSaveGameOnStartup); + + quit("|bye!"); + return EXIT_NORMAL; +} + +bool engine_try_set_gfxmode_any(const ScreenSetup &setup) +{ + engine_shutdown_gfxmode(); + + const Size init_desktop = get_desktop_size(); + if (!graphics_mode_init_any(game.GetGameRes(), setup, ColorDepthOption(game.GetColorDepth()))) + return false; + + engine_post_gfxmode_setup(init_desktop); + return true; +} + +bool engine_try_switch_windowed_gfxmode() +{ + if (!gfxDriver || !gfxDriver->IsModeSet()) + return false; + + // Keep previous mode in case we need to revert back + DisplayMode old_dm = gfxDriver->GetDisplayMode(); + GameFrameSetup old_frame = graphics_mode_get_render_frame(); + + // Release engine resources that depend on display mode + engine_pre_gfxmode_release(); + + Size init_desktop = get_desktop_size(); + bool switch_to_windowed = !old_dm.Windowed; + ActiveDisplaySetting setting = graphics_mode_get_last_setting(switch_to_windowed); + DisplayMode last_opposite_mode = setting.Dm; + GameFrameSetup use_frame_setup = setting.FrameSetup; + + // If there are saved parameters for given mode (fullscreen/windowed) + // then use them, if there are not, get default setup for the new mode. + bool res; + if (last_opposite_mode.IsValid()) + { + res = graphics_mode_set_dm(last_opposite_mode); + } + else + { + // we need to clone from initial config, because not every parameter is set by graphics_mode_get_defaults() + DisplayModeSetup dm_setup = usetup.Screen.DisplayMode; + dm_setup.Windowed = !old_dm.Windowed; + graphics_mode_get_defaults(dm_setup.Windowed, dm_setup.ScreenSize, use_frame_setup); + res = graphics_mode_set_dm_any(game.GetGameRes(), dm_setup, old_dm.ColorDepth, use_frame_setup); + } + + // Apply corresponding frame render method + if (res) + res = graphics_mode_set_render_frame(use_frame_setup); + + if (!res) + { + // If failed, try switching back to previous gfx mode + res = graphics_mode_set_dm(old_dm) && + graphics_mode_set_render_frame(old_frame); + } + + if (res) + { + // If succeeded (with any case), update engine objects that rely on + // active display mode. + if (gfxDriver->GetDisplayMode().Windowed) + init_desktop = get_desktop_size(); + engine_post_gfxmode_setup(init_desktop); + } + ags_clear_input_buffer(); + return res; +} + +void engine_shutdown_gfxmode() +{ + if (!gfxDriver) + return; + + engine_pre_gfxsystem_shutdown(); + graphics_mode_shutdown(); +} + +const char *get_engine_name() +{ + return "Adventure Game Studio run-time engine"; +} + +const char *get_engine_version() { + return EngineVersion.LongString.GetCStr(); +} + +void engine_set_pre_init_callback(t_engine_pre_init_callback callback) { + engine_pre_init_callback = callback; +} diff --git a/engines/ags/engine/main/engine.h b/engines/ags/engine/main/engine.h new file mode 100644 index 00000000000..d5c2079a306 --- /dev/null +++ b/engines/ags/engine/main/engine.h @@ -0,0 +1,58 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __AGS_EE_MAIN__ENGINE_H +#define __AGS_EE_MAIN__ENGINE_H + +#include "util/ini_util.h" + +const char *get_engine_name(); +const char *get_engine_version(); +void show_preload(); +void engine_init_game_settings(); +int initialize_engine(const AGS::Common::ConfigTree &startup_opts); + +struct ScreenSetup; +// Try to set new graphics mode deduced from given configuration; +// if requested mode fails, tries to find any compatible mode close to the +// requested one. +bool engine_try_set_gfxmode_any(const ScreenSetup &setup); +// Tries to switch between fullscreen and windowed mode; uses previously saved +// setup if it is available, or default settings for the new mode +bool engine_try_switch_windowed_gfxmode(); +// Shutdown graphics mode (used before shutting down tha application) +void engine_shutdown_gfxmode(); + +using AGS::Common::String; +// Defines a package file location +struct PackLocation +{ + String Name; // filename, for the reference or to use as an ID + String Path; // full path +}; +// Game resource paths +struct ResourcePaths +{ + String DataDir; // path to the data directory + PackLocation GamePak; // main game package + PackLocation AudioPak; // audio package + PackLocation SpeechPak; // voice-over package +}; +extern ResourcePaths ResPaths; + +// Register a callback that will be called before engine is initialised. +// Used for apps to register their own plugins and other configuration +typedef void (*t_engine_pre_init_callback)(void); +extern void engine_set_pre_init_callback(t_engine_pre_init_callback callback); + +#endif // __AGS_EE_MAIN__ENGINE_H diff --git a/engines/ags/engine/main/engine_setup.cpp b/engines/ags/engine/main/engine_setup.cpp new file mode 100644 index 00000000000..ba9780030da --- /dev/null +++ b/engines/ags/engine/main/engine_setup.cpp @@ -0,0 +1,367 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" +#include "ac/common.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/game_version.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/mouse.h" +#include "ac/runtime_defines.h" +#include "ac/walkbehind.h" +#include "ac/dynobj/scriptsystem.h" +#include "debug/out.h" +#include "device/mousew32.h" +#include "font/fonts.h" +#include "gfx/ali3dexception.h" +#include "gfx/graphicsdriver.h" +#include "gui/guimain.h" +#include "gui/guiinv.h" +#include "main/graphics_mode.h" +#include "main/engine_setup.h" +#include "media/video/video.h" +#include "platform/base/agsplatformdriver.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern ScriptSystem scsystem; +extern int _places_r, _places_g, _places_b; +extern IGraphicsDriver *gfxDriver; + +int convert_16bit_bgr = 0; + +// Convert guis position and size to proper game resolution. +// Necessary for pre 3.1.0 games only to sync with modern engine. +void convert_gui_to_game_resolution(GameDataVersion filever) +{ + if (filever > kGameVersion_310) + return; + + const int mul = game.GetDataUpscaleMult(); + for (int i = 0; i < game.numcursors; ++i) + { + game.mcurs[i].hotx *= mul; + game.mcurs[i].hoty *= mul; + } + + for (int i = 0; i < game.numinvitems; ++i) + { + game.invinfo[i].hotx *= mul; + game.invinfo[i].hoty *= mul; + } + + for (int i = 0; i < game.numgui; ++i) + { + GUIMain*cgp = &guis[i]; + cgp->X *= mul; + cgp->Y *= mul; + if (cgp->Width < 1) + cgp->Width = 1; + if (cgp->Height < 1) + cgp->Height = 1; + // This is probably a way to fix GUIs meant to be covering whole screen + if (cgp->Width == game.GetDataRes().Width - 1) + cgp->Width = game.GetDataRes().Width; + + cgp->Width *= mul; + cgp->Height *= mul; + + cgp->PopupAtMouseY *= mul; + + for (int j = 0; j < cgp->GetControlCount(); ++j) + { + GUIObject *guio = cgp->GetControl(j); + guio->X *= mul; + guio->Y *= mul; + guio->Width *= mul; + guio->Height *= mul; + guio->IsActivated = false; + guio->OnResized(); + } + } +} + +// Convert certain coordinates to data resolution (only if it's different from game resolution). +// Necessary for 3.1.0 and above games with legacy "low-res coordinates" setting. +void convert_objects_to_data_resolution(GameDataVersion filever) +{ + if (filever < kGameVersion_310 || game.GetDataUpscaleMult() == 1) + return; + + const int mul = game.GetDataUpscaleMult(); + for (int i = 0; i < game.numcharacters; ++i) + { + game.chars[i].x /= mul; + game.chars[i].y /= mul; + } + + for (int i = 0; i < numguiinv; ++i) + { + guiinv[i].ItemWidth /= mul; + guiinv[i].ItemHeight /= mul; + guiinv[i].OnResized(); + } +} + +void engine_setup_system_gamesize() +{ + scsystem.width = game.GetGameRes().Width; + scsystem.height = game.GetGameRes().Height; + scsystem.viewport_width = game_to_data_coord(play.GetMainViewport().GetWidth()); + scsystem.viewport_height = game_to_data_coord(play.GetMainViewport().GetHeight()); +} + +void engine_init_resolution_settings(const Size game_size) +{ + Debug::Printf("Initializing resolution settings"); + usetup.textheight = getfontheight_outlined(0) + 1; + + Debug::Printf(kDbgMsg_Info, "Game native resolution: %d x %d (%d bit)%s", game_size.Width, game_size.Height, game.color_depth * 8, + game.IsLegacyLetterbox() ? " letterbox-by-design" : ""); + + convert_gui_to_game_resolution(loaded_game_file_version); + convert_objects_to_data_resolution(loaded_game_file_version); + + Rect viewport = RectWH(game_size); + play.SetMainViewport(viewport); + play.SetUIViewport(viewport); + engine_setup_system_gamesize(); +} + +// Setup gfx driver callbacks and options +void engine_post_gfxmode_driver_setup() +{ + gfxDriver->SetCallbackForPolling(update_polled_stuff_if_runtime); + gfxDriver->SetCallbackToDrawScreen(draw_game_screen_callback, construct_engine_overlay); + gfxDriver->SetCallbackForNullSprite(GfxDriverNullSpriteCallback); +} + +// Reset gfx driver callbacks +void engine_pre_gfxmode_driver_cleanup() +{ + gfxDriver->SetCallbackForPolling(nullptr); + gfxDriver->SetCallbackToDrawScreen(nullptr, nullptr); + gfxDriver->SetCallbackForNullSprite(nullptr); + gfxDriver->SetMemoryBackBuffer(nullptr); +} + +// Setup virtual screen +void engine_post_gfxmode_screen_setup(const DisplayMode &dm, bool recreate_bitmaps) +{ + if (recreate_bitmaps) + { + // TODO: find out if + // - we need to support this case at all; + // - if yes then which bitmaps need to be recreated (probably only video bitmaps and textures?) + } +} + +void engine_pre_gfxmode_screen_cleanup() +{ +} + +// Release virtual screen +void engine_pre_gfxsystem_screen_destroy() +{ +} + +// Setup color conversion parameters +void engine_setup_color_conversions(int coldepth) +{ + // default shifts for how we store the sprite data1 + _rgb_r_shift_32 = 16; + _rgb_g_shift_32 = 8; + _rgb_b_shift_32 = 0; + _rgb_r_shift_16 = 11; + _rgb_g_shift_16 = 5; + _rgb_b_shift_16 = 0; + _rgb_r_shift_15 = 10; + _rgb_g_shift_15 = 5; + _rgb_b_shift_15 = 0; + + // Most cards do 5-6-5 RGB, which is the format the files are saved in + // Some do 5-6-5 BGR, or 6-5-5 RGB, in which case convert the gfx + if ((coldepth == 16) && ((_rgb_b_shift_16 != 0) || (_rgb_r_shift_16 != 11))) + { + convert_16bit_bgr = 1; + if (_rgb_r_shift_16 == 10) { + // some very old graphics cards lie about being 16-bit when they + // are in fact 15-bit ... get around this + _places_r = 3; + _places_g = 3; + } + } + if (coldepth > 16) + { + // when we're using 32-bit colour, it converts hi-color images + // the wrong way round - so fix that + +#if AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_ANDROID + _rgb_b_shift_16 = 0; + _rgb_g_shift_16 = 5; + _rgb_r_shift_16 = 11; + + _rgb_b_shift_15 = 0; + _rgb_g_shift_15 = 5; + _rgb_r_shift_15 = 10; + + _rgb_r_shift_32 = 0; + _rgb_g_shift_32 = 8; + _rgb_b_shift_32 = 16; +#else + _rgb_r_shift_16 = 11; + _rgb_g_shift_16 = 5; + _rgb_b_shift_16 = 0; +#endif + } + else if (coldepth == 16) + { + // ensure that any 32-bit graphics displayed are converted + // properly to the current depth + _rgb_r_shift_32 = 16; + _rgb_g_shift_32 = 8; + _rgb_b_shift_32 = 0; + } + else if (coldepth < 16) + { + // ensure that any 32-bit graphics displayed are converted + // properly to the current depth +#if AGS_PLATFORM_OS_WINDOWS + _rgb_r_shift_32 = 16; + _rgb_g_shift_32 = 8; + _rgb_b_shift_32 = 0; +#else + _rgb_r_shift_32 = 0; + _rgb_g_shift_32 = 8; + _rgb_b_shift_32 = 16; + + _rgb_b_shift_15 = 0; + _rgb_g_shift_15 = 5; + _rgb_r_shift_15 = 10; +#endif + } + + set_color_conversion(COLORCONV_MOST | COLORCONV_EXPAND_256); +} + +// Setup drawing modes and color conversions; +// they depend primarily on gfx driver capabilities and new color depth +void engine_post_gfxmode_draw_setup(const DisplayMode &dm) +{ + engine_setup_color_conversions(dm.ColorDepth); + init_draw_method(); +} + +// Cleanup auxiliary drawing objects +void engine_pre_gfxmode_draw_cleanup() +{ + dispose_draw_method(); +} + +// Setup mouse control mode and graphic area +void engine_post_gfxmode_mouse_setup(const DisplayMode &dm, const Size &init_desktop) +{ + // Assign mouse control parameters. + // + // NOTE that we setup speed and other related properties regardless of + // whether mouse control was requested because it may be enabled later. + Mouse::SetSpeedUnit(1.f); + if (usetup.mouse_speed_def == kMouseSpeed_CurrentDisplay) + { + Size cur_desktop; + if (get_desktop_resolution(&cur_desktop.Width, &cur_desktop.Height) == 0) + Mouse::SetSpeedUnit(Math::Max((float)cur_desktop.Width / (float)init_desktop.Width, + (float)cur_desktop.Height / (float)init_desktop.Height)); + } + + Mouse_EnableControl(usetup.mouse_ctrl_enabled); + Debug::Printf(kDbgMsg_Info, "Mouse control: %s, base: %f, speed: %f", Mouse::IsControlEnabled() ? "on" : "off", + Mouse::GetSpeedUnit(), Mouse::GetSpeed()); + + on_coordinates_scaling_changed(); + + // If auto lock option is set, lock mouse to the game window + if (usetup.mouse_auto_lock && scsystem.windowed != 0) + Mouse::TryLockToWindow(); +} + +// Reset mouse controls before changing gfx mode +void engine_pre_gfxmode_mouse_cleanup() +{ + // Always disable mouse control and unlock mouse when releasing down gfx mode + Mouse::DisableControl(); + Mouse::UnlockFromWindow(); +} + +// Fill in scsystem struct with display mode parameters +void engine_setup_scsystem_screen(const DisplayMode &dm) +{ + scsystem.coldepth = dm.ColorDepth; + scsystem.windowed = dm.Windowed; + scsystem.vsync = dm.Vsync; +} + +void engine_post_gfxmode_setup(const Size &init_desktop) +{ + DisplayMode dm = gfxDriver->GetDisplayMode(); + // If color depth has changed (or graphics mode was inited for the + // very first time), we also need to recreate bitmaps + bool has_driver_changed = scsystem.coldepth != dm.ColorDepth; + + engine_setup_scsystem_screen(dm); + engine_post_gfxmode_driver_setup(); + engine_post_gfxmode_screen_setup(dm, has_driver_changed); + if (has_driver_changed) + engine_post_gfxmode_draw_setup(dm); + engine_post_gfxmode_mouse_setup(dm, init_desktop); + + // TODO: the only reason this call was put here is that it requires + // "windowed" flag to be specified. Find out whether this function + // has anything to do with graphics mode at all. It is quite possible + // that we may split it into two functions, or remove parameter. + platform->PostAllegroInit(scsystem.windowed != 0); + + video_on_gfxmode_changed(); + invalidate_screen(); +} + +void engine_pre_gfxmode_release() +{ + engine_pre_gfxmode_mouse_cleanup(); + engine_pre_gfxmode_driver_cleanup(); + engine_pre_gfxmode_screen_cleanup(); +} + +void engine_pre_gfxsystem_shutdown() +{ + engine_pre_gfxmode_release(); + engine_pre_gfxmode_draw_cleanup(); + engine_pre_gfxsystem_screen_destroy(); +} + +void on_coordinates_scaling_changed() +{ + // Reset mouse graphic area and bounds + Mouse::SetGraphicArea(); + // If mouse bounds do not have valid values yet, then limit cursor to viewport + if (play.mboundx1 == 0 && play.mboundy1 == 0 && play.mboundx2 == 0 && play.mboundy2 == 0) + Mouse::SetMoveLimit(play.GetMainViewport()); + else + Mouse::SetMoveLimit(Rect(play.mboundx1, play.mboundy1, play.mboundx2, play.mboundy2)); +} diff --git a/engines/ags/engine/main/engine_setup.h b/engines/ags/engine/main/engine_setup.h new file mode 100644 index 00000000000..ad5e6fa4078 --- /dev/null +++ b/engines/ags/engine/main/engine_setup.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __AGS_EE_MAIN__ENGINESETUP_H +#define __AGS_EE_MAIN__ENGINESETUP_H + +#include "util/geometry.h" +#include "gfx/gfxdefines.h" + +// Sets up game viewport and object scaling parameters depending on game. +// TODO: this is part of the game init, not engine init, move it later +void engine_init_resolution_settings(const Size game_size); +// Setup engine after the graphics mode has changed +void engine_post_gfxmode_setup(const Size &init_desktop); +// Prepare engine for graphics mode release; could be called before switching display mode too +void engine_pre_gfxmode_release(); +// Prepare engine to the graphics mode shutdown and gfx driver destruction +void engine_pre_gfxsystem_shutdown(); +// Applies necessary changes after screen<->virtual coordinate transformation has changed +void on_coordinates_scaling_changed(); + +#endif // __AGS_EE_MAIN__ENGINESETUP_H diff --git a/engines/ags/engine/main/game_file.cpp b/engines/ags/engine/main/game_file.cpp new file mode 100644 index 00000000000..82973f51026 --- /dev/null +++ b/engines/ags/engine/main/game_file.cpp @@ -0,0 +1,161 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +// +// Game data file management +// + +#include "main/mainheader.h" +#include "main/game_file.h" +#include "ac/common.h" +#include "ac/character.h" +#include "ac/charactercache.h" +#include "ac/dialogtopic.h" +#include "ac/draw.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/gamestructdefines.h" +#include "ac/gui.h" +#include "ac/viewframe.h" +#include "debug/debug_log.h" +#include "debug/out.h" +#include "gui/guilabel.h" +#include "main/main.h" +#include "platform/base/agsplatformdriver.h" +#include "util/stream.h" +#include "gfx/bitmap.h" +#include "gfx/blender.h" +#include "core/assetmanager.h" +#include "util/alignedstream.h" +#include "ac/gamesetup.h" +#include "game/main_game_file.h" +#include "game/game_init.h" +#include "plugin/agsplugin.h" +#include "script/script.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern int ifacepopped; + +extern GameSetupStruct game; +extern ViewStruct*views; +extern DialogTopic *dialog; + +extern AGSPlatformDriver *platform; +extern int numScriptModules; + + +// Test if engine supports extended capabilities required to run the game +bool test_game_caps(const std::set &caps, std::set &failed_caps) +{ + // Currently we support nothing special + failed_caps = caps; + return caps.size() == 0; +} + +// Forms a simple list of capability names +String get_caps_list(const std::set &caps) +{ + String caps_list; + for (std::set::const_iterator it = caps.begin(); it != caps.end(); ++it) + { + caps_list.Append("\n\t"); + caps_list.Append(*it); + } + return caps_list; +} + +// Called when the game file is opened for the first time (when preloading game data); +// it logs information on data version and reports first found errors, if any. +HGameFileError game_file_first_open(MainGameSource &src) +{ + HGameFileError err = OpenMainGameFileFromDefaultAsset(src); + if (err || + err->Code() == kMGFErr_SignatureFailed || + err->Code() == kMGFErr_FormatVersionTooOld || + err->Code() == kMGFErr_FormatVersionNotSupported) + { + // Log data description for debugging + Debug::Printf(kDbgMsg_Info, "Opened game data file: %s", src.Filename.GetCStr()); + Debug::Printf(kDbgMsg_Info, "Game data version: %d", src.DataVersion); + Debug::Printf(kDbgMsg_Info, "Compiled with: %s", src.CompiledWith.GetCStr()); + if (src.Caps.size() > 0) + { + String caps_list = get_caps_list(src.Caps); + Debug::Printf(kDbgMsg_Info, "Requested engine caps: %s", caps_list.GetCStr()); + } + } + // Quit in case of error + if (!err) + return err; + + // Test the extended caps + std::set failed_caps; + if (!test_game_caps(src.Caps, failed_caps)) + { + String caps_list = get_caps_list(failed_caps); + return new MainGameFileError(kMGFErr_CapsNotSupported, String::FromFormat("Missing engine caps: %s", caps_list.GetCStr())); + } + return HGameFileError::None(); +} + +void PreReadSaveFileInfo(Stream *in, GameDataVersion data_ver) +{ + AlignedStream align_s(in, Common::kAligned_Read); + game.ReadFromFile(&align_s); + // Discard game messages we do not need here + delete [] game.load_messages; + game.load_messages = nullptr; + game.read_savegame_info(in, data_ver); +} + +HError preload_game_data() +{ + MainGameSource src; + HGameFileError err = game_file_first_open(src); + if (!err) + return (HError)err; + // Read only the particular data we need for preliminary game analysis + PreReadSaveFileInfo(src.InputStream.get(), src.DataVersion); + game.compiled_with = src.CompiledWith; + FixupSaveDirectory(game); + return HError::None(); +} + +HError load_game_file() +{ + MainGameSource src; + LoadedGameEntities ents(game, dialog, views); + HGameFileError load_err = OpenMainGameFileFromDefaultAsset(src); + if (load_err) + { + load_err = ReadGameData(ents, src.InputStream.get(), src.DataVersion); + if (load_err) + load_err = UpdateGameData(ents, src.DataVersion); + } + if (!load_err) + return (HError)load_err; + HGameInitError init_err = InitGameState(ents, src.DataVersion); + if (!init_err) + return (HError)init_err; + return HError::None(); +} + +void display_game_file_error(HError err) +{ + platform->DisplayAlert("Loading game failed with error:\n%s.\n\nThe game files may be incomplete, corrupt or from unsupported version of AGS.", + err->FullMessage().GetCStr()); +} diff --git a/engines/ags/engine/main/game_file.h b/engines/ags/engine/main/game_file.h new file mode 100644 index 00000000000..d0a50a7c6c9 --- /dev/null +++ b/engines/ags/engine/main/game_file.h @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__GAMEFILE_H +#define __AGS_EE_MAIN__GAMEFILE_H + +#include "util/error.h" +#include "util/string.h" + +using AGS::Common::HError; + +// Preload particular game-describing parameters from the game data header (title, save game dir name, etc) +HError preload_game_data(); +// Loads game data and reinitializes the game state; assigns error message in case of failure +HError load_game_file(); +void display_game_file_error(HError err); + +#endif // __AGS_EE_MAIN__GAMEFILE_H diff --git a/engines/ags/engine/main/game_run.cpp b/engines/ags/engine/main/game_run.cpp new file mode 100644 index 00000000000..173d2db1803 --- /dev/null +++ b/engines/ags/engine/main/game_run.cpp @@ -0,0 +1,1050 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +// +// Game loop +// + +#include +#include +#include "ac/common.h" +#include "ac/characterextras.h" +#include "ac/characterinfo.h" +#include "ac/draw.h" +#include "ac/event.h" +#include "ac/game.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_debug.h" +#include "ac/global_display.h" +#include "ac/global_game.h" +#include "ac/global_gui.h" +#include "ac/global_region.h" +#include "ac/gui.h" +#include "ac/hotspot.h" +#include "ac/keycode.h" +#include "ac/mouse.h" +#include "ac/overlay.h" +#include "ac/sys_events.h" +#include "ac/room.h" +#include "ac/roomobject.h" +#include "ac/roomstatus.h" +#include "debug/debugger.h" +#include "debug/debug_log.h" +#include "gui/guiinv.h" +#include "gui/guimain.h" +#include "gui/guitextbox.h" +#include "main/mainheader.h" +#include "main/engine.h" +#include "main/game_run.h" +#include "main/update.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "script/script.h" +#include "ac/spritecache.h" +#include "media/audio/audio_system.h" +#include "platform/base/agsplatformdriver.h" +#include "ac/timer.h" +#include "ac/keycode.h" + +using namespace AGS::Common; + +extern AnimatingGUIButton animbuts[MAX_ANIMATING_BUTTONS]; +extern int numAnimButs; +extern int mouse_on_iface; // mouse cursor is over this interface +extern int ifacepopped; +extern int is_text_overlay; +extern volatile char want_exit, abort_engine; +extern int proper_exit,our_eip; +extern int displayed_room, starting_room, in_new_room, new_room_was; +extern GameSetupStruct game; +extern RoomStruct thisroom; +extern int game_paused; +extern int getloctype_index; +extern int in_enters_screen,done_es_error; +extern int in_leaves_screen; +extern int inside_script,in_graph_script; +extern int no_blocking_functions; +extern CharacterInfo*playerchar; +extern GameState play; +extern int mouse_ifacebut_xoffs,mouse_ifacebut_yoffs; +extern int cur_mode; +extern RoomObject*objs; +extern char noWalkBehindsAtAll; +extern RoomStatus*croom; +extern CharacterExtras *charextra; +extern SpriteCache spriteset; +extern int cur_mode,cur_cursor; + +// Checks if user interface should remain disabled for now +static int ShouldStayInWaitMode(); + +static int numEventsAtStartOfFunction; +static auto t1 = AGS_Clock::now(); // timer for FPS // ... 't1'... how very appropriate.. :) + +#define UNTIL_ANIMEND 1 +#define UNTIL_MOVEEND 2 +#define UNTIL_CHARIS0 3 +#define UNTIL_NOOVERLAY 4 +#define UNTIL_NEGATIVE 5 +#define UNTIL_INTIS0 6 +#define UNTIL_SHORTIS0 7 +#define UNTIL_INTISNEG 8 + +// Following 3 parameters instruct the engine to run game loops until +// certain condition is not fullfilled. +static int restrict_until=0; +static int user_disabled_for = 0; +static const void *user_disabled_data = nullptr; + +unsigned int loopcounter=0; +static unsigned int lastcounter=0; + +static void ProperExit() +{ + want_exit = 0; + proper_exit = 1; + quit("||exit!"); +} + +static void game_loop_check_problems_at_start() +{ + if ((in_enters_screen != 0) & (displayed_room == starting_room)) + quit("!A text script run in the Player Enters Screen event caused the\n" + "screen to be updated. If you need to use Wait(), do so in After Fadein"); + if ((in_enters_screen != 0) && (done_es_error == 0)) { + debug_script_warn("Wait() was used in Player Enters Screen - use Enters Screen After Fadein instead"); + done_es_error = 1; + } + if (no_blocking_functions) + quit("!A blocking function was called from within a non-blocking event such as " REP_EXEC_ALWAYS_NAME); +} + +static void game_loop_check_new_room() +{ + if (in_new_room == 0) { + // Run the room and game script repeatedly_execute + run_function_on_non_blocking_thread(&repExecAlways); + setevent(EV_TEXTSCRIPT,TS_REPEAT); + setevent(EV_RUNEVBLOCK,EVB_ROOM,0,6); + } + // run this immediately to make sure it gets done before fade-in + // (player enters screen) + check_new_room (); +} + +static void game_loop_do_late_update() +{ + if (in_new_room == 0) + { + // Run the room and game script late_repeatedly_execute + run_function_on_non_blocking_thread(&lateRepExecAlways); + } +} + +static int game_loop_check_ground_level_interactions() +{ + if ((play.ground_level_areas_disabled & GLED_INTERACTION) == 0) { + // check if he's standing on a hotspot + int hotspotThere = get_hotspot_at(playerchar->x, playerchar->y); + // run Stands on Hotspot event + setevent(EV_RUNEVBLOCK, EVB_HOTSPOT, hotspotThere, 0); + + // check current region + int onRegion = GetRegionIDAtRoom(playerchar->x, playerchar->y); + int inRoom = displayed_room; + + if (onRegion != play.player_on_region) { + // we need to save this and set play.player_on_region + // now, so it's correct going into RunRegionInteraction + int oldRegion = play.player_on_region; + + play.player_on_region = onRegion; + // Walks Off last region + if (oldRegion > 0) + RunRegionInteraction (oldRegion, 2); + // Walks Onto new region + if (onRegion > 0) + RunRegionInteraction (onRegion, 1); + } + if (play.player_on_region > 0) // player stands on region + RunRegionInteraction (play.player_on_region, 0); + + // one of the region interactions sent us to another room + if (inRoom != displayed_room) { + check_new_room(); + } + + // if in a Wait loop which is no longer valid (probably + // because the Region interaction did a NewRoom), abort + // the rest of the loop + if ((restrict_until) && (!ShouldStayInWaitMode())) { + // cancel the Rep Exec and Stands on Hotspot events that + // we just added -- otherwise the event queue gets huge + numevents = numEventsAtStartOfFunction; + return 0; + } + } // end if checking ground level interactions + + return RETURN_CONTINUE; +} + +static void lock_mouse_on_click() +{ + if (usetup.mouse_auto_lock && scsystem.windowed) + Mouse::TryLockToWindow(); +} + +static void toggle_mouse_lock() +{ + if (scsystem.windowed) + { + if (Mouse::IsLockedToWindow()) + Mouse::UnlockFromWindow(); + else + Mouse::TryLockToWindow(); + } +} + +// Runs default mouse button handling +static void check_mouse_controls() +{ + int mongu=-1; + + mongu = gui_on_mouse_move(); + + mouse_on_iface=mongu; + if ((ifacepopped>=0) && (mousey>=guis[ifacepopped].Y+guis[ifacepopped].Height)) + remove_popup_interface(ifacepopped); + + // check mouse clicks on GUIs + static int wasbutdown=0,wasongui=0; + + if ((wasbutdown>0) && (ags_misbuttondown(wasbutdown-1))) { + gui_on_mouse_hold(wasongui, wasbutdown); + } + else if ((wasbutdown>0) && (!ags_misbuttondown(wasbutdown-1))) { + gui_on_mouse_up(wasongui, wasbutdown); + wasbutdown=0; + } + + int mbut = NONE; + int mwheelz = 0; + if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0) { + + check_skip_cutscene_mclick(mbut); + + if (play.fast_forward || play.IsIgnoringInput()) { /* do nothing if skipping cutscene or input disabled */ } + else if ((play.wait_counter != 0) && (play.key_skip_wait & SKIP_MOUSECLICK) != 0) { + play.wait_counter = 0; + play.wait_skipped_by = SKIP_MOUSECLICK; + play.wait_skipped_by_data = mbut; + } + else if (is_text_overlay > 0) { + if (play.cant_skip_speech & SKIP_MOUSECLICK) + remove_screen_overlay(OVER_TEXTMSG); + } + else if (!IsInterfaceEnabled()) ; // blocking cutscene, ignore mouse + else if (pl_run_plugin_hooks(AGSE_MOUSECLICK, mbut+1)) { + // plugin took the click + debug_script_log("Plugin handled mouse button %d", mbut+1); + } + else if (mongu>=0) { + if (wasbutdown==0) { + gui_on_mouse_down(mongu, mbut+1); + } + wasongui=mongu; + wasbutdown=mbut+1; + } + else setevent(EV_TEXTSCRIPT,TS_MCLICK,mbut+1); + // else RunTextScriptIParam(gameinst,"on_mouse_click",aa+1); + } + + if (mwheelz < 0) + setevent (EV_TEXTSCRIPT, TS_MCLICK, 9); + else if (mwheelz > 0) + setevent (EV_TEXTSCRIPT, TS_MCLICK, 8); +} + +// Returns current key modifiers; +// NOTE: annoyingly enough, on Windows (not sure about other platforms) +// Allegro API's 'key_shifts' variable seem to be always one step behind real +// situation: if first modifier gets pressed, 'key_shifts' will be zero, +// when second modifier gets pressed it will only contain first one, and so on. +static int get_active_shifts() +{ + int shifts = 0; + if (key[KEY_LSHIFT] || key[KEY_RSHIFT]) + shifts |= KB_SHIFT_FLAG; + if (key[KEY_LCONTROL] || key[KEY_RCONTROL]) + shifts |= KB_CTRL_FLAG; + if (key[KEY_ALT] || key[KEY_ALTGR]) + shifts |= KB_ALT_FLAG; + return shifts; +} + +// Special flags to OR saved shift flags with: +// Shifts key combination already fired (wait until full shifts release) +#define KEY_SHIFTS_FIRED 0x80000000 + +// Runs service key controls, returns false if service key combinations were handled +// and no more processing required, otherwise returns true and provides current keycode and key shifts. +bool run_service_key_controls(int &kgn) +{ + // check keypresses + static int old_key_shifts = 0; // for saving shift modes + + bool handled = false; + int kbhit_res = ags_kbhit(); + // First, check shifts + const int act_shifts = get_active_shifts(); + // If shifts combination have already triggered an action, then do nothing + // until new shifts are empty, in which case reset saved shifts + if (old_key_shifts & KEY_SHIFTS_FIRED) + { + if (act_shifts == 0) + old_key_shifts = 0; + } + else + { + // If any non-shift key is pressed, add fired flag to indicate that + // this is no longer a pure shifts key combination + if (kbhit_res) + { + old_key_shifts = act_shifts | KEY_SHIFTS_FIRED; + } + // If all the previously registered shifts are still pressed, + // then simply resave new shift state. + else if ((old_key_shifts & act_shifts) == old_key_shifts) + { + old_key_shifts = act_shifts; + } + // Otherwise some of the shifts were released, then run key combo action + // and set KEY_COMBO_FIRED flag to prevent multiple execution + else if (old_key_shifts) + { + // Toggle mouse lock on Ctrl + Alt + if (old_key_shifts == (KB_ALT_FLAG | KB_CTRL_FLAG)) + { + toggle_mouse_lock(); + handled = true; + } + old_key_shifts |= KEY_SHIFTS_FIRED; + } + } + + if (!kbhit_res || handled) + return false; + + int keycode = ags_getch(); + // NS: I'm still not sure why we read a second key. + // Perhaps it's of a time when we read the keyboard one byte at a time? + // if (keycode == 0) + // keycode = ags_getch() + AGS_EXT_KEY_SHIFT; + if (keycode == 0) + return false; + + // LAlt or RAlt + Enter + // NOTE: for some reason LAlt + Enter produces same code as F9 + if (act_shifts == KB_ALT_FLAG && ((keycode == eAGSKeyCodeF9 && !key[KEY_F9]) || keycode == eAGSKeyCodeReturn)) + { + engine_try_switch_windowed_gfxmode(); + return false; + } + + // No service operation triggered? return active keypress and shifts to caller + kgn = keycode; + return true; +} + +bool run_service_mb_controls(int &mbut, int &mwheelz) +{ + int mb = ags_mgetbutton(); + int mz = ags_check_mouse_wheel(); + if (mb == NONE && mz == 0) + return false; + lock_mouse_on_click(); // do not claim + mbut = mb; + mwheelz = mz; + return true; +} + +// Runs default keyboard handling +static void check_keyboard_controls() +{ + // First check for service engine's combinations (mouse lock, display mode switch, and so forth) + int kgn; + if (!run_service_key_controls(kgn)) { + return; + } + // Then, check cutscene skip + check_skip_cutscene_keypress(kgn); + if (play.fast_forward) { + return; + } + if (play.IsIgnoringInput()) { + return; + } + // Now check for in-game controls + if (pl_run_plugin_hooks(AGSE_KEYPRESS, kgn)) { + // plugin took the keypress + debug_script_log("Keypress code %d taken by plugin", kgn); + return; + } + + // debug console + if ((kgn == '`') && (play.debug_mode > 0)) { + display_console = !display_console; + return; + } + + // skip speech if desired by Speech.SkipStyle + if ((is_text_overlay > 0) && (play.cant_skip_speech & SKIP_KEYPRESS)) { + // only allow a key to remove the overlay if the icon bar isn't up + if (IsGamePaused() == 0) { + // check if it requires a specific keypress + if ((play.skip_speech_specific_key > 0) && + (kgn != play.skip_speech_specific_key)) { } + else + remove_screen_overlay(OVER_TEXTMSG); + } + + return; + } + + if ((play.wait_counter != 0) && (play.key_skip_wait & SKIP_KEYPRESS) != 0) { + play.wait_counter = 0; + play.wait_skipped_by = SKIP_KEYPRESS; + play.wait_skipped_by_data = kgn; + debug_script_log("Keypress code %d ignored - in Wait", kgn); + return; + } + + if ((kgn == eAGSKeyCodeCtrlE) && (display_fps == kFPS_Forced)) { + // if --fps paramter is used, Ctrl+E will max out frame rate + setTimerFps( isTimerFpsMaxed() ? frames_per_second : 1000 ); + return; + } + + if ((kgn == eAGSKeyCodeCtrlD) && (play.debug_mode > 0)) { + // ctrl+D - show info + char infobuf[900]; + int ff; + // MACPORT FIX 9/6/5: added last %s + sprintf(infobuf,"In room %d %s[Player at %d, %d (view %d, loop %d, frame %d)%s%s%s", + displayed_room, (noWalkBehindsAtAll ? "(has no walk-behinds)" : ""), playerchar->x,playerchar->y, + playerchar->view + 1, playerchar->loop,playerchar->frame, + (IsGamePaused() == 0) ? "" : "[Game paused.", + (play.ground_level_areas_disabled == 0) ? "" : "[Ground areas disabled.", + (IsInterfaceEnabled() == 0) ? "[Game in Wait state" : ""); + for (ff=0;ffnumobj;ff++) { + if (ff >= 8) break; // buffer not big enough for more than 7 + sprintf(&infobuf[strlen(infobuf)], + "[Object %d: (%d,%d) size (%d x %d) on:%d moving:%s animating:%d slot:%d trnsp:%d clkble:%d", + ff, objs[ff].x, objs[ff].y, + (spriteset[objs[ff].num] != nullptr) ? game.SpriteInfos[objs[ff].num].Width : 0, + (spriteset[objs[ff].num] != nullptr) ? game.SpriteInfos[objs[ff].num].Height : 0, + objs[ff].on, + (objs[ff].moving > 0) ? "yes" : "no", objs[ff].cycling, + objs[ff].num, objs[ff].transparent, + ((objs[ff].flags & OBJF_NOINTERACT) != 0) ? 0 : 1 ); + } + Display(infobuf); + int chd = game.playercharacter; + char bigbuffer[STD_BUFFER_SIZE] = "CHARACTERS IN THIS ROOM:["; + for (ff = 0; ff < game.numcharacters; ff++) { + if (game.chars[ff].room != displayed_room) continue; + if (strlen(bigbuffer) > 430) { + strcat(bigbuffer, "and more..."); + Display(bigbuffer); + strcpy(bigbuffer, "CHARACTERS IN THIS ROOM (cont'd):["); + } + chd = ff; + sprintf(&bigbuffer[strlen(bigbuffer)], + "%s (view/loop/frm:%d,%d,%d x/y/z:%d,%d,%d idleview:%d,time:%d,left:%d walk:%d anim:%d follow:%d flags:%X wait:%d zoom:%d)[", + game.chars[chd].scrname, game.chars[chd].view+1, game.chars[chd].loop, game.chars[chd].frame, + game.chars[chd].x, game.chars[chd].y, game.chars[chd].z, + game.chars[chd].idleview, game.chars[chd].idletime, game.chars[chd].idleleft, + game.chars[chd].walking, game.chars[chd].animating, game.chars[chd].following, + game.chars[chd].flags, game.chars[chd].wait, charextra[chd].zoom); + } + Display(bigbuffer); + + return; + } + + // if (kgn == key_ctrl_u) { + // play.debug_mode++; + // script_debug(5,0); + // play.debug_mode--; + // return; + // } + + if ((kgn == eAGSKeyCodeAltV) && (key[KEY_LCONTROL] || key[KEY_RCONTROL]) && (play.wait_counter < 1) && (is_text_overlay == 0) && (restrict_until == 0)) { + // make sure we can't interrupt a Wait() + // and desync the music to cutscene + play.debug_mode++; + script_debug (1,0); + play.debug_mode--; + + return; + } + + if (inside_script) { + // Don't queue up another keypress if it can't be run instantly + debug_script_log("Keypress %d ignored (game blocked)", kgn); + return; + } + + int keywasprocessed = 0; + + // determine if a GUI Text Box should steal the click + // it should do if a displayable character (32-255) is + // pressed, but exclude control characters (<32) and + // extended keys (eg. up/down arrow; 256+) + if ( (((kgn >= 32) && (kgn <= 255) && (kgn != '[')) || (kgn == eAGSKeyCodeReturn) || (kgn == eAGSKeyCodeBackspace)) + && !all_buttons_disabled) { + for (int guiIndex = 0; guiIndex < game.numgui; guiIndex++) { + auto &gui = guis[guiIndex]; + + if (!gui.IsDisplayed()) continue; + + for (int controlIndex = 0; controlIndex < gui.GetControlCount(); controlIndex++) { + // not a text box, ignore it + if (gui.GetControlType(controlIndex) != kGUITextBox) { continue; } + + auto *guitex = static_cast(gui.GetControl(controlIndex)); + if (guitex == nullptr) { continue; } + + // if the text box is disabled, it cannot accept keypresses + if (!guitex->IsEnabled()) { continue; } + if (!guitex->IsVisible()) { continue; } + + keywasprocessed = 1; + + guitex->OnKeyPress(kgn); + + if (guitex->IsActivated) { + guitex->IsActivated = false; + setevent(EV_IFACECLICK, guiIndex, controlIndex, 1); + } + } + } + } + + if (!keywasprocessed) { + kgn = GetKeyForKeyPressCb(kgn); + debug_script_log("Running on_key_press keycode %d", kgn); + setevent(EV_TEXTSCRIPT,TS_KEYPRESS,kgn); + } + + // RunTextScriptIParam(gameinst,"on_key_press",kgn); +} + +// check_controls: checks mouse & keyboard interface +static void check_controls() { + our_eip = 1007; + + check_mouse_controls(); + check_keyboard_controls(); +} + +static void check_room_edges(int numevents_was) +{ + if ((IsInterfaceEnabled()) && (IsGamePaused() == 0) && + (in_new_room == 0) && (new_room_was == 0)) { + // Only allow walking off edges if not in wait mode, and + // if not in Player Enters Screen (allow walking in from off-screen) + int edgesActivated[4] = {0, 0, 0, 0}; + // Only do it if nothing else has happened (eg. mouseclick) + if ((numevents == numevents_was) && + ((play.ground_level_areas_disabled & GLED_INTERACTION) == 0)) { + + if (playerchar->x <= thisroom.Edges.Left) + edgesActivated[0] = 1; + else if (playerchar->x >= thisroom.Edges.Right) + edgesActivated[1] = 1; + if (playerchar->y >= thisroom.Edges.Bottom) + edgesActivated[2] = 1; + else if (playerchar->y <= thisroom.Edges.Top) + edgesActivated[3] = 1; + + if ((play.entered_edge >= 0) && (play.entered_edge <= 3)) { + // once the player is no longer outside the edge, forget the stored edge + if (edgesActivated[play.entered_edge] == 0) + play.entered_edge = -10; + // if we are walking in from off-screen, don't activate edges + else + edgesActivated[play.entered_edge] = 0; + } + + for (int ii = 0; ii < 4; ii++) { + if (edgesActivated[ii]) + setevent(EV_RUNEVBLOCK, EVB_ROOM, 0, ii); + } + } + } + our_eip = 1008; + +} + +static void game_loop_check_controls(bool checkControls) +{ + // don't let the player do anything before the screen fades in + if ((in_new_room == 0) && (checkControls)) { + int inRoom = displayed_room; + int numevents_was = numevents; + check_controls(); + check_room_edges(numevents_was); + // If an inventory interaction changed the room + if (inRoom != displayed_room) + check_new_room(); + } +} + +static void game_loop_do_update() +{ + if (debug_flags & DBG_NOUPDATE) ; + else if (game_paused==0) update_stuff(); +} + +static void game_loop_update_animated_buttons() +{ + // update animating GUI buttons + // this bit isn't in update_stuff because it always needs to + // happen, even when the game is paused + for (int aa = 0; aa < numAnimButs; aa++) { + if (UpdateAnimatingButton(aa)) { + StopButtonAnimation(aa); + aa--; + } + } +} + +static void game_loop_do_render_and_check_mouse(IDriverDependantBitmap *extraBitmap, int extraX, int extraY) +{ + if (!play.fast_forward) { + int mwasatx=mousex,mwasaty=mousey; + + // Only do this if we are not skipping a cutscene + render_graphics(extraBitmap, extraX, extraY); + + // Check Mouse Moves Over Hotspot event + // TODO: move this out of render related function? find out why we remember mwasatx and mwasaty before render + // TODO: do not use static variables! + // TODO: if we support rotation then we also need to compare full transform! + if (displayed_room < 0) + return; + auto view = play.GetRoomViewportAt(mousex, mousey); + auto cam = view ? view->GetCamera() : nullptr; + if (cam) + { + // NOTE: all cameras are in same room right now, so their positions are in same coordinate system; + // therefore we may use this as an indication that mouse is over different camera too. + static int offsetxWas = -1000, offsetyWas = -1000; + int offsetx = cam->GetRect().Left; + int offsety = cam->GetRect().Top; + + if (((mwasatx!=mousex) || (mwasaty!=mousey) || + (offsetxWas != offsetx) || (offsetyWas != offsety))) + { + // mouse moves over hotspot + if (__GetLocationType(game_to_data_coord(mousex), game_to_data_coord(mousey), 1) == LOCTYPE_HOTSPOT) { + int onhs = getloctype_index; + + setevent(EV_RUNEVBLOCK,EVB_HOTSPOT,onhs,6); + } + } + + offsetxWas = offsetx; + offsetyWas = offsety; + } // camera found under mouse + } +} + +static void game_loop_update_events() +{ + new_room_was = in_new_room; + if (in_new_room>0) + setevent(EV_FADEIN,0,0,0); + in_new_room=0; + update_events(); + if ((new_room_was > 0) && (in_new_room == 0)) { + // if in a new room, and the room wasn't just changed again in update_events, + // then queue the Enters Screen scripts + // run these next time round, when it's faded in + if (new_room_was==2) // first time enters screen + setevent(EV_RUNEVBLOCK,EVB_ROOM,0,4); + if (new_room_was!=3) // enters screen after fadein + setevent(EV_RUNEVBLOCK,EVB_ROOM,0,7); + } +} + +static void game_loop_update_background_animation() +{ + if (play.bg_anim_delay > 0) play.bg_anim_delay--; + else if (play.bg_frame_locked) ; + else { + play.bg_anim_delay = play.anim_background_speed; + play.bg_frame++; + if ((size_t)play.bg_frame >= thisroom.BgFrameCount) + play.bg_frame=0; + if (thisroom.BgFrameCount >= 2) { + // get the new frame's palette + on_background_frame_change(); + } + } +} + +static void game_loop_update_loop_counter() +{ + loopcounter++; + + if (play.wait_counter > 0) play.wait_counter--; + if (play.shakesc_length > 0) play.shakesc_length--; + + if (loopcounter % 5 == 0) + { + update_ambient_sound_vol(); + update_directional_sound_vol(); + } +} + +static void game_loop_update_fps() +{ + auto t2 = AGS_Clock::now(); + auto duration = std::chrono::duration_cast(t2 - t1); + auto frames = loopcounter - lastcounter; + + if (duration >= std::chrono::milliseconds(1000) && frames > 0) { + fps = 1000.0f * frames / duration.count(); + t1 = t2; + lastcounter = loopcounter; + } +} + +float get_current_fps() { + // if we have maxed out framerate then return the frame rate we're seeing instead + // fps must be greater that 0 or some timings will take forever. + if (isTimerFpsMaxed() && fps > 0.0f) { + return fps; + } + return frames_per_second; +} + +void set_loop_counter(unsigned int new_counter) { + loopcounter = new_counter; + t1 = AGS_Clock::now(); + lastcounter = loopcounter; + fps = std::numeric_limits::quiet_NaN(); +} + +void UpdateGameOnce(bool checkControls, IDriverDependantBitmap *extraBitmap, int extraX, int extraY) { + + int res; + + update_polled_mp3(); + + numEventsAtStartOfFunction = numevents; + + if (want_exit) { + ProperExit(); + } + + ccNotifyScriptStillAlive (); + our_eip=1; + + game_loop_check_problems_at_start(); + + // if we're not fading in, don't count the fadeouts + if ((play.no_hicolor_fadein) && (game.options[OPT_FADETYPE] == FADE_NORMAL)) + play.screen_is_faded_out = 0; + + our_eip = 1014; + + update_gui_disabled_status(); + + our_eip = 1004; + + game_loop_check_new_room(); + + our_eip = 1005; + + res = game_loop_check_ground_level_interactions(); + if (res != RETURN_CONTINUE) { + return; + } + + mouse_on_iface=-1; + + check_debug_keys(); + + game_loop_check_controls(checkControls); + + our_eip=2; + + game_loop_do_update(); + + game_loop_update_animated_buttons(); + + game_loop_do_late_update(); + + update_audio_system_on_game_loop(); + + game_loop_do_render_and_check_mouse(extraBitmap, extraX, extraY); + + our_eip=6; + + game_loop_update_events(); + + our_eip=7; + + // if (ags_mgetbutton()>NONE) break; + update_polled_stuff_if_runtime(); + + game_loop_update_background_animation(); + + game_loop_update_loop_counter(); + + // Immediately start the next frame if we are skipping a cutscene + if (play.fast_forward) + return; + + our_eip=72; + + game_loop_update_fps(); + + update_polled_stuff_if_runtime(); + + WaitForNextFrame(); +} + +static void UpdateMouseOverLocation() +{ + // Call GetLocationName - it will internally force a GUI refresh + // if the result it returns has changed from last time + char tempo[STD_BUFFER_SIZE]; + GetLocationName(game_to_data_coord(mousex), game_to_data_coord(mousey), tempo); + + if ((play.get_loc_name_save_cursor >= 0) && + (play.get_loc_name_save_cursor != play.get_loc_name_last_time) && + (mouse_on_iface < 0) && (ifacepopped < 0)) { + // we have saved the cursor, but the mouse location has changed + // and it's time to restore it + play.get_loc_name_save_cursor = -1; + set_cursor_mode(play.restore_cursor_mode_to); + + if (cur_mode == play.restore_cursor_mode_to) + { + // make sure it changed -- the new mode might have been disabled + // in which case don't change the image + set_mouse_cursor(play.restore_cursor_image_to); + } + debug_script_log("Restore mouse to mode %d cursor %d", play.restore_cursor_mode_to, play.restore_cursor_image_to); + } +} + +// Checks if user interface should remain disabled for now +static int ShouldStayInWaitMode() { + if (restrict_until == 0) + quit("end_wait_loop called but game not in loop_until state"); + int retval = restrict_until; + + if (restrict_until==UNTIL_MOVEEND) { + short*wkptr=(short*)user_disabled_data; + if (wkptr[0]<1) retval=0; + } + else if (restrict_until==UNTIL_CHARIS0) { + char*chptr=(char*)user_disabled_data; + if (chptr[0]==0) retval=0; + } + else if (restrict_until==UNTIL_NEGATIVE) { + short*wkptr=(short*)user_disabled_data; + if (wkptr[0]<0) retval=0; + } + else if (restrict_until==UNTIL_INTISNEG) { + int*wkptr=(int*)user_disabled_data; + if (wkptr[0]<0) retval=0; + } + else if (restrict_until==UNTIL_NOOVERLAY) { + if (is_text_overlay < 1) retval=0; + } + else if (restrict_until==UNTIL_INTIS0) { + int*wkptr=(int*)user_disabled_data; + if (wkptr[0]==0) retval=0; + } + else if (restrict_until==UNTIL_SHORTIS0) { + short*wkptr=(short*)user_disabled_data; + if (wkptr[0]==0) retval=0; + } + else quit("loop_until: unknown until event"); + + return retval; +} + +static int UpdateWaitMode() +{ + if (restrict_until==0) { return RETURN_CONTINUE; } + + restrict_until = ShouldStayInWaitMode(); + our_eip = 77; + + if (restrict_until!=0) { return RETURN_CONTINUE; } + + auto was_disabled_for = user_disabled_for; + + set_default_cursor(); + guis_need_update = 1; + play.disabled_user_interface--; + user_disabled_for = 0; + + switch (was_disabled_for) { + // case FOR_ANIMATION: + // run_animation((FullAnimation*)user_disabled_data2,user_disabled_data3); + // break; + case FOR_EXITLOOP: + return -1; + case FOR_SCRIPT: + quit("err: for_script obsolete (v2.1 and earlier only)"); + break; + default: + quit("Unknown user_disabled_for in end restrict_until"); + } + + // we shouldn't get here. + return RETURN_CONTINUE; +} + +// Run single game iteration; calls UpdateGameOnce() internally +static int GameTick() +{ + if (displayed_room < 0) + quit("!A blocking function was called before the first room has been loaded"); + + UpdateGameOnce(true); + UpdateMouseOverLocation(); + + our_eip=76; + + int res = UpdateWaitMode(); + if (res == RETURN_CONTINUE) { return 0; } // continue looping + return res; +} + +static void SetupLoopParameters(int untilwhat,const void* udata) { + play.disabled_user_interface++; + guis_need_update = 1; + // Only change the mouse cursor if it hasn't been specifically changed first + // (or if it's speech, always change it) + if (((cur_cursor == cur_mode) || (untilwhat == UNTIL_NOOVERLAY)) && + (cur_mode != CURS_WAIT)) + set_mouse_cursor(CURS_WAIT); + + restrict_until=untilwhat; + user_disabled_data=udata; + user_disabled_for=FOR_EXITLOOP; +} + +// This function is called from lot of various functions +// in the game core, character, room object etc +static void GameLoopUntilEvent(int untilwhat,const void* daaa) { + // blocking cutscene - end skipping + EndSkippingUntilCharStops(); + + // this function can get called in a nested context, so + // remember the state of these vars in case a higher level + // call needs them + auto cached_restrict_until = restrict_until; + auto cached_user_disabled_data = user_disabled_data; + auto cached_user_disabled_for = user_disabled_for; + + SetupLoopParameters(untilwhat,daaa); + while (GameTick()==0); + + our_eip = 78; + + restrict_until = cached_restrict_until; + user_disabled_data = cached_user_disabled_data; + user_disabled_for = cached_user_disabled_for; +} + +void GameLoopUntilValueIsZero(const char *value) +{ + GameLoopUntilEvent(UNTIL_CHARIS0, value); +} + +void GameLoopUntilValueIsZero(const short *value) +{ + GameLoopUntilEvent(UNTIL_SHORTIS0, value); +} + +void GameLoopUntilValueIsZero(const int *value) +{ + GameLoopUntilEvent(UNTIL_INTIS0, value); +} + +void GameLoopUntilValueIsZeroOrLess(const short *value) +{ + GameLoopUntilEvent(UNTIL_MOVEEND, value); +} + +void GameLoopUntilValueIsNegative(const short *value) +{ + GameLoopUntilEvent(UNTIL_NEGATIVE, value); +} + +void GameLoopUntilValueIsNegative(const int *value) +{ + GameLoopUntilEvent(UNTIL_INTISNEG, value); +} + +void GameLoopUntilNotMoving(const short *move) +{ + GameLoopUntilEvent(UNTIL_MOVEEND, move); +} + +void GameLoopUntilNoOverlay() +{ + GameLoopUntilEvent(UNTIL_NOOVERLAY, 0); +} + + +extern unsigned int load_new_game; +void RunGameUntilAborted() +{ + // skip ticks to account for time spent starting game. + skipMissedTicks(); + + while (!abort_engine) { + GameTick(); + + if (load_new_game) { + RunAGSGame (nullptr, load_new_game, 0); + load_new_game = 0; + } + } +} + +void update_polled_stuff_if_runtime() +{ + if (want_exit) { + want_exit = 0; + quit("||exit!"); + } + + update_polled_mp3(); + + if (editor_debugging_initialized) + check_for_messages_from_editor(); +} diff --git a/engines/ags/engine/main/game_run.h b/engines/ags/engine/main/game_run.h new file mode 100644 index 00000000000..450cd3ba750 --- /dev/null +++ b/engines/ags/engine/main/game_run.h @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__GAMERUN_H +#define __AGS_EE_MAIN__GAMERUN_H + +namespace AGS { namespace Engine { class IDriverDependantBitmap; }} +using namespace AGS::Engine; // FIXME later + +// Loops game frames until certain event takes place (for blocking actions) +void GameLoopUntilValueIsZero(const char *value); +void GameLoopUntilValueIsZero(const short *value); +void GameLoopUntilValueIsZero(const int *value); +void GameLoopUntilValueIsZeroOrLess(const short *move); +void GameLoopUntilValueIsNegative(const short *value); +void GameLoopUntilValueIsNegative(const int *value); +void GameLoopUntilNotMoving(const short *move); +void GameLoopUntilNoOverlay(); + +// Run the actual game until it ends, or aborted by player/error; loops GameTick() internally +void RunGameUntilAborted(); +// Update everything game related +void UpdateGameOnce(bool checkControls = false, IDriverDependantBitmap *extraBitmap = nullptr, int extraX = 0, int extraY = 0); +// Gets current logical game FPS, this is normally a fixed number set in script; +// in case of "maxed fps" mode this function returns real measured FPS. +float get_current_fps(); +// Runs service key controls, returns false if key input was claimed by the engine, +// otherwise returns true and provides a keycode. +bool run_service_key_controls(int &kgn); +// Runs service mouse controls, returns false if mouse input was claimed by the engine, +// otherwise returns true and provides mouse button code. +bool run_service_mb_controls(int &mbut, int &mwheelz); + +#endif // __AGS_EE_MAIN__GAMERUN_H diff --git a/engines/ags/engine/main/game_start.cpp b/engines/ags/engine/main/game_start.cpp new file mode 100644 index 00000000000..43a46cbfdd8 --- /dev/null +++ b/engines/ags/engine/main/game_start.cpp @@ -0,0 +1,159 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +// +// Game initialization +// + +#include "ac/common.h" +#include "ac/characterinfo.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_game.h" +#include "ac/mouse.h" +#include "ac/room.h" +#include "ac/screen.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "debug/out.h" +#include "gfx/ali3dexception.h" +#include "main/mainheader.h" +#include "main/game_run.h" +#include "main/game_start.h" +#include "script/script.h" +#include "media/audio/audio_system.h" +#include "ac/timer.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern int our_eip, displayed_room; +extern volatile char want_exit, abort_engine; +extern GameSetupStruct game; +extern GameState play; +extern const char *loadSaveGameOnStartup; +extern std::vector moduleInst; +extern int numScriptModules; +extern CharacterInfo*playerchar; +extern int convert_16bit_bgr; + +void start_game_init_editor_debugging() +{ + if (editor_debugging_enabled) + { + SetMultitasking(1); + if (init_editor_debugging()) + { + auto waitUntil = AGS_Clock::now() + std::chrono::milliseconds(500); + while (waitUntil > AGS_Clock::now()) + { + // pick up any breakpoints in game_start + check_for_messages_from_editor(); + } + + ccSetDebugHook(scriptDebugHook); + } + } +} + +void start_game_load_savegame_on_startup() +{ + if (loadSaveGameOnStartup != nullptr) + { + int saveGameNumber = 1000; + const char *sgName = strstr(loadSaveGameOnStartup, "agssave."); + if (sgName != nullptr) + { + sscanf(sgName, "agssave.%03d", &saveGameNumber); + } + current_fade_out_effect(); + try_restore_save(loadSaveGameOnStartup, saveGameNumber); + } +} + +void start_game() { + set_cursor_mode(MODE_WALK); + Mouse::SetPosition(Point(160, 100)); + newmusic(0); + + our_eip = -42; + + // skip ticks to account for initialisation or a restored game. + skipMissedTicks(); + + for (int kk = 0; kk < numScriptModules; kk++) + RunTextScript(moduleInst[kk], "game_start"); + + RunTextScript(gameinst, "game_start"); + + our_eip = -43; + + SetRestartPoint(); + + our_eip=-3; + + if (displayed_room < 0) { + current_fade_out_effect(); + load_new_room(playerchar->room,playerchar); + // load_new_room updates it, but it should be -1 in the first room + playerchar->prevroom = -1; + } + + first_room_initialization(); +} + +void do_start_game() +{ + // only start if replay playback hasn't loaded a game + if (displayed_room < 0) + start_game(); +} + +void initialize_start_and_play_game(int override_start_room, const char *loadSaveGameOnStartup) +{ + try { // BEGIN try for ALI3DEXception + + set_cursor_mode (MODE_WALK); + + if (convert_16bit_bgr) { + // Disable text as speech while displaying the warning message + // This happens if the user's graphics card does BGR order 16-bit colour + int oldalways = game.options[OPT_ALWAYSSPCH]; + game.options[OPT_ALWAYSSPCH] = 0; + // PSP: This is normal. Don't show a warning. + //Display ("WARNING: AGS has detected that you have an incompatible graphics card for this game. You may experience colour problems during the game. Try running the game with \"--15bit\" command line parameter and see if that helps.[[Click the mouse to continue."); + game.options[OPT_ALWAYSSPCH] = oldalways; + } + + srand (play.randseed); + if (override_start_room) + playerchar->room = override_start_room; + + Debug::Printf(kDbgMsg_Info, "Engine initialization complete"); + Debug::Printf(kDbgMsg_Info, "Starting game"); + + start_game_init_editor_debugging(); + + start_game_load_savegame_on_startup(); + + do_start_game(); + + RunGameUntilAborted(); + + } catch (Ali3DException gfxException) + { + quit((char*)gfxException._message); + } +} diff --git a/engines/ags/engine/main/game_start.h b/engines/ags/engine/main/game_start.h new file mode 100644 index 00000000000..b732e2d4c0c --- /dev/null +++ b/engines/ags/engine/main/game_start.h @@ -0,0 +1,24 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__GAMESTART_H +#define __AGS_EE_MAIN__GAMESTART_H + +void start_game(); +void initialize_start_and_play_game(int override_start_room, const char *loadSaveGameOnStartup); + +#endif // __AGS_EE_MAIN__GAMESTART_H diff --git a/engines/ags/engine/main/graphics_mode.cpp b/engines/ags/engine/main/graphics_mode.cpp new file mode 100644 index 00000000000..1c853bce8a7 --- /dev/null +++ b/engines/ags/engine/main/graphics_mode.cpp @@ -0,0 +1,679 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +// +// Graphics initialization +// + +#include +#include "core/platform.h" +#include "ac/draw.h" +#include "debug/debugger.h" +#include "debug/out.h" +#include "gfx/ali3dexception.h" +#include "gfx/bitmap.h" +#include "gfx/gfxdriverfactory.h" +#include "gfx/gfxfilter.h" +#include "gfx/graphicsdriver.h" +#include "main/config.h" +#include "main/engine_setup.h" +#include "main/graphics_mode.h" +#include "main/main_allegro.h" +#include "platform/base/agsplatformdriver.h" + +// Don't try to figure out the window size on the mac because the port resizes itself. +#if AGS_PLATFORM_OS_MACOS || defined(ALLEGRO_SDL2) || AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_ANDROID +#define USE_SIMPLE_GFX_INIT +#endif + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern int proper_exit; +extern AGSPlatformDriver *platform; +extern IGraphicsDriver *gfxDriver; + + +IGfxDriverFactory *GfxFactory = nullptr; + +// Last saved fullscreen and windowed configs; they are used when switching +// between between fullscreen and windowed modes at runtime. +// If particular mode is modified, e.g. by script command, related config should be overwritten. +ActiveDisplaySetting SavedFullscreenSetting; +ActiveDisplaySetting SavedWindowedSetting; +// Current frame scaling setup +GameFrameSetup CurFrameSetup; +// The game-to-screen transformation +PlaneScaling GameScaling; + + +GameFrameSetup::GameFrameSetup() + : ScaleDef(kFrame_IntScale) + , ScaleFactor(1) +{ +} + +GameFrameSetup::GameFrameSetup(FrameScaleDefinition def, int factor) + : ScaleDef(def) + , ScaleFactor(factor) +{ +} + +bool GameFrameSetup::IsValid() const +{ + return ScaleDef != kFrame_IntScale || ScaleFactor > 0; +} + +ScreenSizeSetup::ScreenSizeSetup() + : SizeDef(kScreenDef_MaxDisplay) + , MatchDeviceRatio(true) +{ +} + +DisplayModeSetup::DisplayModeSetup() + : RefreshRate(0) + , VSync(false) + , Windowed(false) +{ +} + + +Size get_desktop_size() +{ + Size sz; + get_desktop_resolution(&sz.Width, &sz.Height); + return sz; +} + +Size get_max_display_size(bool windowed) +{ + Size device_size = get_desktop_size(); + if (windowed) + platform->ValidateWindowSize(device_size.Width, device_size.Height, false); + return device_size; +} + +bool create_gfx_driver(const String &gfx_driver_id) +{ + GfxFactory = GetGfxDriverFactory(gfx_driver_id); + if (!GfxFactory) + { + Debug::Printf(kDbgMsg_Error, "Failed to initialize %s graphics factory. Error: %s", gfx_driver_id.GetCStr(), get_allegro_error()); + return false; + } + Debug::Printf("Using graphics factory: %s", gfx_driver_id.GetCStr()); + gfxDriver = GfxFactory->GetDriver(); + if (!gfxDriver) + { + Debug::Printf(kDbgMsg_Error, "Failed to create graphics driver. Error: %s", get_allegro_error()); + return false; + } + Debug::Printf("Created graphics driver: %s", gfxDriver->GetDriverName()); + return true; +} + +// Set requested graphics filter, or default filter if the requested one failed +bool graphics_mode_set_filter_any(const GfxFilterSetup &setup) +{ + Debug::Printf("Requested gfx filter: %s", setup.UserRequest.GetCStr()); + if (!graphics_mode_set_filter(setup.ID)) + { + String def_filter = GfxFactory->GetDefaultFilterID(); + if (def_filter.CompareNoCase(setup.ID) == 0) + return false; + Debug::Printf(kDbgMsg_Error, "Failed to apply gfx filter: %s; will try to use factory default filter '%s' instead", + setup.UserRequest.GetCStr(), def_filter.GetCStr()); + if (!graphics_mode_set_filter(def_filter)) + return false; + } + Debug::Printf("Using gfx filter: %s", GfxFactory->GetDriver()->GetGraphicsFilter()->GetInfo().Id.GetCStr()); + return true; +} + +bool find_nearest_supported_mode(const IGfxModeList &modes, const Size &wanted_size, const int color_depth, + const Size *ratio_reference, const Size *upper_bound, DisplayMode &dm, int *mode_index) +{ + uint32_t wanted_ratio = 0; + if (ratio_reference && !ratio_reference->IsNull()) + { + wanted_ratio = (ratio_reference->Height << kShift) / ratio_reference->Width; + } + + int nearest_width = 0; + int nearest_height = 0; + int nearest_width_diff = 0; + int nearest_height_diff = 0; + DisplayMode nearest_mode; + int nearest_mode_index = -1; + int mode_count = modes.GetModeCount(); + for (int i = 0; i < mode_count; ++i) + { + DisplayMode mode; + if (!modes.GetMode(i, mode)) + { + continue; + } + if (mode.ColorDepth != color_depth) + { + continue; + } + if (wanted_ratio > 0) + { + uint32_t mode_ratio = (mode.Height << kShift) / mode.Width; + if (mode_ratio != wanted_ratio) + { + continue; + } + } + if (upper_bound && (mode.Width > upper_bound->Width || mode.Height > upper_bound->Height)) + continue; + if (mode.Width == wanted_size.Width && mode.Height == wanted_size.Height) + { + nearest_width = mode.Width; + nearest_height = mode.Height; + nearest_mode_index = i; + nearest_mode = mode; + break; + } + + int diff_w = abs(wanted_size.Width - mode.Width); + int diff_h = abs(wanted_size.Height - mode.Height); + bool same_diff_w_higher = (diff_w == nearest_width_diff && nearest_width < wanted_size.Width); + bool same_diff_h_higher = (diff_h == nearest_height_diff && nearest_height < wanted_size.Height); + + if (nearest_width == 0 || + ((diff_w < nearest_width_diff || same_diff_w_higher) && diff_h <= nearest_height_diff) || + ((diff_h < nearest_height_diff || same_diff_h_higher) && diff_w <= nearest_width_diff)) + { + nearest_width = mode.Width; + nearest_width_diff = diff_w; + nearest_height = mode.Height; + nearest_height_diff = diff_h; + nearest_mode = mode; + nearest_mode_index = i; + } + } + + if (nearest_width > 0 && nearest_height > 0) + { + dm = nearest_mode; + if (mode_index) + *mode_index = nearest_mode_index; + return true; + } + return false; +} + +Size set_game_frame_after_screen_size(const Size &game_size, const Size screen_size, const GameFrameSetup &setup) +{ + // Set game frame as native game resolution scaled by particular method + Size frame_size; + if (setup.ScaleDef == kFrame_MaxStretch) + { + frame_size = screen_size; + } + else if (setup.ScaleDef == kFrame_MaxProportional) + { + frame_size = ProportionalStretch(screen_size, game_size); + } + else + { + int scale; + if (setup.ScaleDef == kFrame_MaxRound) + scale = Math::Min((screen_size.Width / game_size.Width) << kShift, + (screen_size.Height / game_size.Height) << kShift); + else + scale = convert_scaling_to_fp(setup.ScaleFactor); + + // Ensure scaling factors are sane + if (scale <= 0) + scale = kUnit; + + frame_size = Size((game_size.Width * scale) >> kShift, (game_size.Height * scale) >> kShift); + // If the scaled game size appear larger than the screen, + // use "proportional stretch" method instead + if (frame_size.ExceedsByAny(screen_size)) + frame_size = ProportionalStretch(screen_size, game_size); + } + return frame_size; +} + +Size precalc_screen_size(const Size &game_size, const DisplayModeSetup &dm_setup, const GameFrameSetup &frame_setup) +{ + Size screen_size, frame_size; + Size device_size = get_max_display_size(dm_setup.Windowed); + + // Set requested screen (window) size, depending on screen definition option + ScreenSizeSetup scsz = dm_setup.ScreenSize; + switch (scsz.SizeDef) + { + case kScreenDef_Explicit: + // Use resolution from user config + screen_size = scsz.Size; + if (screen_size.IsNull()) + { + // If the configuration did not define proper screen size, + // use the scaled game size instead + frame_size = set_game_frame_after_screen_size(game_size, device_size, frame_setup); + if (screen_size.Width <= 0) + screen_size.Width = frame_size.Width; + if (screen_size.Height <= 0) + screen_size.Height = frame_size.Height; + } + break; + case kScreenDef_ByGameScaling: + // Use game frame (scaled game) size + frame_size = set_game_frame_after_screen_size(game_size, device_size, frame_setup); + screen_size = frame_size; + break; + case kScreenDef_MaxDisplay: + // Set as big as current device size + screen_size = device_size; + break; + } + return screen_size; +} + +// Find closest possible compatible display mode and initialize it +bool try_init_compatible_mode(const DisplayMode &dm, const bool match_device_ratio) +{ + const Size &screen_size = Size(dm.Width, dm.Height); + // Find nearest compatible mode and init that + Debug::Printf("Attempting to find nearest supported resolution for screen size %d x %d (%d-bit) %s", + dm.Width, dm.Height, dm.ColorDepth, dm.Windowed ? "windowed" : "fullscreen"); + const Size device_size = get_max_display_size(dm.Windowed); + if (dm.Windowed) + Debug::Printf("Maximal allowed window size: %d x %d", device_size.Width, device_size.Height); + DisplayMode dm_compat = dm; + + std::unique_ptr modes(gfxDriver->GetSupportedModeList(dm.ColorDepth)); // TODO: use unique_ptr when available + + // Windowed mode + if (dm.Windowed) + { + // If windowed mode, make the resolution stay in the generally supported limits + dm_compat.Width = Math::Min(dm_compat.Width, device_size.Width); + dm_compat.Height = Math::Min(dm_compat.Height, device_size.Height); + } + // Fullscreen mode + else + { + // If told to find mode with aspect ratio matching current desktop resolution, then first + // try find matching one, and if failed then try any compatible one + bool mode_found = false; + if (modes.get()) + { + if (match_device_ratio) + mode_found = find_nearest_supported_mode(*modes.get(), screen_size, dm.ColorDepth, &device_size, nullptr, dm_compat); + if (!mode_found) + mode_found = find_nearest_supported_mode(*modes.get(), screen_size, dm.ColorDepth, nullptr, nullptr, dm_compat); + } + if (!mode_found) + Debug::Printf("Could not find compatible fullscreen mode. Will try to force-set mode requested by user and fallback to windowed mode if that fails."); + dm_compat.Vsync = dm.Vsync; + dm_compat.Windowed = false; + } + + bool result = graphics_mode_set_dm(dm_compat); + if (!result && dm.Windowed) + { + // When initializing windowed mode we could start with any random window size; + // if that did not work, try to find nearest supported mode, as with fullscreen mode, + // except refering to max window size as an upper bound + if (find_nearest_supported_mode(*modes.get(), screen_size, dm.ColorDepth, nullptr, &device_size, dm_compat)) + { + dm_compat.Vsync = dm.Vsync; + dm_compat.Windowed = true; + result = graphics_mode_set_dm(dm_compat); + } + } + return result; +} + +// Try to find and initialize compatible display mode as close to given setup as possible +bool try_init_mode_using_setup(const Size &game_size, const DisplayModeSetup &dm_setup, + const int col_depth, const GameFrameSetup &frame_setup, + const GfxFilterSetup &filter_setup) +{ + // We determine the requested size of the screen using setup options + const Size screen_size = precalc_screen_size(game_size, dm_setup, frame_setup); + DisplayMode dm(GraphicResolution(screen_size.Width, screen_size.Height, col_depth), + dm_setup.Windowed, dm_setup.RefreshRate, dm_setup.VSync); + if (!try_init_compatible_mode(dm, dm_setup.ScreenSize.SizeDef == kScreenDef_Explicit ? false : dm_setup.ScreenSize.MatchDeviceRatio)) + return false; + + // Set up native size and render frame + if (!graphics_mode_set_native_size(game_size) || !graphics_mode_set_render_frame(frame_setup)) + return false; + + // Set up graphics filter + if (!graphics_mode_set_filter_any(filter_setup)) + return false; + return true; +} + +void log_out_driver_modes(const int color_depth) +{ + IGfxModeList *modes = gfxDriver->GetSupportedModeList(color_depth); + if (!modes) + { + Debug::Printf(kDbgMsg_Error, "Couldn't get a list of supported resolutions for color depth = %d", color_depth); + return; + } + const int mode_count = modes->GetModeCount(); + DisplayMode mode; + String mode_str; + for (int i = 0, in_str = 0; i < mode_count; ++i) + { + if (!modes->GetMode(i, mode) || mode.ColorDepth != color_depth) + continue; + mode_str.Append(String::FromFormat("%dx%d;", mode.Width, mode.Height)); + if (++in_str % 8 == 0) + mode_str.Append("\n\t"); + } + delete modes; + + String out_str = String::FromFormat("Supported gfx modes (%d-bit): ", color_depth); + if (!mode_str.IsEmpty()) + { + out_str.Append("\n\t"); + out_str.Append(mode_str); + } + else + out_str.Append("none"); + Debug::Printf(out_str); +} + +// Create requested graphics driver and try to find and initialize compatible display mode as close to user setup as possible; +// if the given setup fails, gets default setup for the opposite type of mode (fullscreen/windowed) and tries that instead. +bool create_gfx_driver_and_init_mode_any(const String &gfx_driver_id, const Size &game_size, const DisplayModeSetup &dm_setup, + const ColorDepthOption &color_depth, const GameFrameSetup &frame_setup, const GfxFilterSetup &filter_setup) +{ + if (!graphics_mode_create_renderer(gfx_driver_id)) + return false; + + const int use_col_depth = + color_depth.Forced ? color_depth.Bits : gfxDriver->GetDisplayDepthForNativeDepth(color_depth.Bits); + // Log out supported driver modes + log_out_driver_modes(use_col_depth); + + bool result = try_init_mode_using_setup(game_size, dm_setup, use_col_depth, frame_setup, filter_setup); + // Try windowed mode if fullscreen failed, and vice versa + if (!result && editor_debugging_enabled == 0) + { + // we need to clone from initial config, because not every parameter is set by graphics_mode_get_defaults() + DisplayModeSetup dm_setup_alt = dm_setup; + dm_setup_alt.Windowed = !dm_setup.Windowed; + GameFrameSetup frame_setup_alt; + graphics_mode_get_defaults(dm_setup_alt.Windowed, dm_setup_alt.ScreenSize, frame_setup_alt); + result = try_init_mode_using_setup(game_size, dm_setup_alt, use_col_depth, frame_setup_alt, filter_setup); + } + return result; +} + +bool simple_create_gfx_driver_and_init_mode(const String &gfx_driver_id, + const Size &game_size, + const DisplayModeSetup &dm_setup, + const ColorDepthOption &color_depth, + const GameFrameSetup &frame_setup, + const GfxFilterSetup &filter_setup) +{ + if (!graphics_mode_create_renderer(gfx_driver_id)) { return false; } + + const int col_depth = gfxDriver->GetDisplayDepthForNativeDepth(color_depth.Bits); + + DisplayMode dm(GraphicResolution(game_size.Width, game_size.Height, col_depth), + dm_setup.Windowed, dm_setup.RefreshRate, dm_setup.VSync); + + if (!graphics_mode_set_dm(dm)) { return false; } + if (!graphics_mode_set_native_size(game_size)) { return false; } + if (!graphics_mode_set_render_frame(frame_setup)) { return false; } + if (!graphics_mode_set_filter_any(filter_setup)) { return false; } + + return true; +} + + +void display_gfx_mode_error(const Size &game_size, const ScreenSetup &setup, const int color_depth) +{ + proper_exit=1; + platform->FinishedUsingGraphicsMode(); + + String main_error; + ScreenSizeSetup scsz = setup.DisplayMode.ScreenSize; + PGfxFilter filter = gfxDriver ? gfxDriver->GetGraphicsFilter() : PGfxFilter(); + Size wanted_screen; + if (scsz.SizeDef == kScreenDef_Explicit) + main_error.Format("There was a problem initializing graphics mode %d x %d (%d-bit), or finding nearest compatible mode, with game size %d x %d and filter '%s'.", + scsz.Size.Width, scsz.Size.Height, color_depth, game_size.Width, game_size.Height, filter ? filter->GetInfo().Id.GetCStr() : "Undefined"); + else + main_error.Format("There was a problem finding and/or creating valid graphics mode for game size %d x %d (%d-bit) and requested filter '%s'.", + game_size.Width, game_size.Height, color_depth, setup.Filter.UserRequest.IsEmpty() ? "Undefined" : setup.Filter.UserRequest.GetCStr()); + + platform->DisplayAlert("%s\n" + "(Problem: '%s')\n" + "Try to correct the problem, or seek help from the AGS homepage." + "%s", + main_error.GetCStr(), get_allegro_error(), platform->GetGraphicsTroubleshootingText()); +} + +bool graphics_mode_init_any(const Size game_size, const ScreenSetup &setup, const ColorDepthOption &color_depth) +{ + // Log out display information + Size device_size; + if (get_desktop_resolution(&device_size.Width, &device_size.Height) == 0) + Debug::Printf("Device display resolution: %d x %d", device_size.Width, device_size.Height); + else + Debug::Printf(kDbgMsg_Error, "Unable to obtain device resolution"); + + const char *screen_sz_def_options[kNumScreenDef] = { "explicit", "scaling", "max" }; + ScreenSizeSetup scsz = setup.DisplayMode.ScreenSize; + const bool ignore_device_ratio = setup.DisplayMode.Windowed || scsz.SizeDef == kScreenDef_Explicit; + GameFrameSetup gameframe = setup.DisplayMode.Windowed ? setup.WinGameFrame : setup.FsGameFrame; + const String scale_option = make_scaling_option(gameframe); + Debug::Printf(kDbgMsg_Info, "Graphic settings: driver: %s, windowed: %s, screen def: %s, screen size: %d x %d, match device ratio: %s, game scale: %s", + setup.DriverID.GetCStr(), + setup.DisplayMode.Windowed ? "yes" : "no", screen_sz_def_options[scsz.SizeDef], + scsz.Size.Width, scsz.Size.Height, + ignore_device_ratio ? "ignore" : (scsz.MatchDeviceRatio ? "yes" : "no"), scale_option.GetCStr()); + + // Prepare the list of available gfx factories, having the one requested by user at first place + // TODO: make factory & driver IDs case-insensitive! + StringV ids; + GetGfxDriverFactoryNames(ids); + StringV::iterator it = ids.begin(); + for (; it != ids.end(); ++it) + { + if (it->CompareNoCase(setup.DriverID) == 0) break; + } + if (it != ids.end()) + std::rotate(ids.begin(), it, ids.end()); + else + Debug::Printf(kDbgMsg_Error, "Requested graphics driver '%s' not found, will try existing drivers instead", setup.DriverID.GetCStr()); + + // Try to create renderer and init gfx mode, choosing one factory at a time + bool result = false; + for (StringV::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + result = +#ifdef USE_SIMPLE_GFX_INIT + simple_create_gfx_driver_and_init_mode +#else + create_gfx_driver_and_init_mode_any +#endif + (*it, game_size, setup.DisplayMode, color_depth, gameframe, setup.Filter); + + if (result) + break; + graphics_mode_shutdown(); + } + // If all possibilities failed, display error message and quit + if (!result) + { + display_gfx_mode_error(game_size, setup, color_depth.Bits); + return false; + } + return true; +} + +ActiveDisplaySetting graphics_mode_get_last_setting(bool windowed) +{ + return windowed ? SavedWindowedSetting : SavedFullscreenSetting; +} + +bool graphics_mode_update_render_frame(); +void GfxDriverOnSurfaceUpdate() +{ + // Resize render frame using current scaling settings + graphics_mode_update_render_frame(); + on_coordinates_scaling_changed(); +} + +bool graphics_mode_create_renderer(const String &driver_id) +{ + if (!create_gfx_driver(driver_id)) + return false; + + gfxDriver->SetCallbackOnInit(GfxDriverOnInitCallback); + gfxDriver->SetCallbackOnSurfaceUpdate(GfxDriverOnSurfaceUpdate); + // TODO: this is remains of the old code; find out if this is really + // the best time and place to set the tint method + gfxDriver->SetTintMethod(TintReColourise); + return true; +} + +bool graphics_mode_set_dm_any(const Size &game_size, const DisplayModeSetup &dm_setup, + const ColorDepthOption &color_depth, const GameFrameSetup &frame_setup) +{ + // We determine the requested size of the screen using setup options + const Size screen_size = precalc_screen_size(game_size, dm_setup, frame_setup); + DisplayMode dm(GraphicResolution(screen_size.Width, screen_size.Height, color_depth.Bits), + dm_setup.Windowed, dm_setup.RefreshRate, dm_setup.VSync); + return try_init_compatible_mode(dm, dm_setup.ScreenSize.MatchDeviceRatio); +} + +bool graphics_mode_set_dm(const DisplayMode &dm) +{ + Debug::Printf("Attempt to switch gfx mode to %d x %d (%d-bit) %s", + dm.Width, dm.Height, dm.ColorDepth, dm.Windowed ? "windowed" : "fullscreen"); + + // Tell Allegro new default bitmap color depth (must be done before set_gfx_mode) + // TODO: this is also done inside ALSoftwareGraphicsDriver implementation; can remove one? + set_color_depth(dm.ColorDepth); + // TODO: this is remains of the old code; find out what it means and do we + // need this if we are not using allegro software driver? + if (dm.RefreshRate >= 50) + request_refresh_rate(dm.RefreshRate); + + if (!gfxDriver->SetDisplayMode(dm, nullptr)) + { + Debug::Printf(kDbgMsg_Error, "Failed to init gfx mode. Error: %s", get_allegro_error()); + return false; + } + + DisplayMode rdm = gfxDriver->GetDisplayMode(); + if (rdm.Windowed) + SavedWindowedSetting.Dm = rdm; + else + SavedFullscreenSetting.Dm = rdm; + Debug::Printf("Succeeded. Using gfx mode %d x %d (%d-bit) %s", + rdm.Width, rdm.Height, rdm.ColorDepth, rdm.Windowed ? "windowed" : "fullscreen"); + return true; +} + +bool graphics_mode_update_render_frame() +{ + if (!gfxDriver || !gfxDriver->IsModeSet() || !gfxDriver->IsNativeSizeValid()) + return false; + + DisplayMode dm = gfxDriver->GetDisplayMode(); + Size screen_size = Size(dm.Width, dm.Height); + Size native_size = gfxDriver->GetNativeSize(); + Size frame_size = set_game_frame_after_screen_size(native_size, screen_size, CurFrameSetup); + Rect render_frame = CenterInRect(RectWH(screen_size), RectWH(frame_size)); + + if (!gfxDriver->SetRenderFrame(render_frame)) + { + Debug::Printf(kDbgMsg_Error, "Failed to set render frame (%d, %d, %d, %d : %d x %d). Error: %s", + render_frame.Left, render_frame.Top, render_frame.Right, render_frame.Bottom, + render_frame.GetWidth(), render_frame.GetHeight(), get_allegro_error()); + return false; + } + + Rect dst_rect = gfxDriver->GetRenderDestination(); + Debug::Printf("Render frame set, render dest (%d, %d, %d, %d : %d x %d)", + dst_rect.Left, dst_rect.Top, dst_rect.Right, dst_rect.Bottom, dst_rect.GetWidth(), dst_rect.GetHeight()); + // init game scaling transformation + GameScaling.Init(native_size, gfxDriver->GetRenderDestination()); + return true; +} + +bool graphics_mode_set_native_size(const Size &native_size) +{ + if (!gfxDriver || native_size.IsNull()) + return false; + if (!gfxDriver->SetNativeSize(native_size)) + return false; + // if render frame translation was already set, then update it with new native size + if (gfxDriver->IsRenderFrameValid()) + graphics_mode_update_render_frame(); + return true; +} + +GameFrameSetup graphics_mode_get_render_frame() +{ + return CurFrameSetup; +} + +bool graphics_mode_set_render_frame(const GameFrameSetup &frame_setup) +{ + if (!frame_setup.IsValid()) + return false; + CurFrameSetup = frame_setup; + if (gfxDriver->GetDisplayMode().Windowed) + SavedWindowedSetting.FrameSetup = frame_setup; + else + SavedFullscreenSetting.FrameSetup = frame_setup; + graphics_mode_update_render_frame(); + return true; +} + +bool graphics_mode_set_filter(const String &filter_id) +{ + if (!GfxFactory) + return false; + + String filter_error; + PGfxFilter filter = GfxFactory->SetFilter(filter_id, filter_error); + if (!filter) + { + Debug::Printf(kDbgMsg_Error, "Unable to set graphics filter '%s'. Error: %s", filter_id.GetCStr(), filter_error.GetCStr()); + return false; + } + Rect filter_rect = filter->GetDestination(); + Debug::Printf("Graphics filter set: '%s', filter dest (%d, %d, %d, %d : %d x %d)", filter->GetInfo().Id.GetCStr(), + filter_rect.Left, filter_rect.Top, filter_rect.Right, filter_rect.Bottom, filter_rect.GetWidth(), filter_rect.GetHeight()); + return true; +} + +void graphics_mode_shutdown() +{ + if (GfxFactory) + GfxFactory->Shutdown(); + GfxFactory = nullptr; + gfxDriver = nullptr; + + // Tell Allegro that we are no longer in graphics mode + set_gfx_mode(GFX_TEXT, 0, 0, 0, 0); +} diff --git a/engines/ags/engine/main/graphics_mode.h b/engines/ags/engine/main/graphics_mode.h new file mode 100644 index 00000000000..8329e407120 --- /dev/null +++ b/engines/ags/engine/main/graphics_mode.h @@ -0,0 +1,158 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__GRAPHICSMODE_H +#define __AGS_EE_MAIN__GRAPHICSMODE_H + +#include "gfx/gfxdefines.h" +#include "util/scaling.h" +#include "util/string.h" + +using AGS::Common::String; +using AGS::Engine::DisplayMode; + +Size get_desktop_size(); +String make_scaling_factor_string(uint32_t scaling); + +namespace AGS { namespace Engine { class IGfxModeList; }} +bool find_nearest_supported_mode(const AGS::Engine::IGfxModeList &modes, const Size &wanted_size, + const int color_depth, const Size *ratio_reference, const Size *upper_bound, + AGS::Engine::DisplayMode &dm, int *mode_index = nullptr); + + +// The game-to-screen transformation +// TODO: this is only required for low-level mouse processing; +// when possible, move to mouse "manager" object, and assign at gfxmode init +extern AGS::Engine::PlaneScaling GameScaling; + + +// Filter configuration +struct GfxFilterSetup +{ + String ID; // internal filter ID + String UserRequest; // filter name, requested by user +}; + +enum FrameScaleDefinition +{ + kFrame_IntScale, // explicit integer scaling x/y factors + kFrame_MaxRound, // calculate max round uniform scaling factor + kFrame_MaxStretch, // resize to maximal possible inside the display box + kFrame_MaxProportional, // same as stretch, but keep game's aspect ratio + kNumFrameScaleDef +}; + +// Game frame configuration +struct GameFrameSetup +{ + FrameScaleDefinition ScaleDef; // a method used to determine game frame scaling + int ScaleFactor; // explicit scale factor + + GameFrameSetup(); + GameFrameSetup(FrameScaleDefinition def, int factor = 0); + bool IsValid() const; +}; + +enum ScreenSizeDefinition +{ + kScreenDef_Explicit, // define by width & height + kScreenDef_ByGameScaling, // define by game scale factor + kScreenDef_MaxDisplay, // set to maximal supported (desktop/device screen size) + kNumScreenDef +}; + +// Configuration that is used to determine the size of the screen +struct ScreenSizeSetup +{ + ScreenSizeDefinition SizeDef; // a method used to determine screen size + ::Size Size; // explicitly defined screen metrics + bool MatchDeviceRatio; // whether to choose resolution matching device aspect ratio + + ScreenSizeSetup(); +}; + +// Display mode configuration +struct DisplayModeSetup +{ + ScreenSizeSetup ScreenSize; + + int RefreshRate; // gfx mode refresh rate + bool VSync; // vertical sync + bool Windowed; // is mode windowed + + DisplayModeSetup(); +}; + +// Full graphics configuration +struct ScreenSetup +{ + String DriverID; // graphics driver ID + DisplayModeSetup DisplayMode; // definition of the initial display mode + + // Definitions for the fullscreen and windowed scaling methods. + // When the initial display mode is set, corresponding scaling method from this pair is used. + // The second method is meant to be saved and used if display mode is switched at runtime. + GameFrameSetup FsGameFrame; // how the game frame should be scaled/positioned in fullscreen mode + GameFrameSetup WinGameFrame; // how the game frame should be scaled/positioned in windowed mode + + GfxFilterSetup Filter; // graphics filter definition +}; + +// Display mode color depth variants suggested for the use +struct ColorDepthOption +{ + int Bits; // color depth value in bits + bool Forced; // whether the depth should be forced, or driver's recommendation used + + ColorDepthOption() : Bits(0), Forced(false) {} + ColorDepthOption(int bits, bool forced = false) : Bits(bits), Forced(forced) {} +}; + +// ActiveDisplaySetting struct merges DisplayMode and GameFrameSetup, +// which is useful if you need to save active settings and reapply them later. +struct ActiveDisplaySetting +{ + DisplayMode Dm; + GameFrameSetup FrameSetup; +}; + +// Initializes any possible gfx mode, using user config as a recommendation; +// may try all available renderers and modes before succeeding (or failing) +bool graphics_mode_init_any(const Size game_size, const ScreenSetup &setup, const ColorDepthOption &color_depth); +// Return last saved display mode of the given kind +ActiveDisplaySetting graphics_mode_get_last_setting(bool windowed); +// Creates graphics driver of given id +bool graphics_mode_create_renderer(const String &driver_id); +// Try to find and initialize compatible display mode as close to given setup as possible +bool graphics_mode_set_dm_any(const Size &game_size, const DisplayModeSetup &dm_setup, + const ColorDepthOption &color_depth, const GameFrameSetup &frame_setup); +// Set the display mode with given parameters +bool graphics_mode_set_dm(const AGS::Engine::DisplayMode &dm); +// Set the native image size +bool graphics_mode_set_native_size(const Size &native_size); +// Get current render frame setup +GameFrameSetup graphics_mode_get_render_frame(); +// Set the render frame position inside the window +bool graphics_mode_set_render_frame(const GameFrameSetup &frame_setup); +// Set requested graphics filter, or default filter if the requested one failed +bool graphics_mode_set_filter_any(const GfxFilterSetup &setup); +// Set the scaling filter with given ID +bool graphics_mode_set_filter(const String &filter_id); +// Releases current graphic mode and shuts down renderer +void graphics_mode_shutdown(); + +#endif // __AGS_EE_MAIN__GRAPHICSMODE_H diff --git a/engines/ags/engine/main/main.cpp b/engines/ags/engine/main/main.cpp new file mode 100644 index 00000000000..5ac2f6a903d --- /dev/null +++ b/engines/ags/engine/main/main.cpp @@ -0,0 +1,495 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +// +// Entry point of the application here. +// +// +// For Windows main() function is really called _mangled_main and is called +// not by system, but from insides of allegro library. +// (See allegro\platform\alwin.h) +// What about other platforms? +// + +#include "core/platform.h" +#define AGS_PLATFORM_DEFINES_PSP_VARS (AGS_PLATFORM_OS_IOS || AGS_PLATFORM_OS_ANDROID) + +#include +#include "ac/common.h" +#include "ac/gamesetup.h" +#include "ac/gamestate.h" +#include "core/def_version.h" +#include "debug/debugger.h" +#include "debug/debug_log.h" +#include "debug/out.h" +#include "main/config.h" +#include "main/engine.h" +#include "main/mainheader.h" +#include "main/main.h" +#include "platform/base/agsplatformdriver.h" +#include "ac/route_finder.h" +#include "core/assetmanager.h" +#include "util/directory.h" +#include "util/path.h" +#include "util/string_compat.h" + +#if AGS_PLATFORM_OS_WINDOWS +#include "platform/windows/win_ex_handling.h" +#endif +#if AGS_PLATFORM_DEBUG +#include "test/test_all.h" +#endif + +#if AGS_PLATFORM_OS_WINDOWS && !AGS_PLATFORM_DEBUG +#define USE_CUSTOM_EXCEPTION_HANDLER +#endif + +using namespace AGS::Common; +using namespace AGS::Engine; + +String appDirectory; // Needed for library loading +String cmdGameDataPath; + +char **global_argv = nullptr; +int global_argc = 0; + + +extern GameSetup usetup; +extern GameState play; +extern int our_eip; +extern AGSPlatformDriver *platform; +extern int convert_16bit_bgr; +extern int editor_debugging_enabled; +extern int editor_debugging_initialized; +extern char editor_debugger_instance_token[100]; + + +// Startup flags, set from parameters to engine +int force_window = 0; +int override_start_room = 0; +bool justDisplayHelp = false; +bool justDisplayVersion = false; +bool justRunSetup = false; +bool justRegisterGame = false; +bool justUnRegisterGame = false; +bool justTellInfo = false; +bool attachToParentConsole = false; +bool hideMessageBoxes = false; +std::set tellInfoKeys; +const char *loadSaveGameOnStartup = nullptr; + +#if ! AGS_PLATFORM_DEFINES_PSP_VARS +int psp_video_framedrop = 1; +int psp_audio_enabled = 1; +int psp_midi_enabled = 1; +int psp_ignore_acsetup_cfg_file = 0; +int psp_clear_cache_on_room_change = 0; + +int psp_midi_preload_patches = 0; +int psp_audio_cachesize = 10; +char psp_game_file_name[] = ""; +char psp_translation[] = "default"; + +int psp_gfx_renderer = 0; +int psp_gfx_scaling = 1; +int psp_gfx_smoothing = 0; +int psp_gfx_super_sampling = 1; +int psp_gfx_smooth_sprites = 0; +#endif + + +void main_pre_init() +{ + our_eip = -999; + Common::AssetManager::SetSearchPriority(Common::kAssetPriorityDir); + play.takeover_data = 0; +} + +void main_create_platform_driver() +{ + platform = AGSPlatformDriver::GetDriver(); +} + +// this needs to be updated if the "play" struct changes +#define SVG_VERSION_BWCOMPAT_MAJOR 3 +#define SVG_VERSION_BWCOMPAT_MINOR 2 +#define SVG_VERSION_BWCOMPAT_RELEASE 0 +#define SVG_VERSION_BWCOMPAT_REVISION 1103 +// CHECKME: we may lower this down, if we find that earlier versions may still +// load new savedgames +#define SVG_VERSION_FWCOMPAT_MAJOR 3 +#define SVG_VERSION_FWCOMPAT_MINOR 2 +#define SVG_VERSION_FWCOMPAT_RELEASE 1 +#define SVG_VERSION_FWCOMPAT_REVISION 1111 + +// Current engine version +AGS::Common::Version EngineVersion; +// Lowest savedgame version, accepted by this engine +AGS::Common::Version SavedgameLowestBackwardCompatVersion; +// Lowest engine version, which would accept current savedgames +AGS::Common::Version SavedgameLowestForwardCompatVersion; + +void main_init(int argc, char*argv[]) +{ + EngineVersion = Version(ACI_VERSION_STR " " SPECIAL_VERSION); +#if defined (BUILD_STR) + EngineVersion.BuildInfo = BUILD_STR; +#endif + SavedgameLowestBackwardCompatVersion = Version(SVG_VERSION_BWCOMPAT_MAJOR, SVG_VERSION_BWCOMPAT_MINOR, SVG_VERSION_BWCOMPAT_RELEASE, SVG_VERSION_BWCOMPAT_REVISION); + SavedgameLowestForwardCompatVersion = Version(SVG_VERSION_FWCOMPAT_MAJOR, SVG_VERSION_FWCOMPAT_MINOR, SVG_VERSION_FWCOMPAT_RELEASE, SVG_VERSION_FWCOMPAT_REVISION); + + Common::AssetManager::CreateInstance(); + main_pre_init(); + main_create_platform_driver(); + + global_argv = argv; + global_argc = argc; +} + +String get_engine_string() +{ + return String::FromFormat("Adventure Game Studio v%s Interpreter\n" + "Copyright (c) 1999-2011 Chris Jones and " ACI_COPYRIGHT_YEARS " others\n" +#ifdef BUILD_STR + "ACI version %s (Build: %s)\n", + EngineVersion.ShortString.GetCStr(), EngineVersion.LongString.GetCStr(), EngineVersion.BuildInfo.GetCStr()); +#else + "ACI version %s\n", EngineVersion.ShortString.GetCStr(), EngineVersion.LongString.GetCStr()); +#endif +} + +extern char return_to_roomedit[30]; +extern char return_to_room[150]; + +void main_print_help() { + platform->WriteStdOut( + "Usage: ags [OPTIONS] [GAMEFILE or DIRECTORY]\n\n" + //--------------------------------------------------------------------------------| + "Options:\n" +#if AGS_PLATFORM_OS_WINDOWS + " --console-attach Write output to the parent process's console\n" +#endif + " --fps Display fps counter\n" + " --fullscreen Force display mode to fullscreen\n" + " --gfxdriver Request graphics driver. Available options:\n" +#if AGS_PLATFORM_OS_WINDOWS + " d3d9, ogl, software\n" +#else + " ogl, software\n" +#endif + " --gfxfilter FILTER [SCALING]\n" + " Request graphics filter. Available options:\n" + " hqx, linear, none, stdscale\n" + " (support differs between graphic drivers);\n" + " scaling is specified by integer number\n" + " --help Print this help message and stop\n" + " --log-OUTPUT=GROUP[:LEVEL][,GROUP[:LEVEL]][,...]\n" + " --log-OUTPUT=+GROUPLIST[:LEVEL]\n" + " Setup logging to the chosen OUTPUT with given\n" + " log groups and verbosity levels. Groups may\n" + " be also defined by a LIST of one-letter IDs,\n" + " preceded by '+', e.g. +ABCD:LEVEL. Verbosity may\n" + " be also defined by a numberic ID.\n" + " OUTPUTs are\n" + " stdout, file, console\n" + " (where \"console\" is internal engine's console)\n" + " GROUPs are:\n" + " all, main (m), game (g), manobj (o),\n" + " script (s), sprcache (c)\n" + " LEVELs are:\n" + " all, alert (1), fatal (2), error (3), warn (4),\n" + " info (5), debug (6)\n" + " Examples:\n" + " --log-stdout=+mgs:debug\n" + " --log-file=all:warn\n" + " --log-file-path=PATH Define custom path for the log file\n" + //--------------------------------------------------------------------------------| +#if AGS_PLATFORM_OS_WINDOWS + " --no-message-box Disable reporting of alerts to message boxes\n" + " --setup Run setup application\n" +#endif + " --tell Print various information concerning engine\n" + " and the game; for selected output use:\n" + " --tell-config Print contents of merged game config\n" + " --tell-configpath Print paths to available config files\n" + " --tell-data Print information on game data and its location\n" + " --tell-engine Print engine name and version\n" + " --tell-graphicdriver Print list of supported graphic drivers\n" + "\n" + " --version Print engine's version and stop\n" + " --windowed Force display mode to windowed\n" + "\n" + "Gamefile options:\n" + " /dir/path/game/ Launch the game in specified directory\n" + " /dir/path/game/penguin.exe Launch penguin.exe\n" + " [nothing] Launch the game in the current directory\n" + //--------------------------------------------------------------------------------| + ); +} + +static int main_process_cmdline(ConfigTree &cfg, int argc, char *argv[]) +{ + int datafile_argv = 0; + for (int ee = 1; ee < argc; ++ee) + { + const char *arg = argv[ee]; + // + // Startup options + // + if (ags_stricmp(arg,"--help") == 0 || ags_stricmp(arg,"/?") == 0 || ags_stricmp(arg,"-?") == 0) + { + justDisplayHelp = true; + return 0; + } + if (ags_stricmp(arg, "-v") == 0 || ags_stricmp(arg, "--version") == 0) + { + justDisplayVersion = true; + return 0; + } + else if (ags_stricmp(arg,"-updatereg") == 0) + debug_flags |= DBG_REGONLY; +#if AGS_PLATFORM_DEBUG + else if ((ags_stricmp(arg,"--startr") == 0) && (ee < argc-1)) { + override_start_room = atoi(argv[ee+1]); + ee++; + } +#endif + else if ((ags_stricmp(arg,"--testre") == 0) && (ee < argc-2)) { + strcpy(return_to_roomedit, argv[ee+1]); + strcpy(return_to_room, argv[ee+2]); + ee+=2; + } + else if (ags_stricmp(arg,"-noexceptionhandler")==0) usetup.disable_exception_handling = true; + else if (ags_stricmp(arg, "--setup") == 0) + { + justRunSetup = true; + } + else if (ags_stricmp(arg,"-registergame") == 0) + { + justRegisterGame = true; + } + else if (ags_stricmp(arg,"-unregistergame") == 0) + { + justUnRegisterGame = true; + } + else if ((ags_stricmp(arg,"-loadsavedgame") == 0) && (argc > ee + 1)) + { + loadSaveGameOnStartup = argv[ee + 1]; + ee++; + } + else if ((ags_stricmp(arg,"--enabledebugger") == 0) && (argc > ee + 1)) + { + strcpy(editor_debugger_instance_token, argv[ee + 1]); + editor_debugging_enabled = 1; + force_window = 1; + ee++; + } + else if (ags_stricmp(arg, "--runfromide") == 0 && (argc > ee + 3)) + { + usetup.install_dir = argv[ee + 1]; + usetup.install_audio_dir = argv[ee + 2]; + usetup.install_voice_dir = argv[ee + 3]; + ee += 3; + } + else if (ags_stricmp(arg,"--takeover")==0) { + if (argc < ee+2) + break; + play.takeover_data = atoi (argv[ee + 1]); + strncpy (play.takeover_from, argv[ee + 2], 49); + play.takeover_from[49] = 0; + ee += 2; + } + else if (ags_strnicmp(arg, "--tell", 6) == 0) { + if (arg[6] == 0) + tellInfoKeys.insert(String("all")); + else if (arg[6] == '-' && arg[7] != 0) + tellInfoKeys.insert(String(arg + 7)); + } + // + // Config overrides + // + else if (ags_stricmp(arg, "-windowed") == 0 || ags_stricmp(arg, "--windowed") == 0) + force_window = 1; + else if (ags_stricmp(arg, "-fullscreen") == 0 || ags_stricmp(arg, "--fullscreen") == 0) + force_window = 2; + else if ((ags_stricmp(arg, "-gfxdriver") == 0 || ags_stricmp(arg, "--gfxdriver") == 0) && (argc > ee + 1)) + { + INIwritestring(cfg, "graphics", "driver", argv[++ee]); + } + else if ((ags_stricmp(arg, "-gfxfilter") == 0 || ags_stricmp(arg, "--gfxfilter") == 0) && (argc > ee + 1)) + { + // NOTE: we make an assumption here that if user provides scaling factor, + // this factor means to be applied to windowed mode only. + INIwritestring(cfg, "graphics", "filter", argv[++ee]); + if (argc > ee + 1 && argv[ee + 1][0] != '-') + INIwritestring(cfg, "graphics", "game_scale_win", argv[++ee]); + else + INIwritestring(cfg, "graphics", "game_scale_win", "max_round"); + } + else if (ags_stricmp(arg, "--fps") == 0) display_fps = kFPS_Forced; + else if (ags_stricmp(arg, "--test") == 0) debug_flags |= DBG_DEBUGMODE; + else if (ags_stricmp(arg, "-noiface") == 0) debug_flags |= DBG_NOIFACE; + else if (ags_stricmp(arg, "-nosprdisp") == 0) debug_flags |= DBG_NODRAWSPRITES; + else if (ags_stricmp(arg, "-nospr") == 0) debug_flags |= DBG_NOOBJECTS; + else if (ags_stricmp(arg, "-noupdate") == 0) debug_flags |= DBG_NOUPDATE; + else if (ags_stricmp(arg, "-nosound") == 0) debug_flags |= DBG_NOSFX; + else if (ags_stricmp(arg, "-nomusic") == 0) debug_flags |= DBG_NOMUSIC; + else if (ags_stricmp(arg, "-noscript") == 0) debug_flags |= DBG_NOSCRIPT; + else if (ags_stricmp(arg, "-novideo") == 0) debug_flags |= DBG_NOVIDEO; + else if (ags_stricmp(arg, "-dbgscript") == 0) debug_flags |= DBG_DBGSCRIPT; + else if (ags_strnicmp(arg, "--log-", 6) == 0 && arg[6] != 0) + { + String logarg = arg + 6; + size_t split_at = logarg.FindChar('='); + if (split_at >= 0) + cfg["log"][logarg.Left(split_at)] = logarg.Mid(split_at + 1); + else + cfg["log"][logarg] = ""; + } + else if (ags_stricmp(arg, "--console-attach") == 0) attachToParentConsole = true; + else if (ags_stricmp(arg, "--no-message-box") == 0) hideMessageBoxes = true; + // + // Special case: data file location + // + else if (arg[0]!='-') datafile_argv=ee; + } + + if (datafile_argv > 0) + { + cmdGameDataPath = GetPathFromCmdArg(datafile_argv); + } + else + { + // assign standard path for mobile/consoles (defined in their own platform implementation) + cmdGameDataPath = psp_game_file_name; + } + + if (tellInfoKeys.size() > 0) + justTellInfo = true; + + return 0; +} + +void main_set_gamedir(int argc, char*argv[]) +{ + appDirectory = Path::GetDirectoryPath(GetPathFromCmdArg(0)); + + if ((loadSaveGameOnStartup != nullptr) && (argv[0] != nullptr)) + { + // When launched by double-clicking a save game file, the curdir will + // be the save game folder unless we correct it + Directory::SetCurrentDirectory(appDirectory); + } + else + { + // It looks like Allegro library does not like ANSI (ACP) paths. + // When *not* working in U_UNICODE filepath mode, whenever it gets + // current directory for its own operations, it "fixes" it by + // substituting non-ASCII symbols with '^'. + // Here we explicitly set current directory to ASCII path. + String cur_dir = Directory::GetCurrentDirectory(); + String path = Path::GetPathInASCII(cur_dir); + if (!path.IsEmpty()) + Directory::SetCurrentDirectory(Path::MakeAbsolutePath(path)); + else + Debug::Printf(kDbgMsg_Error, "Unable to determine current directory: GetPathInASCII failed.\nArg: %s", cur_dir.GetCStr()); + } +} + +String GetPathFromCmdArg(int arg_index) +{ + if (arg_index < 0 || arg_index >= global_argc) + return ""; + String path = Path::GetCmdLinePathInASCII(global_argv[arg_index], arg_index); + if (!path.IsEmpty()) + return Path::MakeAbsolutePath(path); + Debug::Printf(kDbgMsg_Error, "Unable to determine path: GetCmdLinePathInASCII failed.\nCommand line argument %i: %s", arg_index, global_argv[arg_index]); + return global_argv[arg_index]; +} + +const char *get_allegro_error() +{ + return allegro_error; +} + +const char *set_allegro_error(const char *format, ...) +{ + va_list argptr; + va_start(argptr, format); + uvszprintf(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text(format), argptr); + va_end(argptr); + return allegro_error; +} + +int ags_entry_point(int argc, char *argv[]) { + +#ifdef AGS_RUN_TESTS + Test_DoAllTests(); +#endif + main_init(argc, argv); + +#if AGS_PLATFORM_OS_WINDOWS + setup_malloc_handling(); +#endif + debug_flags=0; + + ConfigTree startup_opts; + int res = main_process_cmdline(startup_opts, argc, argv); + if (res != 0) + return res; + + if (attachToParentConsole) + platform->AttachToParentConsole(); + + if (justDisplayVersion) + { + platform->WriteStdOut(get_engine_string()); + return EXIT_NORMAL; + } + + if (justDisplayHelp) + { + main_print_help(); + return EXIT_NORMAL; + } + + if (!justTellInfo && !hideMessageBoxes) + platform->SetGUIMode(true); + + init_debug(startup_opts, justTellInfo); + Debug::Printf(kDbgMsg_Alert, get_engine_string()); + + main_set_gamedir(argc, argv); + + // Update shell associations and exit + if (debug_flags & DBG_REGONLY) + exit(EXIT_NORMAL); + +#ifdef USE_CUSTOM_EXCEPTION_HANDLER + if (usetup.disable_exception_handling) +#endif + { + int result = initialize_engine(startup_opts); + // TODO: refactor engine shutdown routine (must shutdown and delete everything started and created) + allegro_exit(); + platform->PostAllegroExit(); + return result; + } +#ifdef USE_CUSTOM_EXCEPTION_HANDLER + else + { + return initialize_engine_with_exception_handling(initialize_engine, startup_opts); + } +#endif +} diff --git a/engines/ags/engine/main/main.h b/engines/ags/engine/main/main.h new file mode 100644 index 00000000000..1f5116d342b --- /dev/null +++ b/engines/ags/engine/main/main.h @@ -0,0 +1,65 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__MAIN_H +#define __AGS_EE_MAIN__MAIN_H + +#include "core/platform.h" +#include "util/version.h" + +// Current engine version +extern AGS::Common::Version EngineVersion; +// Lowest savedgame version, accepted by this engine +extern AGS::Common::Version SavedgameLowestBackwardCompatVersion; +// Lowest engine version, which would accept current savedgames +extern AGS::Common::Version SavedgameLowestForwardCompatVersion; + +//============================================================================= + +extern char **global_argv; + +// Location of the engine executable +extern AGS::Common::String appDirectory; +// Game path from the startup options (before reading config) +extern AGS::Common::String cmdGameDataPath; + +AGS::Common::String GetPathFromCmdArg(int arg_index); + +// Startup flags, set from parameters to engine +extern int force_window; +extern int override_start_room; +extern bool justRegisterGame; +extern bool justUnRegisterGame; +extern bool justTellInfo; +extern const char *loadSaveGameOnStartup; + +extern int psp_video_framedrop; +extern int psp_audio_enabled; +extern int psp_midi_enabled; +extern int psp_ignore_acsetup_cfg_file; +extern int psp_clear_cache_on_room_change; + +extern int psp_midi_preload_patches; +extern int psp_audio_cachesize; +extern char psp_game_file_name[]; +extern char psp_translation[]; + +void main_print_help(); + +int ags_entry_point(int argc, char *argv[]); + +#endif // __AGS_EE_MAIN__MAIN_H \ No newline at end of file diff --git a/engines/ags/engine/main/main_allegro.cpp b/engines/ags/engine/main/main_allegro.cpp new file mode 100644 index 00000000000..5a43762f44e --- /dev/null +++ b/engines/ags/engine/main/main_allegro.cpp @@ -0,0 +1,7 @@ +#include "allegro.h" +#include "main/main.h" + +int main(int argc, char *argv[]) { + return ags_entry_point(argc, argv); +} +END_OF_MAIN() diff --git a/engines/ags/engine/main/main_allegro.h b/engines/ags/engine/main/main_allegro.h new file mode 100644 index 00000000000..bbf7bcf6e8a --- /dev/null +++ b/engines/ags/engine/main/main_allegro.h @@ -0,0 +1,31 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__MAINALLEGRO_H +#define __AGS_EE_MAIN__MAINALLEGRO_H + +// Gets allegro_error as a const string. +// Please, use this getter to acquire error text, do not use allegro_error +// global variable directly. +const char *get_allegro_error(); +// Sets allegro_error global variable and returns a resulting string. +// The maximal allowed text length is defined by ALLEGRO_ERROR_SIZE macro +// (usually 256). If the formatted message is larger than that it will be +// truncated. Null terminator is always guaranteed. +const char *set_allegro_error(const char *format, ...); + +#endif // __AGS_EE_MAIN__MAINALLEGRO_H diff --git a/engines/ags/engine/main/maindefines_ex.h b/engines/ags/engine/main/maindefines_ex.h new file mode 100644 index 00000000000..d2b8729f47d --- /dev/null +++ b/engines/ags/engine/main/maindefines_ex.h @@ -0,0 +1,23 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__MAINDEFINES_H +#define __AGS_EE_MAIN__MAINDEFINES_H + +#define RETURN_CONTINUE 1 + +#endif // __AGS_EE_MAIN__MAINDEFINES_H diff --git a/engines/ags/engine/main/mainheader.h b/engines/ags/engine/main/mainheader.h new file mode 100644 index 00000000000..7b1d23f6a3a --- /dev/null +++ b/engines/ags/engine/main/mainheader.h @@ -0,0 +1,47 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__MAINHEADER_H +#define __AGS_EE_MAIN__MAINHEADER_H + +#include "core/platform.h" + +#include "main/maindefines_ex.h" + +#include "ac/math.h" +#include "script/script_runtime.h" +#include "gui/animatingguibutton.h" +#include "gui/guibutton.h" +#include "gfx/gfxfilter.h" +#include "util/string_utils.h" +#include "device/mousew32.h" +#include "ac/route_finder.h" +#include "util/misc.h" +#include "script/cc_error.h" + +// include last since we affect windows includes +#include "ac/file.h" + +#if AGS_PLATFORM_OS_ANDROID +#include +#include + +extern "C" void selectLatestSavegame(); +extern bool psp_load_latest_savegame; +#endif + +#endif // __AGS_EE_MAIN__MAINHEADER_H diff --git a/engines/ags/engine/main/quit.cpp b/engines/ags/engine/main/quit.cpp new file mode 100644 index 00000000000..678b376eef2 --- /dev/null +++ b/engines/ags/engine/main/quit.cpp @@ -0,0 +1,320 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +// +// Quit game procedure +// + +#include "core/platform.h" +#include "ac/cdaudio.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/roomstatus.h" +#include "ac/translation.h" +#include "debug/agseditordebugger.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "debug/out.h" +#include "font/fonts.h" +#include "main/config.h" +#include "main/engine.h" +#include "main/main.h" +#include "main/mainheader.h" +#include "main/quit.h" +#include "ac/spritecache.h" +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" +#include "core/assetmanager.h" +#include "plugin/plugin_engine.h" +#include "media/audio/audio_system.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern SpriteCache spriteset; +extern RoomStruct thisroom; +extern RoomStatus troom; // used for non-saveable rooms, eg. intro +extern int our_eip; +extern GameSetup usetup; +extern char pexbuf[STD_BUFFER_SIZE]; +extern int proper_exit; +extern char check_dynamic_sprites_at_exit; +extern int editor_debugging_initialized; +extern IAGSEditorDebugger *editor_debugger; +extern int need_to_stop_cd; +extern int use_cdplayer; +extern IGraphicsDriver *gfxDriver; + +bool handledErrorInEditor; + +void quit_tell_editor_debugger(const String &qmsg, QuitReason qreason) +{ + if (editor_debugging_initialized) + { + if (qreason & kQuitKind_GameException) + handledErrorInEditor = send_exception_to_editor(qmsg); + send_message_to_editor("EXIT"); + editor_debugger->Shutdown(); + } +} + +void quit_stop_cd() +{ + if (need_to_stop_cd) + cd_manager(3,0); +} + +void quit_shutdown_scripts() +{ + ccUnregisterAllObjects(); +} + +void quit_check_dynamic_sprites(QuitReason qreason) +{ + if ((qreason & kQuitKind_NormalExit) && (check_dynamic_sprites_at_exit) && + (game.options[OPT_DEBUGMODE] != 0)) { + // game exiting normally -- make sure the dynamic sprites + // have been deleted + for (int i = 1; i < spriteset.GetSpriteSlotCount(); i++) { + if (game.SpriteInfos[i].Flags & SPF_DYNAMICALLOC) + debug_script_warn("Dynamic sprite %d was never deleted", i); + } + } +} + +void quit_shutdown_platform(QuitReason qreason) +{ + // Be sure to unlock mouse on exit, or users will hate us + platform->UnlockMouse(); + platform->AboutToQuitGame(); + + our_eip = 9016; + + pl_stop_plugins(); + + quit_check_dynamic_sprites(qreason); + + platform->FinishedUsingGraphicsMode(); + + if (use_cdplayer) + platform->ShutdownCDPlayer(); +} + +void quit_shutdown_audio() +{ + our_eip = 9917; + game.options[OPT_CROSSFADEMUSIC] = 0; + stopmusic(); +#ifndef PSP_NO_MOD_PLAYBACK + if (usetup.mod_player) + remove_mod_player(); +#endif + + // Quit the sound thread. + audioThread.Stop(); + + remove_sound(); +} + +QuitReason quit_check_for_error_state(const char *&qmsg, String &alertis) +{ + if (qmsg[0]=='|') + { + return kQuit_GameRequest; + } + else if (qmsg[0]=='!') + { + QuitReason qreason; + qmsg++; + + if (qmsg[0] == '|') + { + qreason = kQuit_UserAbort; + alertis = "Abort key pressed.\n\n"; + } + else if (qmsg[0] == '?') + { + qmsg++; + qreason = kQuit_ScriptAbort; + alertis = "A fatal error has been generated by the script using the AbortGame function. Please contact the game author for support.\n\n"; + } + else + { + qreason = kQuit_GameError; + alertis.Format("An error has occurred. Please contact the game author for support, as this " + "is likely to be a scripting error and not a bug in AGS.\n" + "(ACI version %s)\n\n", EngineVersion.LongString.GetCStr()); + } + + alertis.Append(get_cur_script(5)); + + if (qreason != kQuit_UserAbort) + alertis.Append("\nError: "); + else + qmsg = ""; + return qreason; + } + else if (qmsg[0] == '%') + { + qmsg++; + alertis.Format("A warning has been generated. This is not normally fatal, but you have selected " + "to treat warnings as errors.\n" + "(ACI version %s)\n\n%s\n", EngineVersion.LongString.GetCStr(), get_cur_script(5).GetCStr()); + return kQuit_GameWarning; + } + else + { + alertis.Format("An internal error has occurred. Please note down the following information.\n" + "If the problem persists, post the details on the AGS Technical Forum.\n" + "(ACI version %s)\n" + "\nError: ", EngineVersion.LongString.GetCStr()); + return kQuit_FatalError; + } +} + +void quit_message_on_exit(const char *qmsg, String &alertis, QuitReason qreason) +{ + // successful exit displays no messages (because Windoze closes the dos-box + // if it is empty). + if ((qreason & kQuitKind_NormalExit) == 0 && !handledErrorInEditor) + { + // Display the message (at this point the window still exists) + sprintf(pexbuf,"%s\n",qmsg); + alertis.Append(pexbuf); + platform->DisplayAlert("%s", alertis.GetCStr()); + } +} + +void quit_release_data() +{ + resetRoomStatuses(); + thisroom.Free(); + play.Free(); + + /* _CrtMemState memstart; + _CrtMemCheckpoint(&memstart); + _CrtMemDumpStatistics( &memstart );*/ + + Common::AssetManager::DestroyInstance(); +} + +void quit_delete_temp_files() +{ + al_ffblk dfb; + int dun = al_findfirst("~ac*.tmp",&dfb,FA_SEARCH); + while (!dun) { + ::remove(dfb.name); + dun = al_findnext(&dfb); + } + al_findclose (&dfb); +} + +// TODO: move to test unit +extern Bitmap *test_allegro_bitmap; +extern IDriverDependantBitmap *test_allegro_ddb; +void allegro_bitmap_test_release() +{ + delete test_allegro_bitmap; + if (test_allegro_ddb) + gfxDriver->DestroyDDB(test_allegro_ddb); +} + +char return_to_roomedit[30] = "\0"; +char return_to_room[150] = "\0"; +// quit - exits the engine, shutting down everything gracefully +// The parameter is the message to print. If this message begins with +// an '!' character, then it is printed as a "contact game author" error. +// If it begins with a '|' then it is treated as a "thanks for playing" type +// message. If it begins with anything else, it is treated as an internal +// error. +// "!|" is a special code used to mean that the player has aborted (Alt+X) +void quit(const char *quitmsg) +{ + String alertis; + QuitReason qreason = quit_check_for_error_state(quitmsg, alertis); + // Need to copy it in case it's from a plugin (since we're + // about to free plugins) + String qmsg = quitmsg; + + if (qreason & kQuitKind_NormalExit) + save_config_file(); + + allegro_bitmap_test_release(); + + handledErrorInEditor = false; + + quit_tell_editor_debugger(qmsg, qreason); + + our_eip = 9900; + + quit_stop_cd(); + + our_eip = 9020; + + quit_shutdown_scripts(); + + quit_shutdown_platform(qreason); + + our_eip = 9019; + + quit_shutdown_audio(); + + our_eip = 9901; + + shutdown_font_renderer(); + our_eip = 9902; + + spriteset.Reset(); + + our_eip = 9907; + + close_translation(); + + our_eip = 9908; + + shutdown_pathfinder(); + + engine_shutdown_gfxmode(); + + quit_message_on_exit(qmsg, alertis, qreason); + + quit_release_data(); + + // release backed library + // WARNING: no Allegro objects should remain in memory after this, + // if their destruction is called later, program will crash! + allegro_exit(); + + platform->PostAllegroExit(); + + our_eip = 9903; + + quit_delete_temp_files(); + + proper_exit=1; + + Debug::Printf(kDbgMsg_Alert, "***** ENGINE HAS SHUTDOWN"); + + shutdown_debug(); + + our_eip = 9904; + exit(EXIT_NORMAL); +} + +extern "C" { + void quit_c(char*msg) { + quit(msg); + } +} diff --git a/engines/ags/engine/main/quit.h b/engines/ags/engine/main/quit.h new file mode 100644 index 00000000000..4eac73fdf68 --- /dev/null +++ b/engines/ags/engine/main/quit.h @@ -0,0 +1,45 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__QUIT_H +#define __AGS_EE_MAIN__QUIT_H + +enum QuitReason +{ + kQuitKind_NormalExit = 0x01, + kQuitKind_DeliberateAbort = 0x02, + kQuitKind_GameException = 0x04, + kQuitKind_EngineException = 0x08, + + // user closed the window or script command QuitGame was executed + kQuit_GameRequest = kQuitKind_NormalExit | 0x10, + + // user pressed abort game key + kQuit_UserAbort = kQuitKind_DeliberateAbort | 0x20, + + // script command AbortGame was executed + kQuit_ScriptAbort = kQuitKind_GameException | 0x10, + // game logic has generated a warning and warnings are treated as error + kQuit_GameWarning = kQuitKind_GameException | 0x20, + // game logic has generated an error (often script error) + kQuit_GameError = kQuitKind_GameException | 0x30, + + // any kind of a fatal engine error + kQuit_FatalError = kQuitKind_EngineException +}; + +#endif // __AGS_EE_MAIN__QUIT_H diff --git a/engines/ags/engine/main/update.cpp b/engines/ags/engine/main/update.cpp new file mode 100644 index 00000000000..c7ede532197 --- /dev/null +++ b/engines/ags/engine/main/update.cpp @@ -0,0 +1,497 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +// +// Game update procedure +// + +#include +#include "ac/common.h" +#include "ac/character.h" +#include "ac/characterextras.h" +#include "ac/draw.h" +#include "ac/gamestate.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_character.h" +#include "ac/lipsync.h" +#include "ac/overlay.h" +#include "ac/sys_events.h" +#include "ac/roomobject.h" +#include "ac/roomstatus.h" +#include "main/mainheader.h" +#include "main/update.h" +#include "ac/screenoverlay.h" +#include "ac/viewframe.h" +#include "ac/walkablearea.h" +#include "gfx/bitmap.h" +#include "gfx/graphicsdriver.h" +#include "media/audio/audio_system.h" +#include "ac/timer.h" +#include "main/game_run.h" +#include "ac/movelist.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern MoveList *mls; +extern RoomStatus*croom; +extern GameSetupStruct game; +extern GameState play; +extern RoomStruct thisroom; +extern RoomObject*objs; +extern ViewStruct*views; +extern int our_eip; +extern CharacterInfo*playerchar; +extern CharacterExtras *charextra; +extern CharacterInfo *facetalkchar; +extern int face_talking,facetalkview,facetalkwait,facetalkframe; +extern int facetalkloop, facetalkrepeat, facetalkAllowBlink; +extern int facetalkBlinkLoop; +extern bool facetalk_qfg4_override_placement_x, facetalk_qfg4_override_placement_y; +extern SpeechLipSyncLine *splipsync; +extern int numLipLines, curLipLine, curLipLinePhoneme; +extern int is_text_overlay; +extern IGraphicsDriver *gfxDriver; + +int do_movelist_move(short*mlnum,int*xx,int*yy) { + int need_to_fix_sprite=0; + if (mlnum[0]<1) quit("movelist_move: attempted to move on a non-exist movelist"); + MoveList*cmls; cmls=&mls[mlnum[0]]; + fixed xpermove=cmls->xpermove[cmls->onstage],ypermove=cmls->ypermove[cmls->onstage]; + + short targetx=short((cmls->pos[cmls->onstage+1] >> 16) & 0x00ffff); + short targety=short(cmls->pos[cmls->onstage+1] & 0x00ffff); + int xps=xx[0],yps=yy[0]; + if (cmls->doneflag & 1) { + // if the X-movement has finished, and the Y-per-move is < 1, finish + // This can cause jump at the end, but without it the character will + // walk on the spot for a while if the Y-per-move is for example 0.2 +// if ((ypermove & 0xfffff000) == 0) cmls->doneflag|=2; +// int ypmm=(ypermove >> 16) & 0x0000ffff; + + // NEW 2.15 SR-1 plan: if X-movement has finished, and Y-per-move is < 1, + // allow it to finish more easily by moving target zone + + int adjAmnt = 3; + // 2.70: if the X permove is also <=1, don't do the skipping + if (((xpermove & 0xffff0000) == 0xffff0000) || + ((xpermove & 0xffff0000) == 0x00000000)) + adjAmnt = 2; + + // 2.61 RC1: correct this to work with > -1 as well as < 1 + if (ypermove == 0) { } + // Y per move is < 1, so finish the move + else if ((ypermove & 0xffff0000) == 0) + targety -= adjAmnt; + // Y per move is -1 exactly, don't snap to finish + else if (ypermove == 0xffff0000) { } + // Y per move is > -1, so finish the move + else if ((ypermove & 0xffff0000) == 0xffff0000) + targety += adjAmnt; + } + else xps=cmls->fromx+(int)(fixtof(xpermove)*(float)cmls->onpart); + + if (cmls->doneflag & 2) { + // Y-movement has finished + + int adjAmnt = 3; + + // if the Y permove is also <=1, don't skip as far + if (((ypermove & 0xffff0000) == 0xffff0000) || + ((ypermove & 0xffff0000) == 0x00000000)) + adjAmnt = 2; + + if (xpermove == 0) { } + // Y per move is < 1, so finish the move + else if ((xpermove & 0xffff0000) == 0) + targetx -= adjAmnt; + // X per move is -1 exactly, don't snap to finish + else if (xpermove == 0xffff0000) { } + // X per move is > -1, so finish the move + else if ((xpermove & 0xffff0000) == 0xffff0000) + targetx += adjAmnt; + +/* int xpmm=(xpermove >> 16) & 0x0000ffff; +// if ((xpmm==0) | (xpmm==0xffff)) cmls->doneflag|=1; + if (xpmm==0) cmls->doneflag|=1;*/ + } + else yps=cmls->fromy+(int)(fixtof(ypermove)*(float)cmls->onpart); + // check if finished horizontal movement + if (((xpermove > 0) && (xps >= targetx)) || + ((xpermove < 0) && (xps <= targetx))) { + cmls->doneflag|=1; + xps = targetx; + // if the Y is almost there too, finish it + // this is new in v2.40 + // removed in 2.70 + /*if (abs(yps - targety) <= 2) + yps = targety;*/ + } + else if (xpermove == 0) + cmls->doneflag|=1; + // check if finished vertical movement + if ((ypermove > 0) & (yps>=targety)) { + cmls->doneflag|=2; + yps = targety; + } + else if ((ypermove < 0) & (yps<=targety)) { + cmls->doneflag|=2; + yps = targety; + } + else if (ypermove == 0) + cmls->doneflag|=2; + + if ((cmls->doneflag & 0x03)==3) { + // this stage is done, go on to the next stage + // signed shorts to ensure that numbers like -20 do not become 65515 + cmls->fromx=(signed short)((cmls->pos[cmls->onstage+1] >> 16) & 0x000ffff); + cmls->fromy=(signed short)(cmls->pos[cmls->onstage+1] & 0x000ffff); + if ((cmls->fromx > 65000) || (cmls->fromy > 65000)) + quit("do_movelist: int to short rounding error"); + + cmls->onstage++; cmls->onpart=-1; cmls->doneflag&=0xf0; + cmls->lastx=-1; + if (cmls->onstage < cmls->numstage) { + xps=cmls->fromx; yps=cmls->fromy; } + if (cmls->onstage>=cmls->numstage-1) { // last stage is just dest pos + cmls->numstage=0; + mlnum[0]=0; + need_to_fix_sprite=1; + } + else need_to_fix_sprite=2; + } + cmls->onpart++; + xx[0]=xps; yy[0]=yps; + return need_to_fix_sprite; + } + + +void update_script_timers() +{ + if (play.gscript_timer > 0) play.gscript_timer--; + for (int aa=0;aa 1) play.script_timers[aa]--; + } +} + +void update_cycling_views() +{ + // update graphics for object if cycling view + for (int aa=0;aanumobj;aa++) { + + RoomObject * obj = &objs[aa]; + + obj->UpdateCyclingView(); + } +} + +void update_shadow_areas() +{ + // shadow areas + int onwalkarea = get_walkable_area_at_character (game.playercharacter); + if (onwalkarea<0) ; + else if (playerchar->flags & CHF_FIXVIEW) ; + else { onwalkarea=thisroom.WalkAreas[onwalkarea].Light; + if (onwalkarea>0) playerchar->view=onwalkarea-1; + else if (thisroom.Options.PlayerView==0) playerchar->view=playerchar->defview; + else playerchar->view=thisroom.Options.PlayerView-1; + } +} + +void update_character_move_and_anim(int &numSheep, int *followingAsSheep) +{ + // move & animate characters + for (int aa=0;aaUpdateMoveAndAnim(aa, chex, numSheep, followingAsSheep); + } +} + +void update_following_exactly_characters(int &numSheep, int *followingAsSheep) +{ + // update location of all following_exactly characters + for (int aa = 0; aa < numSheep; aa++) { + CharacterInfo *chi = &game.chars[followingAsSheep[aa]]; + + chi->UpdateFollowingExactlyCharacter(); + } +} + +void update_overlay_timers() +{ + // update overlay timers + for (size_t i = 0; i < screenover.size();) { + if (screenover[i].timeout > 0) { + screenover[i].timeout--; + if (screenover[i].timeout == 0) + { + remove_screen_overlay_index(i); + continue; + } + } + i++; + } +} + +void update_speech_and_messages() +{ + bool is_voice_playing = false; + if (play.speech_has_voice) + { + AudioChannelsLock lock; + auto *ch = lock.GetChannel(SCHAN_SPEECH); + is_voice_playing = ch && ch->is_playing(); + } + // determine if speech text should be removed + if (play.messagetime>=0) { + play.messagetime--; + // extend life of text if the voice hasn't finished yet + if (play.speech_has_voice && !play.speech_in_post_state) { + if ((is_voice_playing) && (play.fast_forward == 0)) { + if (play.messagetime <= 1) + play.messagetime = 1; + } + else // if the voice has finished, remove the speech + play.messagetime = 0; + } + + if (play.messagetime < 1 && play.speech_display_post_time_ms > 0 && + play.fast_forward == 0) + { + if (!play.speech_in_post_state) + { + play.messagetime = ::lround(play.speech_display_post_time_ms * get_current_fps() / 1000.0f); + } + play.speech_in_post_state = !play.speech_in_post_state; + } + + if (play.messagetime < 1) + { + if (play.fast_forward > 0) + { + remove_screen_overlay(OVER_TEXTMSG); + } + else if (play.cant_skip_speech & SKIP_AUTOTIMER) + { + remove_screen_overlay(OVER_TEXTMSG); + play.SetIgnoreInput(play.ignore_user_input_after_text_timeout_ms); + } + } + } +} + +// update sierra-style speech +void update_sierra_speech() +{ + int voice_pos_ms = -1; + if (play.speech_has_voice) + { + AudioChannelsLock lock; + auto *ch = lock.GetChannel(SCHAN_SPEECH); + voice_pos_ms = ch ? ch->get_pos_ms() : -1; + } + if ((face_talking >= 0) && (play.fast_forward == 0)) + { + int updatedFrame = 0; + + if ((facetalkchar->blinkview > 0) && (facetalkAllowBlink)) { + if (facetalkchar->blinktimer > 0) { + // countdown to playing blink anim + facetalkchar->blinktimer--; + if (facetalkchar->blinktimer == 0) { + facetalkchar->blinkframe = 0; + facetalkchar->blinktimer = -1; + updatedFrame = 2; + } + } + else if (facetalkchar->blinktimer < 0) { + // currently playing blink anim + if (facetalkchar->blinktimer < ( (0 - 6) - views[facetalkchar->blinkview].loops[facetalkBlinkLoop].frames[facetalkchar->blinkframe].speed)) { + // time to advance to next frame + facetalkchar->blinktimer = -1; + facetalkchar->blinkframe++; + updatedFrame = 2; + if (facetalkchar->blinkframe >= views[facetalkchar->blinkview].loops[facetalkBlinkLoop].numFrames) + { + facetalkchar->blinkframe = 0; + facetalkchar->blinktimer = facetalkchar->blinkinterval; + } + } + else + facetalkchar->blinktimer--; + } + + } + + if (curLipLine >= 0) { + // check voice lip sync + if (curLipLinePhoneme >= splipsync[curLipLine].numPhonemes) { + // the lip-sync has finished, so just stay idle + } + else + { + while ((curLipLinePhoneme < splipsync[curLipLine].numPhonemes) && + ((curLipLinePhoneme < 0) || (voice_pos_ms >= splipsync[curLipLine].endtimeoffs[curLipLinePhoneme]))) + { + curLipLinePhoneme ++; + if (curLipLinePhoneme >= splipsync[curLipLine].numPhonemes) + facetalkframe = game.default_lipsync_frame; + else + facetalkframe = splipsync[curLipLine].frame[curLipLinePhoneme]; + + if (facetalkframe >= views[facetalkview].loops[facetalkloop].numFrames) + facetalkframe = 0; + + updatedFrame |= 1; + } + } + } + else if (facetalkwait>0) facetalkwait--; + // don't animate if the speech has finished + else if ((play.messagetime < 1) && (facetalkframe == 0) && + // if play.close_mouth_speech_time = 0, this means animation should play till + // the speech ends; but this should not work in voice mode, and also if the + // speech is in the "post" state + (play.speech_has_voice || play.speech_in_post_state || play.close_mouth_speech_time > 0)) + ; + else { + // Close mouth at end of sentence: if speech has entered the "post" state, + // or if this is a text only mode and close_mouth_speech_time is set + if (play.speech_in_post_state || + (!play.speech_has_voice && + (play.messagetime < play.close_mouth_speech_time) && + (play.close_mouth_speech_time > 0))) { + facetalkframe = 0; + facetalkwait = play.messagetime; + } + else if ((game.options[OPT_LIPSYNCTEXT]) && (facetalkrepeat > 0)) { + // lip-sync speech (and not a thought) + facetalkwait = update_lip_sync (facetalkview, facetalkloop, &facetalkframe); + // It is actually displayed for facetalkwait+1 loops + // (because when it's 1, it gets --'d then wait for next time) + facetalkwait --; + } + else { + // normal non-lip-sync + facetalkframe++; + if ((facetalkframe >= views[facetalkview].loops[facetalkloop].numFrames) || + (!play.speech_has_voice && (play.messagetime < 1) && (play.close_mouth_speech_time > 0))) { + + if ((facetalkframe >= views[facetalkview].loops[facetalkloop].numFrames) && + (views[facetalkview].loops[facetalkloop].RunNextLoop())) + { + facetalkloop++; + } + else + { + facetalkloop = 0; + } + facetalkframe = 0; + if (!facetalkrepeat) + facetalkwait = 999999; + } + if ((facetalkframe != 0) || (facetalkrepeat == 1)) + facetalkwait = views[facetalkview].loops[facetalkloop].frames[facetalkframe].speed + GetCharacterSpeechAnimationDelay(facetalkchar); + } + updatedFrame |= 1; + } + + // is_text_overlay might be 0 if it was only just destroyed this loop + if ((updatedFrame) && (is_text_overlay > 0)) { + + if (updatedFrame & 1) + CheckViewFrame (facetalkview, facetalkloop, facetalkframe); + if (updatedFrame & 2) + CheckViewFrame (facetalkchar->blinkview, facetalkBlinkLoop, facetalkchar->blinkframe); + + int thisPic = views[facetalkview].loops[facetalkloop].frames[facetalkframe].pic; + int view_frame_x = 0; + int view_frame_y = 0; + + if (game.options[OPT_SPEECHTYPE] == 3) { + // QFG4-style fullscreen dialog + if (facetalk_qfg4_override_placement_x) + { + view_frame_x = play.speech_portrait_x; + } + if (facetalk_qfg4_override_placement_y) + { + view_frame_y = play.speech_portrait_y; + } + else + { + view_frame_y = (screenover[face_talking].pic->GetHeight() / 2) - (game.SpriteInfos[thisPic].Height / 2); + } + screenover[face_talking].pic->Clear(0); + } + else { + screenover[face_talking].pic->ClearTransparent(); + } + + Bitmap *frame_pic = screenover[face_talking].pic; + const ViewFrame *face_vf = &views[facetalkview].loops[facetalkloop].frames[facetalkframe]; + bool face_has_alpha = (game.SpriteInfos[face_vf->pic].Flags & SPF_ALPHACHANNEL) != 0; + DrawViewFrame(frame_pic, face_vf, view_frame_x, view_frame_y); + + if ((facetalkchar->blinkview > 0) && (facetalkchar->blinktimer < 0)) { + ViewFrame *blink_vf = &views[facetalkchar->blinkview].loops[facetalkBlinkLoop].frames[facetalkchar->blinkframe]; + face_has_alpha |= (game.SpriteInfos[blink_vf->pic].Flags & SPF_ALPHACHANNEL) != 0; + // draw the blinking sprite on top + DrawViewFrame(frame_pic, blink_vf, view_frame_x, view_frame_y, face_has_alpha); + } + + gfxDriver->UpdateDDBFromBitmap(screenover[face_talking].bmp, screenover[face_talking].pic, face_has_alpha); + } // end if updatedFrame + } +} + +// update_stuff: moves and animates objects, executes repeat scripts, and +// the like. +void update_stuff() { + + our_eip = 20; + + update_script_timers(); + + update_cycling_views(); + + our_eip = 21; + + update_shadow_areas(); + + our_eip = 22; + + int numSheep = 0; + int followingAsSheep[MAX_SHEEP]; + + update_character_move_and_anim(numSheep, followingAsSheep); + + update_following_exactly_characters(numSheep, followingAsSheep); + + our_eip = 23; + + update_overlay_timers(); + + update_speech_and_messages(); + + our_eip = 24; + + update_sierra_speech(); + + our_eip = 25; +} diff --git a/engines/ags/engine/main/update.h b/engines/ags/engine/main/update.h new file mode 100644 index 00000000000..0017787ae45 --- /dev/null +++ b/engines/ags/engine/main/update.h @@ -0,0 +1,26 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MAIN__UPDATE_H +#define __AGS_EE_MAIN__UPDATE_H + +#define MAX_SHEEP 30 // sheep == follower + +int do_movelist_move(short*mlnum,int*xx,int*yy); +void update_stuff(); + +#endif // __AGS_EE_MAIN__UPDATE_H diff --git a/engines/ags/engine/media/audio/ambientsound.cpp b/engines/ags/engine/media/audio/ambientsound.cpp new file mode 100644 index 00000000000..f5b0e7ff0d1 --- /dev/null +++ b/engines/ags/engine/media/audio/ambientsound.cpp @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/audio/ambientsound.h" +#include "media/audio/audio.h" +#include "media/audio/soundclip.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +bool AmbientSound::IsPlaying () { + if (channel <= 0) + return false; + return channel_is_playing(channel); +} + +void AmbientSound::ReadFromFile(Stream *in) +{ + channel = in->ReadInt32(); + x = in->ReadInt32(); + y = in->ReadInt32(); + vol = in->ReadInt32(); + num = in->ReadInt32(); + maxdist = in->ReadInt32(); +} + +void AmbientSound::WriteToFile(Stream *out) +{ + out->WriteInt32(channel); + out->WriteInt32(x); + out->WriteInt32(y); + out->WriteInt32(vol); + out->WriteInt32(num); + out->WriteInt32(maxdist); +} diff --git a/engines/ags/engine/media/audio/ambientsound.h b/engines/ags/engine/media/audio/ambientsound.h new file mode 100644 index 00000000000..7117d364794 --- /dev/null +++ b/engines/ags/engine/media/audio/ambientsound.h @@ -0,0 +1,37 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_AMBIENTSOUND_H +#define __AC_AMBIENTSOUND_H + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +#define AMBIENCE_FULL_DIST 25 + +struct AmbientSound { + int channel; // channel number, 1 upwards + int x,y; + int vol; + int num; // sound number, eg. 3 = sound3.wav + int maxdist; + + bool IsPlaying(); + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); +}; + +#endif // __AC_AMBIENTSOUND_H diff --git a/engines/ags/engine/media/audio/audio.cpp b/engines/ags/engine/media/audio/audio.cpp new file mode 100644 index 00000000000..2a0a7d073c7 --- /dev/null +++ b/engines/ags/engine/media/audio/audio.cpp @@ -0,0 +1,1228 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include + +#include "core/platform.h" +#include "util/wgt2allg.h" +#include "media/audio/audio.h" +#include "ac/audiocliptype.h" +#include "ac/gamesetupstruct.h" +#include "ac/dynobj/cc_audioclip.h" +#include "ac/dynobj/cc_audiochannel.h" +#include "ac/gamestate.h" +#include "script/script_runtime.h" +#include "ac/audiochannel.h" +#include "ac/audioclip.h" +#include "ac/gamesetup.h" +#include "ac/path_helper.h" +#include "media/audio/sound.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "ac/common.h" +#include "ac/file.h" +#include "ac/global_audio.h" +#include +#include "util/stream.h" +#include "core/assetmanager.h" +#include "ac/timer.h" +#include "main/game_run.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +//----------------------- +//sound channel management; all access goes through here, which can't be done without a lock + +static std::array _channels; +AGS::Engine::Mutex AudioChannelsLock::s_mutex; + +SOUNDCLIP *AudioChannelsLock::GetChannel(int index) +{ + return _channels[index]; +} + +SOUNDCLIP *AudioChannelsLock::GetChannelIfPlaying(int index) +{ + auto *ch = _channels[index]; + return (ch != nullptr && ch->is_playing()) ? ch : nullptr; +} + +SOUNDCLIP *AudioChannelsLock::SetChannel(int index, SOUNDCLIP* ch) +{ + // TODO: store clips in smart pointers + if (_channels[index] == ch) + Debug::Printf(kDbgMsg_Warn, "WARNING: channel %d - same clip assigned", index); + else if (_channels[index] != nullptr && ch != nullptr) + Debug::Printf(kDbgMsg_Warn, "WARNING: channel %d - clip overwritten", index); + _channels[index] = ch; + return ch; +} + +SOUNDCLIP *AudioChannelsLock::MoveChannel(int to, int from) +{ + auto from_ch = _channels[from]; + _channels[from] = nullptr; + return SetChannel(to, from_ch); +} + +//----------------------- +// Channel helpers + +bool channel_has_clip(int chanid) +{ + AudioChannelsLock lock; + return lock.GetChannel(chanid) != nullptr; +} + +bool channel_is_playing(int chanid) +{ + AudioChannelsLock lock; + return lock.GetChannelIfPlaying(chanid) != nullptr; +} + +void set_clip_to_channel(int chanid, SOUNDCLIP *clip) +{ + AudioChannelsLock lock; + lock.SetChannel(chanid, clip); +} +//----------------------- + +volatile bool _audio_doing_crossfade; + +extern GameSetupStruct game; +extern GameSetup usetup; +extern GameState play; +extern RoomStruct thisroom; +extern CharacterInfo*playerchar; + +extern volatile int switching_away_from_game; + +#if ! AGS_PLATFORM_OS_IOS && ! AGS_PLATFORM_OS_ANDROID +volatile int psp_audio_multithreaded = 0; +#endif + +ScriptAudioChannel scrAudioChannel[MAX_SOUND_CHANNELS + 1]; +char acaudio_buffer[256]; +int reserved_channel_count = 0; + +AGS::Engine::Thread audioThread; + +void calculate_reserved_channel_count() +{ + int reservedChannels = 0; + for (size_t i = 0; i < game.audioClipTypes.size(); i++) + { + reservedChannels += game.audioClipTypes[i].reservedChannels; + } + reserved_channel_count = reservedChannels; +} + +void update_clip_default_volume(ScriptAudioClip *audioClip) +{ + if (play.default_audio_type_volumes[audioClip->type] >= 0) + { + audioClip->defaultVolume = play.default_audio_type_volumes[audioClip->type]; + } +} + +void start_fading_in_new_track_if_applicable(int fadeInChannel, ScriptAudioClip *newSound) +{ + int crossfadeSpeed = game.audioClipTypes[newSound->type].crossfadeSpeed; + if (crossfadeSpeed > 0) + { + update_clip_default_volume(newSound); + play.crossfade_in_volume_per_step = crossfadeSpeed; + play.crossfade_final_volume_in = newSound->defaultVolume; + play.crossfading_in_channel = fadeInChannel; + } +} + +static void move_track_to_crossfade_channel(int currentChannel, int crossfadeSpeed, int fadeInChannel, ScriptAudioClip *newSound) +{ + AudioChannelsLock lock; + stop_and_destroy_channel(SPECIAL_CROSSFADE_CHANNEL); + auto *cfade_clip = lock.MoveChannel(SPECIAL_CROSSFADE_CHANNEL, currentChannel); + if (!cfade_clip) + return; + + play.crossfading_out_channel = SPECIAL_CROSSFADE_CHANNEL; + play.crossfade_step = 0; + play.crossfade_initial_volume_out = cfade_clip->get_volume(); + play.crossfade_out_volume_per_step = crossfadeSpeed; + + play.crossfading_in_channel = fadeInChannel; + if (newSound != nullptr) + { + start_fading_in_new_track_if_applicable(fadeInChannel, newSound); + } +} + +void stop_or_fade_out_channel(int fadeOutChannel, int fadeInChannel, ScriptAudioClip *newSound) +{ + ScriptAudioClip *sourceClip = AudioChannel_GetPlayingClip(&scrAudioChannel[fadeOutChannel]); + if ((sourceClip != nullptr) && (game.audioClipTypes[sourceClip->type].crossfadeSpeed > 0)) + { + move_track_to_crossfade_channel(fadeOutChannel, game.audioClipTypes[sourceClip->type].crossfadeSpeed, fadeInChannel, newSound); + } + else + { + stop_and_destroy_channel(fadeOutChannel); + } +} + +static int find_free_audio_channel(ScriptAudioClip *clip, int priority, bool interruptEqualPriority) +{ + AudioChannelsLock lock; + + int lowestPrioritySoFar = 9999999; + int lowestPriorityID = -1; + int channelToUse = -1; + + if (!interruptEqualPriority) + priority--; + + int startAtChannel = reserved_channel_count; + int endBeforeChannel = MAX_SOUND_CHANNELS; + + if (game.audioClipTypes[clip->type].reservedChannels > 0) + { + startAtChannel = 0; + for (int i = 0; i < clip->type; i++) + { + startAtChannel += game.audioClipTypes[i].reservedChannels; + } + endBeforeChannel = startAtChannel + game.audioClipTypes[clip->type].reservedChannels; + } + + for (int i = startAtChannel; i < endBeforeChannel; i++) + { + auto* ch = lock.GetChannelIfPlaying(i); + if (ch == nullptr) + { + channelToUse = i; + stop_and_destroy_channel(i); + break; + } + if ((ch->priority < lowestPrioritySoFar) && + (ch->sourceClipType == clip->type)) + { + lowestPrioritySoFar = ch->priority; + lowestPriorityID = i; + } + } + + if ((channelToUse < 0) && (lowestPriorityID >= 0) && + (lowestPrioritySoFar <= priority)) + { + stop_or_fade_out_channel(lowestPriorityID, lowestPriorityID, clip); + channelToUse = lowestPriorityID; + } + else if ((channelToUse >= 0) && (play.crossfading_in_channel < 1)) + { + start_fading_in_new_track_if_applicable(channelToUse, clip); + } + return channelToUse; +} + +bool is_audiotype_allowed_to_play(AudioFileType type) +{ + return (type == eAudioFileMIDI && usetup.midicard != MIDI_NONE) || + (type != eAudioFileMIDI && usetup.digicard != DIGI_NONE); +} + +SOUNDCLIP *load_sound_clip(ScriptAudioClip *audioClip, bool repeat) +{ + if (!is_audiotype_allowed_to_play((AudioFileType)audioClip->fileType)) + { + return nullptr; + } + + update_clip_default_volume(audioClip); + + SOUNDCLIP *soundClip = nullptr; + AssetPath asset_name = get_audio_clip_assetpath(audioClip->bundlingType, audioClip->fileName); + switch (audioClip->fileType) + { + case eAudioFileOGG: + soundClip = my_load_static_ogg(asset_name, audioClip->defaultVolume, repeat); + break; + case eAudioFileMP3: + soundClip = my_load_static_mp3(asset_name, audioClip->defaultVolume, repeat); + break; + case eAudioFileWAV: + case eAudioFileVOC: + soundClip = my_load_wave(asset_name, audioClip->defaultVolume, repeat); + break; + case eAudioFileMIDI: + soundClip = my_load_midi(asset_name, repeat); + break; + case eAudioFileMOD: +#ifndef PSP_NO_MOD_PLAYBACK + soundClip = my_load_mod(asset_name, repeat); +#else + soundClip = NULL; +#endif + break; + default: + quitprintf("AudioClip.Play: invalid audio file type encountered: %d", audioClip->fileType); + } + if (soundClip != nullptr) + { + soundClip->set_volume_percent(audioClip->defaultVolume); + soundClip->sourceClip = audioClip; + soundClip->sourceClipType = audioClip->type; + } + return soundClip; +} + +static void audio_update_polled_stuff() +{ + /////////////////////////////////////////////////////////////////////////// + // Do crossfade + play.crossfade_step++; + + AudioChannelsLock lock; + + if (play.crossfading_out_channel > 0 && !lock.GetChannelIfPlaying(play.crossfading_out_channel)) + play.crossfading_out_channel = 0; + + if (play.crossfading_out_channel > 0) + { + SOUNDCLIP* ch = lock.GetChannel(play.crossfading_out_channel); + int newVolume = ch ? ch->get_volume() - play.crossfade_out_volume_per_step : 0; + if (newVolume > 0) + { + AudioChannel_SetVolume(&scrAudioChannel[play.crossfading_out_channel], newVolume); + } + else + { + stop_and_destroy_channel(play.crossfading_out_channel); + play.crossfading_out_channel = 0; + } + } + + if (play.crossfading_in_channel > 0 && !lock.GetChannelIfPlaying(play.crossfading_in_channel)) + play.crossfading_in_channel = 0; + + if (play.crossfading_in_channel > 0) + { + SOUNDCLIP* ch = lock.GetChannel(play.crossfading_in_channel); + int newVolume = ch ? ch->get_volume() + play.crossfade_in_volume_per_step : 0; + if (newVolume > play.crossfade_final_volume_in) + { + newVolume = play.crossfade_final_volume_in; + } + + AudioChannel_SetVolume(&scrAudioChannel[play.crossfading_in_channel], newVolume); + + if (newVolume >= play.crossfade_final_volume_in) + { + play.crossfading_in_channel = 0; + } + } + + /////////////////////////////////////////////////////////////////////////// + // Do audio queue + if (play.new_music_queue_size > 0) + { + for (int i = 0; i < play.new_music_queue_size; i++) + { + ScriptAudioClip *clip = &game.audioClips[play.new_music_queue[i].audioClipIndex]; + int channel = find_free_audio_channel(clip, clip->defaultPriority, false); + if (channel >= 0) + { + QueuedAudioItem itemToPlay = play.new_music_queue[i]; + + play.new_music_queue_size--; + for (int j = i; j < play.new_music_queue_size; j++) + { + play.new_music_queue[j] = play.new_music_queue[j + 1]; + } + + play_audio_clip_on_channel(channel, clip, itemToPlay.priority, itemToPlay.repeat, 0, itemToPlay.cachedClip); + i--; + } + } + } + + /////////////////////////////////////////////////////////////////////////// + // Do non-blocking voice speech + // NOTE: there's only one speech channel, therefore it's either blocking + // or non-blocking at any given time. If it's changed, we'd need to keep + // record of every channel, or keep a count of active channels. + if (play.IsNonBlockingVoiceSpeech()) + { + if (!channel_is_playing(SCHAN_SPEECH)) + { + stop_voice_nonblocking(); + } + } +} + +// Applies a volume drop modifier to the clip, in accordance to its audio type +static void apply_volume_drop_to_clip(SOUNDCLIP *clip) +{ + int audiotype = clip->sourceClipType; + clip->apply_volume_modifier(-(game.audioClipTypes[audiotype].volume_reduction_while_speech_playing * 255 / 100)); +} + +static void queue_audio_clip_to_play(ScriptAudioClip *clip, int priority, int repeat) +{ + if (play.new_music_queue_size >= MAX_QUEUED_MUSIC) { + debug_script_log("Too many queued music, cannot add %s", clip->scriptName.GetCStr()); + return; + } + + SOUNDCLIP *cachedClip = load_sound_clip(clip, (repeat != 0)); + if (cachedClip != nullptr) + { + play.new_music_queue[play.new_music_queue_size].audioClipIndex = clip->id; + play.new_music_queue[play.new_music_queue_size].priority = priority; + play.new_music_queue[play.new_music_queue_size].repeat = (repeat != 0); + play.new_music_queue[play.new_music_queue_size].cachedClip = cachedClip; + play.new_music_queue_size++; + } + + update_polled_mp3(); +} + +ScriptAudioChannel* play_audio_clip_on_channel(int channel, ScriptAudioClip *clip, int priority, int repeat, int fromOffset, SOUNDCLIP *soundfx) +{ + if (soundfx == nullptr) + { + soundfx = load_sound_clip(clip, (repeat) ? true : false); + } + if (soundfx == nullptr) + { + debug_script_log("AudioClip.Play: unable to load sound file"); + if (play.crossfading_in_channel == channel) + { + play.crossfading_in_channel = 0; + } + return nullptr; + } + soundfx->priority = priority; + + if (play.crossfading_in_channel == channel) + { + soundfx->set_volume_percent(0); + } + + // Mute the audio clip if fast-forwarding the cutscene + if (play.fast_forward) + { + soundfx->set_mute(true); + + // CHECKME!! + // [IKM] According to the 3.2.1 logic the clip will restore + // its value after cutscene, but only if originalVolAsPercentage + // is not zeroed. Something I am not sure about: why does it + // disable the clip under condition that there's more than one + // channel for this audio type? It does not even check if + // anything of this type is currently playing. + if (game.audioClipTypes[clip->type].reservedChannels != 1) + soundfx->set_volume_percent(0); + } + + if (soundfx->play_from(fromOffset) == 0) + { + // not assigned to a channel, so clean up manually. + soundfx->destroy(); + delete soundfx; + soundfx = nullptr; + debug_script_log("AudioClip.Play: failed to play sound file"); + return nullptr; + } + + // Apply volume drop if any speech voice-over is currently playing + // NOTE: there is a confusing logic in sound clip classes, that they do not use + // any modifiers when begin playing, therefore we must apply this only after + // playback was started. + if (!play.fast_forward && play.speech_has_voice) + apply_volume_drop_to_clip(soundfx); + + set_clip_to_channel(channel, soundfx); + return &scrAudioChannel[channel]; +} + +void remove_clips_of_type_from_queue(int audioType) +{ + int aa; + for (aa = 0; aa < play.new_music_queue_size; aa++) + { + ScriptAudioClip *clip = &game.audioClips[play.new_music_queue[aa].audioClipIndex]; + if (clip->type == audioType) + { + play.new_music_queue_size--; + for (int bb = aa; bb < play.new_music_queue_size; bb++) + play.new_music_queue[bb] = play.new_music_queue[bb + 1]; + aa--; + } + } +} + +void update_queued_clips_volume(int audioType, int new_vol) +{ + for (int i = 0; i < play.new_music_queue_size; ++i) + { + // NOTE: if clip is uncached, the volume will be set from defaults when it is loaded + SOUNDCLIP *sndclip = play.new_music_queue[i].cachedClip; + if (sndclip) + { + ScriptAudioClip *clip = &game.audioClips[play.new_music_queue[i].audioClipIndex]; + if (clip->type == audioType) + sndclip->set_volume_percent(new_vol); + } + } +} + +ScriptAudioChannel* play_audio_clip(ScriptAudioClip *clip, int priority, int repeat, int fromOffset, bool queueIfNoChannel) +{ + if (!queueIfNoChannel) + remove_clips_of_type_from_queue(clip->type); + + if (priority == SCR_NO_VALUE) + priority = clip->defaultPriority; + if (repeat == SCR_NO_VALUE) + repeat = clip->defaultRepeat; + + int channel = find_free_audio_channel(clip, priority, !queueIfNoChannel); + if (channel < 0) + { + if (queueIfNoChannel) + queue_audio_clip_to_play(clip, priority, repeat); + else + debug_script_log("AudioClip.Play: no channels available to interrupt PRI:%d TYPE:%d", priority, clip->type); + + return nullptr; + } + + return play_audio_clip_on_channel(channel, clip, priority, repeat, fromOffset); +} + +ScriptAudioChannel* play_audio_clip_by_index(int audioClipIndex) +{ + if ((audioClipIndex >= 0) && ((size_t)audioClipIndex < game.audioClips.size())) + return AudioClip_Play(&game.audioClips[audioClipIndex], SCR_NO_VALUE, SCR_NO_VALUE); + else + return nullptr; +} + +void stop_and_destroy_channel_ex(int chid, bool resetLegacyMusicSettings) +{ + if ((chid < 0) || (chid > MAX_SOUND_CHANNELS)) + quit("!StopChannel: invalid channel ID"); + + AudioChannelsLock lock; + SOUNDCLIP* ch = lock.GetChannel(chid); + + if (ch != nullptr) { + ch->destroy(); + delete ch; + lock.SetChannel(chid, nullptr); + ch = nullptr; + } + + if (play.crossfading_in_channel == chid) + play.crossfading_in_channel = 0; + if (play.crossfading_out_channel == chid) + play.crossfading_out_channel = 0; + // don't update 'crossFading' here as it is updated in all the cross-fading functions. + + // destroyed an ambient sound channel + if (ambient[chid].channel > 0) + ambient[chid].channel = 0; + + if ((chid == SCHAN_MUSIC) && (resetLegacyMusicSettings)) + { + play.cur_music_number = -1; + current_music_type = 0; + } +} + +void stop_and_destroy_channel(int chid) +{ + stop_and_destroy_channel_ex(chid, true); +} + + + +// ***** BACKWARDS COMPATIBILITY WITH OLD AUDIO SYSTEM ***** // + +int get_old_style_number_for_sound(int sound_number) +{ + int audio_clip_id = 0; + + if (game.IsLegacyAudioSystem()) + { + // No sound assigned. + if (sound_number < 1) + return 0; + + // Sound number is not yet updated to audio clip id. + if (sound_number <= 0x10000000) + return sound_number; + + // Remove audio clip id flag. + audio_clip_id = sound_number - 0x10000000; + } + else + audio_clip_id = sound_number; + + if (audio_clip_id >= 0) + { + int old_style_number = 0; + if (sscanf(game.audioClips[audio_clip_id].scriptName.GetCStr(), "aSound%d", &old_style_number) == 1) + return old_style_number; + } + return 0; +} + +SOUNDCLIP *load_sound_clip_from_old_style_number(bool isMusic, int indexNumber, bool repeat) +{ + ScriptAudioClip* audioClip = GetAudioClipForOldStyleNumber(game, isMusic, indexNumber); + + if (audioClip != nullptr) + { + return load_sound_clip(audioClip, repeat); + } + + return nullptr; +} + +//============================================================================= + +void force_audiostream_include() { + // This should never happen, but the call is here to make it + // link the audiostream libraries + stop_audio_stream(nullptr); +} + +// TODO: double check that ambient sounds array actually needs +1 +std::array ambient; + +int get_volume_adjusted_for_distance(int volume, int sndX, int sndY, int sndMaxDist) +{ + int distx = playerchar->x - sndX; + int disty = playerchar->y - sndY; + // it uses Allegro's "fix" sqrt without the :: + int dist = (int)::sqrt((double)(distx*distx + disty*disty)); + + // if they're quite close, full volume + int wantvol = volume; + + if (dist >= AMBIENCE_FULL_DIST) + { + // get the relative volume + wantvol = ((dist - AMBIENCE_FULL_DIST) * volume) / sndMaxDist; + // closer is louder + wantvol = volume - wantvol; + } + + return wantvol; +} + +void update_directional_sound_vol() +{ + AudioChannelsLock lock; + + for (int chnum = 1; chnum < MAX_SOUND_CHANNELS; chnum++) + { + auto* ch = lock.GetChannelIfPlaying(chnum); + if ((ch != nullptr) && (ch->xSource >= 0)) + { + ch->apply_directional_modifier( + get_volume_adjusted_for_distance(ch->vol, + ch->xSource, + ch->ySource, + ch->maximumPossibleDistanceAway) - + ch->vol); + } + } +} + +void update_ambient_sound_vol () +{ + AudioChannelsLock lock; + + for (int chan = 1; chan < MAX_SOUND_CHANNELS; chan++) { + + AmbientSound *thisSound = &ambient[chan]; + + if (thisSound->channel == 0) + continue; + + int sourceVolume = thisSound->vol; + + if (play.speech_has_voice) { + // Negative value means set exactly; positive means drop that amount + if (play.speech_music_drop < 0) + sourceVolume = -play.speech_music_drop; + else + sourceVolume -= play.speech_music_drop; + + if (sourceVolume < 0) + sourceVolume = 0; + if (sourceVolume > 255) + sourceVolume = 255; + } + + // Adjust ambient volume so it maxes out at overall sound volume + int ambientvol = (sourceVolume * play.sound_volume) / 255; + + int wantvol; + + if ((thisSound->x == 0) && (thisSound->y == 0)) { + wantvol = ambientvol; + } + else { + wantvol = get_volume_adjusted_for_distance(ambientvol, thisSound->x, thisSound->y, thisSound->maxdist); + } + + auto *ch = lock.GetChannelIfPlaying(thisSound->channel); + if (ch) + ch->set_volume(wantvol); + } +} + +SOUNDCLIP *load_sound_and_play(ScriptAudioClip *aclip, bool repeat) +{ + SOUNDCLIP *soundfx = load_sound_clip(aclip, repeat); + if (!soundfx) { return nullptr; } + + if (soundfx->play() == 0) { + // not assigned to a channel, so clean up manually. + soundfx->destroy(); + delete soundfx; + return nullptr; + } + + return soundfx; +} + +void stop_all_sound_and_music() +{ + int a; + stopmusic(); + stop_voice_nonblocking(); + // make sure it doesn't start crossfading when it comes back + crossFading = 0; + // any ambient sound will be aborted + for (a = 0; a <= MAX_SOUND_CHANNELS; a++) + stop_and_destroy_channel(a); +} + +void shutdown_sound() +{ + stop_all_sound_and_music(); + +#ifndef PSP_NO_MOD_PLAYBACK + if (usetup.mod_player) + remove_mod_player(); +#endif + remove_sound(); +} + +// the sound will only be played if there is a free channel or +// it has a priority >= an existing sound to override +static int play_sound_priority (int val1, int priority) { + int lowest_pri = 9999, lowest_pri_id = -1; + + AudioChannelsLock lock; + + // find a free channel to play it on + for (int i = SCHAN_NORMAL; i < MAX_SOUND_CHANNELS; i++) { + auto* ch = lock.GetChannelIfPlaying(i); + if (val1 < 0) { + // Playing sound -1 means iterate through and stop all sound + if (ch) + stop_and_destroy_channel (i); + } + else if (ch == nullptr || !ch->is_playing()) { + // PlaySoundEx will destroy the previous channel value. + const int usechan = PlaySoundEx(val1, i); + if (usechan >= 0) + { // channel will hold a different clip here + assert(usechan == i); + auto *ch = lock.GetChannel(usechan); + if (ch) + ch->priority = priority; + } + return usechan; + } + else if (ch->priority < lowest_pri) { + lowest_pri = ch->priority; + lowest_pri_id = i; + } + + } + if (val1 < 0) + return -1; + + // no free channels, see if we have a high enough priority + // to override one + if (priority >= lowest_pri) { + const int usechan = PlaySoundEx(val1, lowest_pri_id); + if (usechan >= 0) { + assert(usechan == lowest_pri_id); + auto *ch = lock.GetChannel(usechan); + if (ch) + ch->priority = priority; + return usechan; + } + } + + return -1; +} + +int play_sound(int val1) { + return play_sound_priority(val1, 10); +} + + +//============================================================================= + + +// This is an indicator of a music played by an old audio system +// (to distinguish from the new system API) +int current_music_type = 0; +// crossFading is >0 (channel number of new track), or -1 (old +// track fading out, no new track) +int crossFading = 0, crossFadeVolumePerStep = 0, crossFadeStep = 0; +int crossFadeVolumeAtStart = 0; +SOUNDCLIP *cachedQueuedMusic = nullptr; + +//============================================================================= +// Music update is scheduled when the voice speech stops; +// we do a small delay before reverting any volume adjustments +static bool music_update_scheduled = false; +static auto music_update_at = AGS_Clock::now(); + +void cancel_scheduled_music_update() { + music_update_scheduled = false; +} + +void schedule_music_update_at(AGS_Clock::time_point at) { + music_update_scheduled = true; + music_update_at = at; +} + +void postpone_scheduled_music_update_by(std::chrono::milliseconds duration) { + if (!music_update_scheduled) { return; } + music_update_at += duration; +} + +void process_scheduled_music_update() { + if (!music_update_scheduled) { return; } + if (music_update_at > AGS_Clock::now()) { return; } + cancel_scheduled_music_update(); + update_music_volume(); + apply_volume_drop_modifier(false); + update_ambient_sound_vol(); +} +// end scheduled music update functions +//============================================================================= + +void clear_music_cache() { + + if (cachedQueuedMusic != nullptr) { + cachedQueuedMusic->destroy(); + delete cachedQueuedMusic; + cachedQueuedMusic = nullptr; + } + +} + +static void play_new_music(int mnum, SOUNDCLIP *music); + +void play_next_queued() { + // check if there's a queued one to play + if (play.music_queue_size > 0) { + + int tuneToPlay = play.music_queue[0]; + + if (tuneToPlay >= QUEUED_MUSIC_REPEAT) { + // Loop it! + play.music_repeat++; + play_new_music(tuneToPlay - QUEUED_MUSIC_REPEAT, cachedQueuedMusic); + play.music_repeat--; + } + else { + // Don't loop it! + int repeatWas = play.music_repeat; + play.music_repeat = 0; + play_new_music(tuneToPlay, cachedQueuedMusic); + play.music_repeat = repeatWas; + } + + // don't free the memory, as it has been transferred onto the + // main music channel + cachedQueuedMusic = nullptr; + + play.music_queue_size--; + for (int i = 0; i < play.music_queue_size; i++) + play.music_queue[i] = play.music_queue[i + 1]; + + if (play.music_queue_size > 0) + cachedQueuedMusic = load_music_from_disk(play.music_queue[0], 0); + } + +} + +int calculate_max_volume() { + // quieter so that sounds can be heard better + int newvol=play.music_master_volume + ((int)thisroom.Options.MusicVolume) * LegacyRoomVolumeFactor; + if (newvol>255) newvol=255; + if (newvol<0) newvol=0; + + if (play.fast_forward) + newvol = 0; + + return newvol; +} + +// add/remove the volume drop to the audio channels while speech is playing +void apply_volume_drop_modifier(bool applyModifier) +{ + AudioChannelsLock lock; + + for (int i = 0; i < MAX_SOUND_CHANNELS; i++) + { + auto* ch = lock.GetChannelIfPlaying(i); + if (ch && ch->sourceClip != nullptr) + { + if (applyModifier) + apply_volume_drop_to_clip(ch); + else + ch->apply_volume_modifier(0); // reset modifier + } + } +} + +// Checks if speech voice-over is currently playing, and reapply volume drop to all other active clips +void update_volume_drop_if_voiceover() +{ + apply_volume_drop_modifier(play.speech_has_voice); +} + +extern volatile char want_exit; + +void update_mp3_thread() +{ + if (switching_away_from_game) { return; } + + AudioChannelsLock lock; + + for(int i = 0; i <= MAX_SOUND_CHANNELS; ++i) + { + auto* ch = lock.GetChannel(i); + if (ch) + ch->poll(); + } +} + +//this is called at various points to give streaming logic a chance to update +//it seems those calls have been littered around and points where it ameliorated skipping +//a better solution would be to forcibly thread the streaming logic +void update_polled_mp3() +{ + if (psp_audio_multithreaded) { return; } + update_mp3_thread(); +} + +// Update the music, and advance the crossfade on a step +// (this should only be called once per game loop) +void update_audio_system_on_game_loop () +{ + update_polled_stuff_if_runtime (); + + AudioChannelsLock lock; + + process_scheduled_music_update(); + + _audio_doing_crossfade = true; + + audio_update_polled_stuff(); + + if (crossFading) { + crossFadeStep++; + update_music_volume(); + } + + // Check if the current music has finished playing + if ((play.cur_music_number >= 0) && (play.fast_forward == 0)) { + if (IsMusicPlaying() == 0) { + // The current music has finished + play.cur_music_number = -1; + play_next_queued(); + } + else if ((game.options[OPT_CROSSFADEMUSIC] > 0) && + (play.music_queue_size > 0) && (!crossFading)) { + // want to crossfade, and new tune in the queue + auto *ch = lock.GetChannel(SCHAN_MUSIC); + if (ch) { + int curpos = ch->get_pos_ms(); + int muslen = ch->get_length_ms(); + if ((curpos > 0) && (muslen > 0)) { + // we want to crossfade, and we know how far through + // the tune we are + int takesSteps = calculate_max_volume() / game.options[OPT_CROSSFADEMUSIC]; + int takesMs = ::lround(takesSteps * 1000.0f / get_current_fps()); + if (curpos >= muslen - takesMs) + play_next_queued(); + } + } + } + } + + _audio_doing_crossfade = false; + +} + +void stopmusic() +{ + AudioChannelsLock lock; + + if (crossFading > 0) { + // stop in the middle of a new track fading in + // Abort the new track, and let the old one finish fading out + stop_and_destroy_channel (crossFading); + crossFading = -1; + } + else if (crossFading < 0) { + // the music is already fading out + if (game.options[OPT_CROSSFADEMUSIC] <= 0) { + // If they have since disabled crossfading, stop the fadeout + stop_and_destroy_channel(SCHAN_MUSIC); + crossFading = 0; + crossFadeStep = 0; + update_music_volume(); + } + } + else if ((game.options[OPT_CROSSFADEMUSIC] > 0) + && (lock.GetChannelIfPlaying(SCHAN_MUSIC) != nullptr) + && (current_music_type != 0) + && (current_music_type != MUS_MIDI) + && (current_music_type != MUS_MOD)) { + + crossFading = -1; + crossFadeStep = 0; + crossFadeVolumePerStep = game.options[OPT_CROSSFADEMUSIC]; + crossFadeVolumeAtStart = calculate_max_volume(); + } + else + stop_and_destroy_channel (SCHAN_MUSIC); + + play.cur_music_number = -1; + current_music_type = 0; +} + +void update_music_volume() +{ + AudioChannelsLock lock; + + if ((current_music_type) || (crossFading < 0)) + { + // targetVol is the maximum volume we're fading in to + // newvol is the starting volume that we faded out from + int targetVol = calculate_max_volume(); + int newvol; + if (crossFading) + newvol = crossFadeVolumeAtStart; + else + newvol = targetVol; + + // fading out old track, target volume is silence + if (crossFading < 0) + targetVol = 0; + + if (crossFading) { + int curvol = crossFadeVolumePerStep * crossFadeStep; + + if ((curvol > targetVol) && (curvol > newvol)) { + // it has fully faded to the new track + newvol = targetVol; + stop_and_destroy_channel_ex(SCHAN_MUSIC, false); + if (crossFading > 0) { + lock.MoveChannel(SCHAN_MUSIC, crossFading); + } + crossFading = 0; + } + else { + if (crossFading > 0) + { + auto *ch = lock.GetChannel(crossFading); + if (ch) + ch->set_volume((curvol > targetVol) ? targetVol : curvol); + } + + newvol -= curvol; + if (newvol < 0) + newvol = 0; + } + } + auto *ch = lock.GetChannel(SCHAN_MUSIC); + if (ch) + ch->set_volume(newvol); + } +} + +// Ensures crossfader is stable after loading (or failing to load) +// new music +void post_new_music_check (int newchannel) +{ + AudioChannelsLock lock; + if ((crossFading > 0) && (lock.GetChannel(crossFading) == nullptr)) { + crossFading = 0; + // Was fading out but then they played invalid music, continue to fade out + if (lock.GetChannel(SCHAN_MUSIC) != nullptr) + crossFading = -1; + } + +} + +int prepare_for_new_music () +{ + AudioChannelsLock lock; + + int useChannel = SCHAN_MUSIC; + + if ((game.options[OPT_CROSSFADEMUSIC] > 0) + && (lock.GetChannelIfPlaying(SCHAN_MUSIC) != nullptr) + && (current_music_type != MUS_MIDI) + && (current_music_type != MUS_MOD)) { + + if (crossFading > 0) { + // It's still crossfading to the previous track + stop_and_destroy_channel_ex(SCHAN_MUSIC, false); + lock.MoveChannel(SCHAN_MUSIC, crossFading); + crossFading = 0; + update_music_volume(); + } + else if (crossFading < 0) { + // an old track is still fading out, no new music yet + // Do nothing, and keep the current crossfade step + } + else { + // start crossfading + crossFadeStep = 0; + crossFadeVolumePerStep = game.options[OPT_CROSSFADEMUSIC]; + crossFadeVolumeAtStart = calculate_max_volume(); + } + useChannel = SPECIAL_CROSSFADE_CHANNEL; + crossFading = useChannel; + } + else { + // crossfading is now turned off + stopmusic(); + // ensure that any traces of old tunes fading are eliminated + // (otherwise the new track will be faded out) + crossFading = 0; + } + + // Just make sure, because it will be overwritten in a sec + if (lock.GetChannel(useChannel) != nullptr) + stop_and_destroy_channel (useChannel); + + return useChannel; +} + +ScriptAudioClip *get_audio_clip_for_music(int mnum) +{ + if (mnum >= QUEUED_MUSIC_REPEAT) + mnum -= QUEUED_MUSIC_REPEAT; + return GetAudioClipForOldStyleNumber(game, true, mnum); +} + +SOUNDCLIP *load_music_from_disk(int mnum, bool doRepeat) { + + if (mnum >= QUEUED_MUSIC_REPEAT) { + mnum -= QUEUED_MUSIC_REPEAT; + doRepeat = true; + } + + SOUNDCLIP *loaded = load_sound_clip_from_old_style_number(true, mnum, doRepeat); + + if ((loaded == nullptr) && (mnum > 0)) + { + debug_script_warn("Music %d not found",mnum); + debug_script_log("FAILED to load music %d", mnum); + } + + return loaded; +} + +static void play_new_music(int mnum, SOUNDCLIP *music) +{ + if (debug_flags & DBG_NOMUSIC) + return; + + if ((play.cur_music_number == mnum) && (music == nullptr)) { + debug_script_log("PlayMusic %d but already playing", mnum); + return; // don't play the music if it's already playing + } + + ScriptAudioClip *aclip = get_audio_clip_for_music(mnum); + if (aclip && !is_audiotype_allowed_to_play((AudioFileType)aclip->fileType)) + return; + + int useChannel = SCHAN_MUSIC; + debug_script_log("Playing music %d", mnum); + + if (mnum<0) { + stopmusic(); + return; + } + + if (play.fast_forward) { + // while skipping cutscene, don't change the music + play.end_cutscene_music = mnum; + return; + } + + useChannel = prepare_for_new_music(); + play.cur_music_number = mnum; + current_music_type = 0; + + play.current_music_repeating = play.music_repeat; + // now that all the previous music is unloaded, load in the new one + + SOUNDCLIP *new_clip; + if (music != nullptr) + new_clip = music; + else + new_clip = load_music_from_disk(mnum, (play.music_repeat > 0)); + + AudioChannelsLock lock; + auto* ch = lock.SetChannel(useChannel, new_clip); + if (ch != nullptr) { + if (!ch->play()) { + // previous behavior was to set channel[] to null on error, so continue to do that here. + ch->destroy(); + delete ch; + ch = nullptr; + lock.SetChannel(useChannel, nullptr); + } else + current_music_type = ch->get_sound_type(); + } + + post_new_music_check(useChannel); + update_music_volume(); +} + +void newmusic(int mnum) +{ + play_new_music(mnum, nullptr); +} diff --git a/engines/ags/engine/media/audio/audio.h b/engines/ags/engine/media/audio/audio.h new file mode 100644 index 00000000000..05600c9d53d --- /dev/null +++ b/engines/ags/engine/media/audio/audio.h @@ -0,0 +1,153 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_AUDIO_H +#define __AC_AUDIO_H + +#include +#include "media/audio/audiodefines.h" +#include "ac/dynobj/scriptaudioclip.h" +#include "ac/dynobj/scriptaudiochannel.h" +#include "media/audio/ambientsound.h" +#include "util/mutex.h" +#include "util/mutex_lock.h" +#include "util/thread.h" +#include "ac/timer.h" + +struct SOUNDCLIP; + +//controls access to the channels, since that's the main point of synchronization between the streaming thread and the user code +//this is going to be dependent on the underlying mutexes being recursive +//yes, we will have more recursive traffic on mutexes than we need +//however this should mostly be happening only when playing sounds, and possibly when sounds numbering only several +//the load should not be high +class AudioChannelsLock : public AGS::Engine::MutexLock +{ +private: + AudioChannelsLock(AudioChannelsLock const &); // non-copyable + AudioChannelsLock& operator=(AudioChannelsLock const &); // not copy-assignable + +public: + static AGS::Engine::Mutex s_mutex; + AudioChannelsLock() + : MutexLock(s_mutex) + { + } + + // Gets a clip from the channel + SOUNDCLIP *GetChannel(int index); + // Gets a clip from the channel but only if it's in playback state + SOUNDCLIP *GetChannelIfPlaying(int index); + // Assign new clip to the channel + SOUNDCLIP *SetChannel(int index, SOUNDCLIP *clip); + // Move clip from one channel to another, clearing the first channel + SOUNDCLIP *MoveChannel(int to, int from); +}; + +// +// Channel helpers, autolock and perform a simple action on a channel. +// +// Tells if channel has got a clip; does not care about its state +bool channel_has_clip(int chanid); +// Tells if channel has got a clip and clip is in playback state +bool channel_is_playing(int chanid); +// Sets new clip to the channel +void set_clip_to_channel(int chanid, SOUNDCLIP *clip); + + +void calculate_reserved_channel_count(); +void update_clip_default_volume(ScriptAudioClip *audioClip); +void start_fading_in_new_track_if_applicable(int fadeInChannel, ScriptAudioClip *newSound); +void stop_or_fade_out_channel(int fadeOutChannel, int fadeInChannel = -1, ScriptAudioClip *newSound = nullptr); +SOUNDCLIP* load_sound_clip(ScriptAudioClip *audioClip, bool repeat); +ScriptAudioChannel* play_audio_clip_on_channel(int channel, ScriptAudioClip *clip, int priority, int repeat, int fromOffset, SOUNDCLIP *cachedClip = nullptr); +void remove_clips_of_type_from_queue(int audioType); +void update_queued_clips_volume(int audioType, int new_vol); +// Checks if speech voice-over is currently playing, and reapply volume drop to all other active clips +void update_volume_drop_if_voiceover(); +ScriptAudioChannel* play_audio_clip(ScriptAudioClip *clip, int priority, int repeat, int fromOffset, bool queueIfNoChannel); +ScriptAudioChannel* play_audio_clip_by_index(int audioClipIndex); +void stop_and_destroy_channel_ex(int chid, bool resetLegacyMusicSettings); +void stop_and_destroy_channel (int chid); + +// ***** BACKWARDS COMPATIBILITY WITH OLD AUDIO SYSTEM ***** // +int get_old_style_number_for_sound(int sound_number); +SOUNDCLIP * load_sound_clip_from_old_style_number(bool isMusic, int indexNumber, bool repeat); + +//============================================================================= + +int init_mod_player(int numVoices); +void remove_mod_player(); +void force_audiostream_include(); +int get_volume_adjusted_for_distance(int volume, int sndX, int sndY, int sndMaxDist); +void update_directional_sound_vol(); +void update_ambient_sound_vol (); +// Tells if the audio type is allowed to play with regards to current sound config +bool is_audiotype_allowed_to_play(AudioFileType type); +// Loads sound data referenced by audio clip item, and starts playback; +// returns NULL on failure +SOUNDCLIP * load_sound_and_play(ScriptAudioClip *aclip, bool repeat); +void stop_all_sound_and_music(); +void shutdown_sound(); +int play_sound(int val1); + +//============================================================================= + +// This is an indicator of a music played by an old audio system +// (to distinguish from the new system API); if it is not set, then old API +// should "think" that no music is played regardless of channel state +// TODO: refactor this and hide behind some good interface to prevent misuse! +extern int current_music_type; + +void clear_music_cache(); +void play_next_queued(); +int calculate_max_volume(); +// add/remove the volume drop to the audio channels while speech is playing +void apply_volume_drop_modifier(bool applyModifier); +// Update the music, and advance the crossfade on a step +// (this should only be called once per game loop); +void update_audio_system_on_game_loop (); +void stopmusic(); +void update_music_volume(); +void post_new_music_check (int newchannel); +// Sets up the crossfading for playing the new music track, +// and returns the channel number to use; the channel is guaranteed to be free +int prepare_for_new_music (); +// Gets audio clip from legacy music number, which also may contain queue flag +ScriptAudioClip *get_audio_clip_for_music(int mnum); +SOUNDCLIP * load_music_from_disk(int mnum, bool doRepeat); +void newmusic(int mnum); + +extern AGS::Engine::Thread audioThread; +extern volatile bool _audio_doing_crossfade; +extern volatile int psp_audio_multithreaded; + +void update_polled_mp3(); +void update_mp3_thread(); + +extern void cancel_scheduled_music_update(); +extern void schedule_music_update_at(AGS_Clock::time_point); +extern void postpone_scheduled_music_update_by(std::chrono::milliseconds); + +// crossFading is >0 (channel number of new track), or -1 (old +// track fading out, no new track) +extern int crossFading, crossFadeVolumePerStep, crossFadeStep; +extern int crossFadeVolumeAtStart; + +extern SOUNDCLIP *cachedQueuedMusic; + +// TODO: double check that ambient sounds array actually needs +1 +extern std::array ambient; + +#endif // __AC_AUDIO_H diff --git a/engines/ags/engine/media/audio/audio_system.h b/engines/ags/engine/media/audio/audio_system.h new file mode 100644 index 00000000000..4cb910c6572 --- /dev/null +++ b/engines/ags/engine/media/audio/audio_system.h @@ -0,0 +1,16 @@ +#ifndef __AC_MEDIA_AUDIO_SYSTEM_H +#define __AC_MEDIA_AUDIO_SYSTEM_H + +#include "media/audio/audiodefines.h" +#include "media/audio/ambientsound.h" + +#include "media/audio/audio.h" + +#include "media/audio/soundcache.h" + +#include "media/audio/soundclip.h" +#include "media/audio/sound.h" + +#include "media/audio/queuedaudioitem.h" + +#endif \ No newline at end of file diff --git a/engines/ags/engine/media/audio/audiodefines.h b/engines/ags/engine/media/audio/audiodefines.h new file mode 100644 index 00000000000..13effae234b --- /dev/null +++ b/engines/ags/engine/media/audio/audiodefines.h @@ -0,0 +1,39 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_AUDIODEFINES_H +#define __AC_AUDIODEFINES_H + +#define MUS_MIDI 1 +#define MUS_MP3 2 +#define MUS_WAVE 3 +#define MUS_MOD 4 +#define MUS_OGG 5 + +#ifndef PSP_NO_MOD_PLAYBACK +#define DUMB_MOD_PLAYER +#endif + +#define MAX_SOUND_CHANNELS 8 +#define SPECIAL_CROSSFADE_CHANNEL 8 + +#define SCHAN_SPEECH 0 +#define SCHAN_AMBIENT 1 +#define SCHAN_MUSIC 2 +#define SCHAN_NORMAL 3 +#define AUDIOTYPE_LEGACY_AMBIENT_SOUND 1 +#define AUDIOTYPE_LEGACY_MUSIC 2 +#define AUDIOTYPE_LEGACY_SOUND 3 + +#endif // __AC_AUDIODEFINES_H \ No newline at end of file diff --git a/engines/ags/engine/media/audio/audiointernaldefs.h b/engines/ags/engine/media/audio/audiointernaldefs.h new file mode 100644 index 00000000000..0d7c0174fb2 --- /dev/null +++ b/engines/ags/engine/media/audio/audiointernaldefs.h @@ -0,0 +1,21 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SOUNDINTERNALDEFS_H +#define __AC_SOUNDINTERNALDEFS_H + +//#define MP3CHUNKSIZE 100000 +#define MP3CHUNKSIZE 32768 + +#endif // __AC_SOUNDINTERNALDEFS_H \ No newline at end of file diff --git a/engines/ags/engine/media/audio/clip_mydumbmod.cpp b/engines/ags/engine/media/audio/clip_mydumbmod.cpp new file mode 100644 index 00000000000..8c040626588 --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mydumbmod.cpp @@ -0,0 +1,163 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/audio/audiodefines.h" + +#ifdef DUMB_MOD_PLAYER + +#include "media/audio/clip_mydumbmod.h" +#include "media/audio/audiointernaldefs.h" + +void al_duh_set_loop(AL_DUH_PLAYER *dp, int loop) { + DUH_SIGRENDERER *sr = al_duh_get_sigrenderer(dp); + DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr); + if (itsr == nullptr) + return; + + if (loop) + dumb_it_set_loop_callback(itsr, nullptr, nullptr); + else + dumb_it_set_loop_callback(itsr, dumb_it_callback_terminate, itsr); +} + +void MYMOD::poll() +{ + if (state_ != SoundClipPlaying) { return; } + + if (al_poll_duh(duhPlayer)) { + state_ = SoundClipStopped; + } +} + +void MYMOD::adjust_volume() +{ + if (!is_playing()) { return; } + al_duh_set_volume(duhPlayer, VOLUME_TO_DUMB_VOL(get_final_volume())); +} + +void MYMOD::set_volume(int newvol) +{ + vol = newvol; + adjust_volume(); +} + +void MYMOD::destroy() +{ + if (duhPlayer) { + al_stop_duh(duhPlayer); + } + duhPlayer = nullptr; + + if (tune) { + unload_duh(tune); + } + tune = nullptr; + + state_ = SoundClipStopped; +} + +void MYMOD::seek(int patnum) +{ + if (!is_playing()) { return; } + + al_stop_duh(duhPlayer); + state_ = SoundClipInitial; + + DUH_SIGRENDERER *sr = dumb_it_start_at_order(tune, 2, patnum); + duhPlayer = al_duh_encapsulate_sigrenderer(sr, VOLUME_TO_DUMB_VOL(vol), 8192, 22050); + if (!duhPlayer) { + duh_end_sigrenderer(sr); + return; + } + + al_duh_set_loop(duhPlayer, repeat); + state_ = SoundClipPlaying; +} + +int MYMOD::get_pos() +{ + if (!is_playing()) { return -1; } + + // determine the current track number (DUMB calls them 'orders') + DUH_SIGRENDERER *sr = al_duh_get_sigrenderer(duhPlayer); + DUMB_IT_SIGRENDERER *itsr = duh_get_it_sigrenderer(sr); + if (itsr == nullptr) + return -1; + + return dumb_it_sr_get_current_order(itsr); +} + +int MYMOD::get_real_mod_pos() +{ + if (!is_playing()) { return -1; } + return al_duh_get_position(duhPlayer); +} + +int MYMOD::get_pos_ms() +{ + if (!is_playing()) { return -1; } + return (get_real_mod_pos() * 10) / 655; +} + +int MYMOD::get_length_ms() +{ + if (tune == nullptr) + return 0; + + // duh_get_length represents time as 65536ths of a second + return (duh_get_length(tune) * 10) / 655; +} + +int MYMOD::get_voice() +{ + // MOD uses so many different voices it's not practical to keep track + return -1; +} + +void MYMOD::pause() { + if (state_ != SoundClipPlaying) { return; } + al_pause_duh(duhPlayer); + state_ = SoundClipPaused; +} + +void MYMOD::resume() { + if (state_ != SoundClipPaused) { return; } + al_resume_duh(duhPlayer); + state_ = SoundClipPlaying; +} + +int MYMOD::get_sound_type() { + return MUS_MOD; +} + +int MYMOD::play() { + if (tune == nullptr) { return 0; } + + duhPlayer = al_start_duh(tune, 2, 0, 1.0, 8192, 22050); + if (!duhPlayer) { + return 0; + } + al_duh_set_loop(duhPlayer, repeat); + set_volume(vol); + + state_ = SoundClipPlaying; + return 1; +} + +MYMOD::MYMOD() : SOUNDCLIP() { + tune = nullptr; + duhPlayer = nullptr; +} + +#endif // DUMB_MOD_PLAYER \ No newline at end of file diff --git a/engines/ags/engine/media/audio/clip_mydumbmod.h b/engines/ags/engine/media/audio/clip_mydumbmod.h new file mode 100644 index 00000000000..193c7f4567c --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mydumbmod.h @@ -0,0 +1,69 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYDUMBMOD_H +#define __AC_MYDUMBMOD_H + +#include "aldumb.h" +#include "media/audio/soundclip.h" + +#define VOLUME_TO_DUMB_VOL(vol) ((float)vol) / 256.0 + +void al_duh_set_loop(AL_DUH_PLAYER *dp, int loop); + +// MOD/XM (DUMB) +struct MYMOD : public SOUNDCLIP +{ + DUH *tune; + AL_DUH_PLAYER *duhPlayer; + + void poll() override; + + void set_volume(int newvol) override; + + void destroy() override; + + void seek(int patnum) override; + + // NOTE: this implementation of the virtual function returns a MOD/XM + // "order" index, not actual playing position; + // this does not make much sense in the context of the interface itself, + // and, as it seems, was implemented so solely for the purpose of emulating + // deprecated "GetMODPattern" script function. + // (see Game_GetMODPattern(), and documentation for AudioChannel.Position property) + // TODO: find a way to redesign this behavior + int get_pos() override; + + int get_pos_ms() override; + + int get_length_ms() override; + + void pause() override; + + void resume() override; + + int get_sound_type() override; + + int play() override; + + MYMOD(); + +protected: + int get_voice() override; + void adjust_volume() override; + // Returns real MOD/XM playing position + int get_real_mod_pos(); +}; + +#endif // __AC_MYDUMBMOD_H \ No newline at end of file diff --git a/engines/ags/engine/media/audio/clip_myjgmod.cpp b/engines/ags/engine/media/audio/clip_myjgmod.cpp new file mode 100644 index 00000000000..45209551e7d --- /dev/null +++ b/engines/ags/engine/media/audio/clip_myjgmod.cpp @@ -0,0 +1,89 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/audio/audiodefines.h" + +#ifdef JGMOD_MOD_PLAYER + +#include "media/audio/clip_myjgmod.h" +#include "media/audio/audiointernaldefs.h" + +int MYMOD::poll() +{ + if (done) + return done; + + if (is_mod_playing() == 0) + done = 1; + + return done; +} + +void MYMOD::set_volume(int newvol) +{ + vol = newvol; + if (!done) + set_mod_volume(newvol); +} + +void MYMOD::destroy() +{ + stop_mod(); + destroy_mod(tune); + tune = NULL; +} + +void MYMOD::seek(int patnum) +{ + if (is_mod_playing() != 0) + goto_mod_track(patnum); +} + +int MYMOD::get_pos() +{ + if (!is_mod_playing()) + return -1; + return mi.trk; +} + +int MYMOD::get_pos_ms() +{ + return 0; // we don't know ms offset +} + +int MYMOD::get_length_ms() +{ // we don't know ms + return 0; +} + +int MYMOD::get_voice() +{ + // MOD uses so many different voices it's not practical to keep track + return -1; +} + +int MYMOD::get_sound_type() { + return MUS_MOD; +} + +int MYMOD::play() { + play_mod(tune, repeat); + + return 1; +} + +MYMOD::MYMOD() : SOUNDCLIP() { +} + +#endif // #ifdef JGMOD_MOD_PLAYER \ No newline at end of file diff --git a/engines/ags/engine/media/audio/clip_myjgmod.h b/engines/ags/engine/media/audio/clip_myjgmod.h new file mode 100644 index 00000000000..6870b67503c --- /dev/null +++ b/engines/ags/engine/media/audio/clip_myjgmod.h @@ -0,0 +1,49 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYJGMOD_H +#define __AC_MYJGMOD_H + +#include "jgmod.h" +#include "media/audio/soundclip.h" + +// MOD/XM (JGMOD) +struct MYMOD:public SOUNDCLIP +{ + JGMOD *tune; + + int poll(); + + void set_volume(int newvol); + + void destroy(); + + void seek(int patnum); + + int get_pos(); + + int get_pos_ms(); + + int get_length_ms(); + + int get_voice(); + + int get_sound_type(); + + int play(); + + MYMOD(); +}; + +#endif // __AC_MYJGMOD_H \ No newline at end of file diff --git a/engines/ags/engine/media/audio/clip_mymidi.cpp b/engines/ags/engine/media/audio/clip_mymidi.cpp new file mode 100644 index 00000000000..1175bab306d --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mymidi.cpp @@ -0,0 +1,111 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/audio/audiodefines.h" +#include "util/wgt2allg.h" +#include "media/audio/clip_mymidi.h" +#include "media/audio/audiointernaldefs.h" + +void MYMIDI::poll() +{ + if (state_ != SoundClipPlaying) { return; } + + if (midi_pos < 0) + state_ = SoundClipStopped; +} + +void MYMIDI::adjust_volume() +{ + if (!is_playing()) { return; } + ::set_volume(-1, get_final_volume()); +} + +void MYMIDI::set_volume(int newvol) +{ + vol = newvol; + adjust_volume(); +} + +void MYMIDI::destroy() +{ + stop_midi(); + + if (tune) { + destroy_midi(tune); + } + tune = nullptr; + + state_ = SoundClipStopped; +} + +void MYMIDI::seek(int pos) +{ + if (!is_playing()) { return; } + midi_seek(pos); +} + +int MYMIDI::get_pos() +{ + if (!is_playing()) { return -1; } + return midi_pos; +} + +int MYMIDI::get_pos_ms() +{ + return 0; // we don't know ms with midi +} + +int MYMIDI::get_length_ms() +{ + return lengthInSeconds * 1000; +} + +int MYMIDI::get_voice() +{ + // voice is N/A for midi + return -1; +} + +void MYMIDI::pause() { + if (state_ != SoundClipPlaying) { return; } + midi_pause(); + state_ = SoundClipPaused; +} + +void MYMIDI::resume() { + if (state_ != SoundClipPaused) { return; } + midi_resume(); + state_ = SoundClipPlaying; +} + +int MYMIDI::get_sound_type() { + return MUS_MIDI; +} + +int MYMIDI::play() { + if (tune == nullptr) { return 0; } + + lengthInSeconds = get_midi_length(tune); + if (::play_midi(tune, repeat)) { + return 0; + } + + state_ = SoundClipPlaying; + return 1; +} + +MYMIDI::MYMIDI() : SOUNDCLIP() { + tune = nullptr; + lengthInSeconds = 0; +} diff --git a/engines/ags/engine/media/audio/clip_mymidi.h b/engines/ags/engine/media/audio/clip_mymidi.h new file mode 100644 index 00000000000..5799f5ab88e --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mymidi.h @@ -0,0 +1,55 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYMIDI_H +#define __AC_MYMIDI_H + +#include "media/audio/soundclip.h" + +// MIDI +struct MYMIDI:public SOUNDCLIP +{ + MIDI *tune; + int lengthInSeconds; + + void poll() override; + + void set_volume(int newvol) override; + + void destroy() override; + + void seek(int pos) override; + + int get_pos() override; + + int get_pos_ms() override; + + int get_length_ms() override; + + void pause() override; + + void resume() override; + + int get_sound_type() override; + + int play() override; + + MYMIDI(); + +protected: + int get_voice() override; + void adjust_volume() override; +}; + +#endif // __AC_MYMIDI_H \ No newline at end of file diff --git a/engines/ags/engine/media/audio/clip_mymp3.cpp b/engines/ags/engine/media/audio/clip_mymp3.cpp new file mode 100644 index 00000000000..b4c98d849ea --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mymp3.cpp @@ -0,0 +1,173 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/audio/audiodefines.h" + +#ifndef NO_MP3_PLAYER + +#include "media/audio/clip_mymp3.h" +#include "media/audio/audiointernaldefs.h" +#include "ac/common.h" // quit() +#include "ac/asset_helper.h" +#include "util/mutex_lock.h" + +#include "platform/base/agsplatformdriver.h" + + +void MYMP3::poll() +{ + if (state_ != SoundClipPlaying) { return; } + + // update the buffer + char *tempbuf = nullptr; + { + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + tempbuf = (char *)almp3_get_mp3stream_buffer(stream); + } + + if (tempbuf != nullptr) { + AGS_PACKFILE_OBJ* obj = (AGS_PACKFILE_OBJ*)in->userdata; + int free_val = -1; + if (chunksize >= obj->remains) { + chunksize = obj->remains; + free_val = chunksize; + } + pack_fread(tempbuf, chunksize, in); + + { + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + almp3_free_mp3stream_buffer(stream, free_val); + } + } + + { + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + if (almp3_poll_mp3stream(stream) == ALMP3_POLL_PLAYJUSTFINISHED) { + state_ = SoundClipStopped; + } + } +} + +void MYMP3::adjust_stream() +{ + if (!is_playing()) { return; } + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + almp3_adjust_mp3stream(stream, get_final_volume(), panning, speed); +} + +void MYMP3::adjust_volume() +{ + adjust_stream(); +} + +void MYMP3::set_volume(int newvol) +{ + // boost MP3 volume + newvol += 20; + if (newvol > 255) + newvol = 255; + + vol = newvol; + adjust_stream(); +} + +void MYMP3::set_speed(int new_speed) +{ + speed = new_speed; + adjust_stream(); +} + +void MYMP3::destroy() +{ + if (stream) { + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + almp3_stop_mp3stream(stream); + almp3_destroy_mp3stream(stream); + } + stream = nullptr; + + if (buffer) + free(buffer); + buffer = nullptr; + + pack_fclose(in); + + state_ = SoundClipStopped; +} + +void MYMP3::seek(int pos) +{ + if (!is_playing()) { return; } + quit("Tried to seek an mp3stream"); +} + +int MYMP3::get_pos() +{ + return 0; // Return 0 to signify that Seek is not supported + // return almp3_get_pos_msecs_mp3stream (stream); +} + +int MYMP3::get_pos_ms() +{ + if (!is_playing()) { return -1; } + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + return almp3_get_pos_msecs_mp3stream(stream); +} + +int MYMP3::get_length_ms() +{ + if (!is_playing()) { return -1; } + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + return almp3_get_length_msecs_mp3stream(stream, filesize); +} + +int MYMP3::get_voice() +{ + if (!is_playing()) { return -1; } + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + AUDIOSTREAM *ast = almp3_get_audiostream_mp3stream(stream); + return (ast != nullptr ? ast->voice : -1); +} + +int MYMP3::get_sound_type() { + return MUS_MP3; +} + +int MYMP3::play() { + if (in == nullptr) { return 0; } + + { + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + if (almp3_play_mp3stream(stream, chunksize, (vol > 230) ? vol : vol + 20, panning) != ALMP3_OK) { + return 0; + } + } + + state_ = SoundClipPlaying; + + if (!psp_audio_multithreaded) + poll(); + + return 1; +} + +MYMP3::MYMP3() : SOUNDCLIP() { + stream = nullptr; + in = nullptr; + filesize = 0; + buffer = nullptr; + chunksize = 0; +} + +#endif // !NO_MP3_PLAYER \ No newline at end of file diff --git a/engines/ags/engine/media/audio/clip_mymp3.h b/engines/ags/engine/media/audio/clip_mymp3.h new file mode 100644 index 00000000000..58448149d7c --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mymp3.h @@ -0,0 +1,50 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYMP3_H +#define __AC_MYMP3_H + +#include "almp3.h" +#include "media/audio/soundclip.h" + +extern AGS::Engine::Mutex _mp3_mutex; + +struct MYMP3:public SOUNDCLIP +{ + ALMP3_MP3STREAM *stream; + PACKFILE *in; + size_t filesize; + char *buffer; + int chunksize; + + void poll() override; + void set_volume(int newvol) override; + void set_speed(int new_speed) override; + void destroy() override; + void seek(int pos) override; + int get_pos() override; + int get_pos_ms() override; + int get_length_ms() override; + int get_sound_type() override; + int play() override; + MYMP3(); + +protected: + int get_voice() override; + void adjust_volume() override; +private: + void adjust_stream(); +}; + +#endif // __AC_MYMP3_H \ No newline at end of file diff --git a/engines/ags/engine/media/audio/clip_myogg.cpp b/engines/ags/engine/media/audio/clip_myogg.cpp new file mode 100644 index 00000000000..da16c94162a --- /dev/null +++ b/engines/ags/engine/media/audio/clip_myogg.cpp @@ -0,0 +1,203 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/audio/audiodefines.h" +#include "media/audio/clip_myogg.h" +#include "media/audio/audiointernaldefs.h" +#include "ac/common.h" // quit() +#include "ac/asset_helper.h" +#include "util/mutex_lock.h" + +#include "platform/base/agsplatformdriver.h" + +extern "C" { + extern int alogg_is_end_of_oggstream(ALOGG_OGGSTREAM *ogg); + extern int alogg_is_end_of_ogg(ALOGG_OGG *ogg); + extern int alogg_get_ogg_freq(ALOGG_OGG *ogg); + extern int alogg_get_ogg_stereo(ALOGG_OGG *ogg); +} + +void MYOGG::poll() +{ + if (state_ != SoundClipPlaying) { return; } + + AGS_PACKFILE_OBJ* obj = (AGS_PACKFILE_OBJ*)in->userdata; + if (obj->remains > 0) + { + // update the buffer + char *tempbuf = (char *)alogg_get_oggstream_buffer(stream); + if (tempbuf != nullptr) + { + int free_val = -1; + if (chunksize >= obj->remains) + { + chunksize = obj->remains; + free_val = chunksize; + } + pack_fread(tempbuf, chunksize, in); + alogg_free_oggstream_buffer(stream, free_val); + } + } + + int ret = alogg_poll_oggstream(stream); + if (ret == ALOGG_OK || ret == ALOGG_POLL_BUFFERUNDERRUN) + get_pos_ms(); // call this to keep the last_but_one stuff up to date + else { + // finished playing or error + state_ = SoundClipStopped; + } +} + +void MYOGG::adjust_stream() +{ + if (!is_playing()) { return; } + alogg_adjust_oggstream(stream, get_final_volume(), panning, speed); +} + +void MYOGG::adjust_volume() +{ + adjust_stream(); +} + +void MYOGG::set_volume(int newvol) +{ + // boost MP3 volume + newvol += 20; + if (newvol > 255) + newvol = 255; + vol = newvol; + adjust_stream(); +} + +void MYOGG::set_speed(int new_speed) +{ + speed = new_speed; + adjust_stream(); +} + +void MYOGG::destroy() +{ + if (stream) { + alogg_stop_oggstream(stream); + alogg_destroy_oggstream(stream); + } + stream = nullptr; + + if (buffer) + free(buffer); + buffer = nullptr; + + pack_fclose(in); + + state_ = SoundClipStopped; +} + +void MYOGG::seek(int pos) +{ + if (!is_playing()) { return; } + quit("Attempted to seek an oggstream; operation not permitted"); +} + +int MYOGG::get_pos() +{ + return 0; +} + +int MYOGG::get_pos_ms() +{ + // Unfortunately the alogg_get_pos_msecs_oggstream function + // returns the ms offset that was last decoded, so it's always + // ahead of the actual playback. Therefore we have this + // hideous hack below to sort it out. + if ((!is_playing()) || (!alogg_is_playing_oggstream(stream))) + return 0; + + AUDIOSTREAM *str = alogg_get_audiostream_oggstream(stream); + long offs = (voice_get_position(str->voice) * 1000) / str->samp->freq; + + if (last_ms_offs != alogg_get_pos_msecs_oggstream(stream)) { + last_but_one_but_one = last_but_one; + last_but_one = last_ms_offs; + last_ms_offs = alogg_get_pos_msecs_oggstream(stream); + } + + // just about to switch buffers + if (offs < 0) + return last_but_one; + + int end_of_stream = alogg_is_end_of_oggstream(stream); + + if ((str->active == 1) && (last_but_one_but_one > 0) && (str->locked == nullptr)) { + switch (end_of_stream) { +case 0: +case 2: + offs -= (last_but_one - last_but_one_but_one); + break; +case 1: + offs -= (last_but_one - last_but_one_but_one); + break; + } + } + + if (end_of_stream == 1) { + + return offs + last_but_one; + } + + return offs + last_but_one_but_one; +} + +int MYOGG::get_length_ms() +{ // streamed OGG is variable bitrate so we don't know + return 0; +} + +int MYOGG::get_voice() +{ + if (!is_playing()) { return -1; } + + AUDIOSTREAM *ast = alogg_get_audiostream_oggstream(stream); + if (ast) + return ast->voice; + return -1; +} + +int MYOGG::get_sound_type() { + return MUS_OGG; +} + +int MYOGG::play() { + if (in == nullptr) { return 0; } + + if (alogg_play_oggstream(stream, MP3CHUNKSIZE, (vol > 230) ? vol : vol + 20, panning) != ALOGG_OK) { + return 0; + } + + state_ = SoundClipPlaying; + + if (!psp_audio_multithreaded) + poll(); + + return 1; +} + +MYOGG::MYOGG() : SOUNDCLIP() { + stream = nullptr; + in = nullptr; + buffer = nullptr; + chunksize = 0; + last_but_one_but_one = 0; + last_but_one = 0; + last_ms_offs = 0; +} diff --git a/engines/ags/engine/media/audio/clip_myogg.h b/engines/ags/engine/media/audio/clip_myogg.h new file mode 100644 index 00000000000..093920476af --- /dev/null +++ b/engines/ags/engine/media/audio/clip_myogg.h @@ -0,0 +1,60 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYOGG_H +#define __AC_MYOGG_H + +#include "alogg.h" +#include "media/audio/soundclip.h" + +struct MYOGG:public SOUNDCLIP +{ + ALOGG_OGGSTREAM *stream; + PACKFILE *in; + char *buffer; + int chunksize; + + int last_but_one_but_one; + int last_but_one; + int last_ms_offs; + + void poll() override; + + void set_volume(int newvol) override; + void set_speed(int new_speed) override; + + void destroy() override; + + void seek(int pos) override; + + int get_pos() override; + + int get_pos_ms() override; + + int get_length_ms() override; + + int get_sound_type() override; + + int play() override; + + MYOGG(); + +protected: + int get_voice() override; + void adjust_volume() override; +private: + void adjust_stream(); +}; + +#endif // __AC_MYOGG_H \ No newline at end of file diff --git a/engines/ags/engine/media/audio/clip_mystaticmp3.cpp b/engines/ags/engine/media/audio/clip_mystaticmp3.cpp new file mode 100644 index 00000000000..1c0c0c46bcc --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mystaticmp3.cpp @@ -0,0 +1,156 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/audio/audiodefines.h" + +#ifndef NO_MP3_PLAYER + +#include "media/audio/clip_mystaticmp3.h" +#include "media/audio/audiointernaldefs.h" +#include "media/audio/soundcache.h" +#include "util/mutex_lock.h" + +#include "platform/base/agsplatformdriver.h" + +extern int our_eip; + +// ALMP3 functions are not reentrant! This mutex should be locked before calling any +// of the mp3 functions and unlocked afterwards. +AGS::Engine::Mutex _mp3_mutex; + +void MYSTATICMP3::poll() +{ + if (state_ != SoundClipPlaying) { return; } + + int oldeip = our_eip; + our_eip = 5997; + + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + int result = almp3_poll_mp3(tune); + + if (result == ALMP3_POLL_PLAYJUSTFINISHED) + { + if (!repeat) + { + state_ = SoundClipStopped; + } + } + our_eip = oldeip; +} + +void MYSTATICMP3::adjust_stream() +{ + if (!is_playing()) { return; } + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + almp3_adjust_mp3(tune, get_final_volume(), panning, speed, repeat); +} + +void MYSTATICMP3::adjust_volume() +{ + adjust_stream(); +} + +void MYSTATICMP3::set_volume(int newvol) +{ + vol = newvol; + adjust_stream(); +} + +void MYSTATICMP3::set_speed(int new_speed) +{ + speed = new_speed; + adjust_stream(); +} + +void MYSTATICMP3::destroy() +{ + if (tune) { + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + almp3_stop_mp3(tune); + almp3_destroy_mp3(tune); + } + tune = nullptr; + + if (mp3buffer) { + sound_cache_free(mp3buffer, false); + } + mp3buffer = nullptr; + + state_ = SoundClipStopped; +} + +void MYSTATICMP3::seek(int pos) +{ + if (!is_playing()) { return; } + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + almp3_seek_abs_msecs_mp3(tune, pos); +} + +int MYSTATICMP3::get_pos() +{ + if (!is_playing()) { return -1; } + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + return almp3_get_pos_msecs_mp3(tune); +} + +int MYSTATICMP3::get_pos_ms() +{ + int result = get_pos(); + return result; +} + +int MYSTATICMP3::get_length_ms() +{ + if (tune == nullptr) { return -1; } + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + return almp3_get_length_msecs_mp3(tune); +} + +int MYSTATICMP3::get_voice() +{ + if (!is_playing()) { return -1; } + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + AUDIOSTREAM *ast = almp3_get_audiostream_mp3(tune); + return (ast != nullptr ? ast->voice : -1); +} + +int MYSTATICMP3::get_sound_type() { + return MUS_MP3; +} + +int MYSTATICMP3::play() { + if (tune == nullptr) { return 0; } + + { + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + int result = almp3_play_ex_mp3(tune, 16384, vol, panning, 1000, repeat); + if (result != ALMP3_OK) { + return 0; + } + } + + state_ = SoundClipPlaying; + + if (!psp_audio_multithreaded) + poll(); + + return 1; +} + +MYSTATICMP3::MYSTATICMP3() : SOUNDCLIP() { + tune = nullptr; + mp3buffer = nullptr; +} + +#endif // !NO_MP3_PLAYER diff --git a/engines/ags/engine/media/audio/clip_mystaticmp3.h b/engines/ags/engine/media/audio/clip_mystaticmp3.h new file mode 100644 index 00000000000..20f79e4711d --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mystaticmp3.h @@ -0,0 +1,57 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYSTATICMP3_H +#define __AC_MYSTATICMP3_H + +#include "almp3.h" +#include "media/audio/soundclip.h" + +extern AGS::Engine::Mutex _mp3_mutex; + +// pre-loaded (non-streaming) MP3 file +struct MYSTATICMP3:public SOUNDCLIP +{ + ALMP3_MP3 *tune; + char *mp3buffer; + + void poll() override; + + void set_volume(int newvol) override; + void set_speed(int new_speed) override; + + void destroy() override; + + void seek(int pos) override; + + int get_pos() override; + + int get_pos_ms() override; + + int get_length_ms() override; + + int get_sound_type() override; + + int play() override; + + MYSTATICMP3(); + +protected: + int get_voice() override; + void adjust_volume() override; +private: + void adjust_stream(); +}; + +#endif // __AC_MYSTATICMP3_H diff --git a/engines/ags/engine/media/audio/clip_mystaticogg.cpp b/engines/ags/engine/media/audio/clip_mystaticogg.cpp new file mode 100644 index 00000000000..5a744e499fd --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mystaticogg.cpp @@ -0,0 +1,205 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/audio/audiodefines.h" +#include "media/audio/clip_mystaticogg.h" +#include "media/audio/audiointernaldefs.h" +#include "media/audio/soundcache.h" +#include "util/mutex_lock.h" + +#include "platform/base/agsplatformdriver.h" + +extern "C" { + extern int alogg_is_end_of_oggstream(ALOGG_OGGSTREAM *ogg); + extern int alogg_is_end_of_ogg(ALOGG_OGG *ogg); + extern int alogg_get_ogg_freq(ALOGG_OGG *ogg); + extern int alogg_get_ogg_stereo(ALOGG_OGG *ogg); +} + +extern int use_extra_sound_offset; // defined in ac.cpp + +void MYSTATICOGG::poll() +{ + if (state_ != SoundClipPlaying) { return; } + + if (alogg_poll_ogg(tune) == ALOGG_POLL_PLAYJUSTFINISHED) { + if (!repeat) + { + state_ = SoundClipStopped; + } + } + else get_pos(); // call this to keep the last_but_one stuff up to date +} + +void MYSTATICOGG::adjust_stream() +{ + if (!is_playing()) { return; } + alogg_adjust_ogg(tune, get_final_volume(), panning, speed, repeat); +} + +void MYSTATICOGG::adjust_volume() +{ + adjust_stream(); +} + +void MYSTATICOGG::set_volume(int newvol) +{ + vol = newvol; + adjust_stream(); +} + +void MYSTATICOGG::set_speed(int new_speed) +{ + speed = new_speed; + adjust_stream(); +} + +void MYSTATICOGG::destroy() +{ + if (tune) { + alogg_stop_ogg(tune); + alogg_destroy_ogg(tune); + } + tune = nullptr; + + if (mp3buffer) { + sound_cache_free(mp3buffer, false); + } + mp3buffer = nullptr; + + state_ = SoundClipStopped; +} + +void MYSTATICOGG::seek(int pos) +{ + if (!is_playing()) { return; } + + // we stop and restart it because otherwise the buffer finishes + // playing first and the seek isn't quite accurate + alogg_stop_ogg(tune); + state_ = SoundClipInitial; + play_from(pos); +} + +int MYSTATICOGG::get_pos() +{ + if (!is_playing()) { return -1; } + return get_pos_ms(); +} + +int MYSTATICOGG::get_pos_ms() +{ + if (!is_playing()) { return -1; } + + // Unfortunately the alogg_get_pos_msecs function + // returns the ms offset that was last decoded, so it's always + // ahead of the actual playback. Therefore we have this + // hideous hack below to sort it out. + if (!alogg_is_playing_ogg(tune)) + return 0; + + AUDIOSTREAM *str = alogg_get_audiostream_ogg(tune); + long offs = (voice_get_position(str->voice) * 1000) / str->samp->freq; + + if (last_ms_offs != alogg_get_pos_msecs_ogg(tune)) { + last_but_one_but_one = last_but_one; + last_but_one = last_ms_offs; + last_ms_offs = alogg_get_pos_msecs_ogg(tune); + } + + // just about to switch buffers + if (offs < 0) + return last_but_one; + + int end_of_stream = alogg_is_end_of_ogg(tune); + + if ((str->active == 1) && (last_but_one_but_one > 0) && (str->locked == nullptr)) { + switch (end_of_stream) { +case 0: +case 2: + offs -= (last_but_one - last_but_one_but_one); + break; +case 1: + offs -= (last_but_one - last_but_one_but_one); + break; + } + } + + if (end_of_stream == 1) { + return offs + last_but_one + extraOffset; + } + + return offs + last_but_one_but_one + extraOffset; +} + +int MYSTATICOGG::get_length_ms() +{ + if (tune == nullptr) { return -1; } + return alogg_get_length_msecs_ogg(tune); +} + +int MYSTATICOGG::get_voice() +{ + if (!is_playing()) { return -1; } + AUDIOSTREAM *ast = alogg_get_audiostream_ogg(tune); + if (ast) + return ast->voice; + return -1; +} + +int MYSTATICOGG::get_sound_type() { + return MUS_OGG; +} + +int MYSTATICOGG::play_from(int position) +{ + if (tune == nullptr) { return 0; } + + if (use_extra_sound_offset) + extraOffset = ((16384 / (alogg_get_wave_is_stereo_ogg(tune) ? 2 : 1)) * 1000) / alogg_get_wave_freq_ogg(tune); + else + extraOffset = 0; + + if (alogg_play_ex_ogg(tune, 16384, vol, panning, 1000, repeat) != ALOGG_OK) { + return 0; + } + + last_ms_offs = position; + last_but_one = position; + last_but_one_but_one = position; + + if (position > 0) + alogg_seek_abs_msecs_ogg(tune, position); + + state_ = SoundClipPlaying; + + if (!psp_audio_multithreaded) + poll(); + + return 1; +} + +int MYSTATICOGG::play() { + return play_from(0); +} + +MYSTATICOGG::MYSTATICOGG() : SOUNDCLIP() { + tune = nullptr; + mp3buffer = nullptr; + mp3buffersize = 0; + extraOffset = 0; + last_but_one = 0; + last_ms_offs = 0; + last_but_one_but_one = 0; +} diff --git a/engines/ags/engine/media/audio/clip_mystaticogg.h b/engines/ags/engine/media/audio/clip_mystaticogg.h new file mode 100644 index 00000000000..a57250857dd --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mystaticogg.h @@ -0,0 +1,63 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYSTATICOGG_H +#define __AC_MYSTATICOGG_H + +#include "alogg.h" +#include "media/audio/soundclip.h" + +// pre-loaded (non-streaming) OGG file +struct MYSTATICOGG:public SOUNDCLIP +{ + ALOGG_OGG *tune; + char *mp3buffer; + int mp3buffersize; + int extraOffset; + + int last_but_one_but_one; + int last_but_one; + int last_ms_offs; + + void poll() override; + + void set_volume(int newvol) override; + void set_speed(int new_speed) override; + + void destroy() override; + + void seek(int pos) override; + + int get_pos() override; + + int get_pos_ms() override; + + int get_length_ms() override; + + int get_sound_type() override; + + int play_from(int position) override; + + int play() override; + + MYSTATICOGG(); + +protected: + int get_voice() override; + void adjust_volume() override; +private: + void adjust_stream(); +}; + +#endif // __AC_MYSTATICOGG_H diff --git a/engines/ags/engine/media/audio/clip_mywave.cpp b/engines/ags/engine/media/audio/clip_mywave.cpp new file mode 100644 index 00000000000..4ab79f06136 --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mywave.cpp @@ -0,0 +1,116 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/wgt2allg.h" +#include "media/audio/audiodefines.h" +#include "media/audio/clip_mywave.h" +#include "media/audio/audiointernaldefs.h" +#include "media/audio/soundcache.h" +#include "util/mutex_lock.h" + +#include "platform/base/agsplatformdriver.h" + +void MYWAVE::poll() +{ + if (state_ != SoundClipPlaying) { return; } + + if (voice_get_position(voice) < 0) + { + state_ = SoundClipStopped; + } +} + +void MYWAVE::adjust_volume() +{ + if (!is_playing()) { return; } + if (voice < 0) { return; } + voice_set_volume(voice, get_final_volume()); +} + +void MYWAVE::set_volume(int newvol) +{ + vol = newvol; + adjust_volume(); +} + +void MYWAVE::destroy() +{ + // Stop sound and decrease reference count. + if (wave) { + stop_sample(wave); + sound_cache_free((char*)wave, true); + } + wave = nullptr; + + state_ = SoundClipStopped; +} + +void MYWAVE::seek(int pos) +{ + if (!is_playing()) { return; } + voice_set_position(voice, pos); +} + +int MYWAVE::get_pos() +{ + if (!is_playing()) { return -1; } + return voice_get_position(voice); +} + +int MYWAVE::get_pos_ms() +{ + // convert the offset in samples into the offset in ms + //return ((1000000 / voice_get_frequency(voice)) * voice_get_position(voice)) / 1000; + + if (voice_get_frequency(voice) < 100) + return 0; + // (number of samples / (samples per second / 100)) * 10 = ms + return (voice_get_position(voice) / (voice_get_frequency(voice) / 100)) * 10; +} + +int MYWAVE::get_length_ms() +{ + if (wave == nullptr) { return -1; } + if (wave->freq < 100) + return 0; + return (wave->len / (wave->freq / 100)) * 10; +} + +int MYWAVE::get_voice() +{ + if (!is_playing()) { return -1; } + return voice; +} + +int MYWAVE::get_sound_type() { + return MUS_WAVE; +} + +int MYWAVE::play() { + if (wave == nullptr) { return 0; } + + voice = play_sample(wave, vol, panning, 1000, repeat); + if (voice < 0) { + return 0; + } + + state_ = SoundClipPlaying; + + return 1; +} + +MYWAVE::MYWAVE() : SOUNDCLIP() { + wave = nullptr; + voice = -1; +} diff --git a/engines/ags/engine/media/audio/clip_mywave.h b/engines/ags/engine/media/audio/clip_mywave.h new file mode 100644 index 00000000000..b78c5ed29b6 --- /dev/null +++ b/engines/ags/engine/media/audio/clip_mywave.h @@ -0,0 +1,50 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MYWAVE_H +#define __AC_MYWAVE_H + +#include "media/audio/soundclip.h" + +// My new MP3STREAM wrapper +struct MYWAVE:public SOUNDCLIP +{ + SAMPLE *wave; + int voice; + + void poll() override; + + void set_volume(int new_speed) override; + + void destroy() override; + + void seek(int pos) override; + + int get_pos() override; + int get_pos_ms() override; + + int get_length_ms() override; + + int get_sound_type() override; + + int play() override; + + MYWAVE(); + +protected: + int get_voice() override; + void adjust_volume() override; +}; + +#endif // __AC_MYWAVE_H \ No newline at end of file diff --git a/engines/ags/engine/media/audio/queuedaudioitem.cpp b/engines/ags/engine/media/audio/queuedaudioitem.cpp new file mode 100644 index 00000000000..0656a102552 --- /dev/null +++ b/engines/ags/engine/media/audio/queuedaudioitem.cpp @@ -0,0 +1,38 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/audio/queuedaudioitem.h" +#include "ac/common_defines.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +// [IKM] 2012-07-02: these functions are used during load/save game, +// and read/written as-is, hence cachedClip pointer should be serialized +// simply like pointer (although that probably does not mean much sense?) +void QueuedAudioItem::ReadFromFile(Stream *in) +{ + audioClipIndex = in->ReadInt16(); + priority = in->ReadInt16(); + repeat = in->ReadBool(); + in->ReadInt32(); // cachedClip +} + +void QueuedAudioItem::WriteToFile(Stream *out) const +{ + out->WriteInt16(audioClipIndex); + out->WriteInt16(priority); + out->WriteBool(repeat); + out->WriteInt32(0); // cachedClip +} diff --git a/engines/ags/engine/media/audio/queuedaudioitem.h b/engines/ags/engine/media/audio/queuedaudioitem.h new file mode 100644 index 00000000000..0e7313e6160 --- /dev/null +++ b/engines/ags/engine/media/audio/queuedaudioitem.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_QUEUEDAUDIOITEM_H +#define __AC_QUEUEDAUDIOITEM_H + +struct SOUNDCLIP; + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +struct QueuedAudioItem { + short audioClipIndex; + short priority; + bool repeat; + SOUNDCLIP *cachedClip; + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out) const; +}; + +#endif // __AC_QUEUEDAUDIOITEM_H \ No newline at end of file diff --git a/engines/ags/engine/media/audio/sound.cpp b/engines/ags/engine/media/audio/sound.cpp new file mode 100644 index 00000000000..4bc3348a397 --- /dev/null +++ b/engines/ags/engine/media/audio/sound.cpp @@ -0,0 +1,356 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// ACSOUND - AGS sound system wrapper +// +//============================================================================= + +#include // for toupper + +#include "core/platform.h" +#include "util/wgt2allg.h" +#include "ac/file.h" +#include "media/audio/audiodefines.h" +#include "media/audio/sound.h" +#include "media/audio/audiointernaldefs.h" +#include "media/audio/clip_mywave.h" +#ifndef NO_MP3_PLAYER +#include "media/audio/clip_mymp3.h" +#include "media/audio/clip_mystaticmp3.h" +#endif +#include "media/audio/clip_myogg.h" +#include "media/audio/clip_mystaticogg.h" +#include "media/audio/clip_mymidi.h" +#ifdef JGMOD_MOD_PLAYER +#include "media/audio/clip_myjgmod.h" +#endif +#ifdef DUMB_MOD_PLAYER +#include "media/audio/clip_mydumbmod.h" +#endif +#include "media/audio/soundcache.h" +#include "util/mutex_lock.h" + +#if defined JGMOD_MOD_PLAYER && defined DUMB_MOD_PLAYER +#error JGMOD_MOD_PLAYER and DUMB_MOD_PLAYER macros cannot be defined at the same time. +#endif + +#if !defined PSP_NO_MOD_PLAYBACK && !defined JGMOD_MOD_PLAYER && !defined DUMB_MOD_PLAYER +#error Either JGMOD_MOD_PLAYER or DUMB_MOD_PLAYER should be defined. +#endif + +extern "C" +{ +// Load MIDI from PACKFILE stream +MIDI *load_midi_pf(PACKFILE *pf); +} + + +int use_extra_sound_offset = 0; + + + +MYWAVE *thiswave; +SOUNDCLIP *my_load_wave(const AssetPath &asset_name, int voll, int loop) +{ + // Load via soundcache. + size_t dummy; + SAMPLE *new_sample = (SAMPLE*)get_cached_sound(asset_name, true, dummy); + + if (new_sample == nullptr) + return nullptr; + + thiswave = new MYWAVE(); + thiswave->wave = new_sample; + thiswave->vol = voll; + thiswave->repeat = (loop != 0); + + return thiswave; +} + +PACKFILE *mp3in; + +#ifndef NO_MP3_PLAYER + +MYMP3 *thistune; +SOUNDCLIP *my_load_mp3(const AssetPath &asset_name, int voll) +{ + size_t asset_size; + mp3in = PackfileFromAsset(asset_name, asset_size); + if (mp3in == nullptr) + return nullptr; + + char *tmpbuffer = (char *)malloc(MP3CHUNKSIZE); + if (tmpbuffer == nullptr) { + pack_fclose(mp3in); + return nullptr; + } + thistune = new MYMP3(); + thistune->in = mp3in; + thistune->chunksize = MP3CHUNKSIZE; + thistune->filesize = asset_size; + thistune->vol = voll; + + if (thistune->chunksize > thistune->filesize) + thistune->chunksize = thistune->filesize; + + pack_fread(tmpbuffer, thistune->chunksize, mp3in); + + thistune->buffer = (char *)tmpbuffer; + + { + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + thistune->stream = almp3_create_mp3stream(tmpbuffer, thistune->chunksize, (thistune->filesize < 1)); + } + + if (thistune->stream == nullptr) { + free(tmpbuffer); + pack_fclose(mp3in); + delete thistune; + return nullptr; + } + + return thistune; +} + + + +MYSTATICMP3 *thismp3; +SOUNDCLIP *my_load_static_mp3(const AssetPath &asset_name, int voll, bool loop) +{ + // Load via soundcache. + size_t muslen = 0; + char* mp3buffer = get_cached_sound(asset_name, false, muslen); + if (mp3buffer == nullptr) + return nullptr; + + // now, create an MP3 structure for it + thismp3 = new MYSTATICMP3(); + if (thismp3 == nullptr) { + free(mp3buffer); + return nullptr; + } + thismp3->vol = voll; + thismp3->mp3buffer = nullptr; + thismp3->repeat = loop; + + { + AGS::Engine::MutexLock _lockMp3(_mp3_mutex); + thismp3->tune = almp3_create_mp3(mp3buffer, muslen); + } + + if (thismp3->tune == nullptr) { + free(mp3buffer); + delete thismp3; + return nullptr; + } + + thismp3->mp3buffer = mp3buffer; + + return thismp3; +} + +#else // NO_MP3_PLAYER + +SOUNDCLIP *my_load_mp3(const AssetPath &asset_name, int voll) +{ + return NULL; +} + +SOUNDCLIP *my_load_static_mp3(const AssetPath &asset_name, int voll, bool loop) +{ + return NULL; +} + +#endif // NO_MP3_PLAYER + + + +MYSTATICOGG *thissogg; +SOUNDCLIP *my_load_static_ogg(const AssetPath &asset_name, int voll, bool loop) +{ + // Load via soundcache. + size_t muslen = 0; + char* mp3buffer = get_cached_sound(asset_name, false, muslen); + if (mp3buffer == nullptr) + return nullptr; + + // now, create an OGG structure for it + thissogg = new MYSTATICOGG(); + thissogg->vol = voll; + thissogg->repeat = loop; + thissogg->mp3buffer = mp3buffer; + thissogg->mp3buffersize = muslen; + + thissogg->tune = alogg_create_ogg_from_buffer(mp3buffer, muslen); + + if (thissogg->tune == nullptr) { + thissogg->destroy(); + delete thissogg; + return nullptr; + } + + return thissogg; +} + +MYOGG *thisogg; +SOUNDCLIP *my_load_ogg(const AssetPath &asset_name, int voll) +{ + size_t asset_size; + mp3in = PackfileFromAsset(asset_name, asset_size); + if (mp3in == nullptr) + return nullptr; + + char *tmpbuffer = (char *)malloc(MP3CHUNKSIZE); + if (tmpbuffer == nullptr) { + pack_fclose(mp3in); + return nullptr; + } + + thisogg = new MYOGG(); + thisogg->in = mp3in; + thisogg->vol = voll; + thisogg->chunksize = MP3CHUNKSIZE; + thisogg->last_but_one = 0; + thisogg->last_ms_offs = 0; + thisogg->last_but_one_but_one = 0; + + if (thisogg->chunksize > asset_size) + thisogg->chunksize = asset_size; + + pack_fread(tmpbuffer, thisogg->chunksize, mp3in); + + thisogg->buffer = (char *)tmpbuffer; + thisogg->stream = alogg_create_oggstream(tmpbuffer, thisogg->chunksize, (asset_size < 1)); + + if (thisogg->stream == nullptr) { + free(tmpbuffer); + pack_fclose(mp3in); + delete thisogg; + return nullptr; + } + + return thisogg; +} + + + +MYMIDI *thismidi; +SOUNDCLIP *my_load_midi(const AssetPath &asset_name, int repet) +{ + // The first a midi is played, preload all patches. + if (!thismidi && psp_midi_preload_patches) + load_midi_patches(); + + size_t asset_size; + PACKFILE *pf = PackfileFromAsset(asset_name, asset_size); + if (!pf) + return nullptr; + + MIDI* midiPtr = load_midi_pf(pf); + pack_fclose(pf); + + if (midiPtr == nullptr) + return nullptr; + + thismidi = new MYMIDI(); + thismidi->tune = midiPtr; + thismidi->repeat = (repet != 0); + + return thismidi; +} + + +#ifdef JGMOD_MOD_PLAYER + +MYMOD *thismod = NULL; +SOUNDCLIP *my_load_mod(const char *filname, int repet) +{ + + JGMOD *modPtr = load_mod((char *)filname); + if (modPtr == NULL) + return NULL; + + thismod = new MYMOD(); + thismod->tune = modPtr; + thismod->repeat = (repet != 0); + + return thismod; +} + +int init_mod_player(int numVoices) { + return install_mod(numVoices); +} + +void remove_mod_player() { + remove_mod(); +} + +//#endif // JGMOD_MOD_PLAYER +#elif defined DUMB_MOD_PLAYER + +MYMOD *thismod = nullptr; +SOUNDCLIP *my_load_mod(const AssetPath &asset_name, int repet) +{ + size_t asset_size; + DUMBFILE *df = DUMBfileFromAsset(asset_name, asset_size); + if (!df) + return nullptr; + + DUH *modPtr = nullptr; + // determine the file extension + const char *lastDot = strrchr(asset_name.second, '.'); + if (lastDot == nullptr) + { + dumbfile_close(df); + return nullptr; + } + // get the first char of the extensin + int charAfterDot = toupper(lastDot[1]); + + // use the appropriate loader + if (charAfterDot == 'I') { + modPtr = dumb_read_it(df); + } + else if (charAfterDot == 'X') { + modPtr = dumb_read_xm(df); + } + else if (charAfterDot == 'S') { + modPtr = dumb_read_s3m(df); + } + else if (charAfterDot == 'M') { + modPtr = dumb_read_mod(df); + } + + dumbfile_close(df); + if (modPtr == nullptr) + return nullptr; + + thismod = new MYMOD(); + thismod->tune = modPtr; + thismod->vol = 255; + thismod->repeat = (repet != 0); + + return thismod; +} + +int init_mod_player(int numVoices) { + dumb_register_packfiles(); + return 0; +} + +void remove_mod_player() { + dumb_exit(); +} + +#endif // DUMB_MOD_PLAYER diff --git a/engines/ags/engine/media/audio/sound.h b/engines/ags/engine/media/audio/sound.h new file mode 100644 index 00000000000..2a644159fb7 --- /dev/null +++ b/engines/ags/engine/media/audio/sound.h @@ -0,0 +1,35 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// ACSOUND - AGS sound system wrapper +// +//============================================================================= + +#ifndef __AC_SOUND_H +#define __AC_SOUND_H + +#include "ac/asset_helper.h" +#include "media/audio/soundclip.h" + +SOUNDCLIP *my_load_wave(const AssetPath &asset_name, int voll, int loop); +SOUNDCLIP *my_load_mp3(const AssetPath &asset_name, int voll); +SOUNDCLIP *my_load_static_mp3(const AssetPath &asset_name, int voll, bool loop); +SOUNDCLIP *my_load_static_ogg(const AssetPath &asset_name, int voll, bool loop); +SOUNDCLIP *my_load_ogg(const AssetPath &asset_name, int voll); +SOUNDCLIP *my_load_midi(const AssetPath &asset_name, int repet); +SOUNDCLIP *my_load_mod(const AssetPath &asset_name, int repet); + +extern int use_extra_sound_offset; + +#endif // __AC_SOUND_H diff --git a/engines/ags/engine/media/audio/soundcache.cpp b/engines/ags/engine/media/audio/soundcache.cpp new file mode 100644 index 00000000000..3f59917c120 --- /dev/null +++ b/engines/ags/engine/media/audio/soundcache.cpp @@ -0,0 +1,234 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "ac/file.h" +#include "util/wgt2allg.h" +#include "media/audio/soundcache.h" +#include "media/audio/audiointernaldefs.h" +#include "util/mutex.h" +#include "util/mutex_lock.h" +#include "util/string.h" +#include "debug/out.h" + +using namespace Common; + +sound_cache_entry_t* sound_cache_entries = nullptr; +unsigned int sound_cache_counter = 0; + +AGS::Engine::Mutex _sound_cache_mutex; + + +void clear_sound_cache() +{ + AGS::Engine::MutexLock _lock(_sound_cache_mutex); + + if (sound_cache_entries) + { + int i; + for (i = 0; i < psp_audio_cachesize; i++) + { + if (sound_cache_entries[i].data) + { + free(sound_cache_entries[i].data); + sound_cache_entries[i].data = nullptr; + free(sound_cache_entries[i].file_name); + sound_cache_entries[i].file_name = nullptr; + sound_cache_entries[i].reference = 0; + } + } + } + else + { + sound_cache_entries = (sound_cache_entry_t*)malloc(psp_audio_cachesize * sizeof(sound_cache_entry_t)); + memset(sound_cache_entries, 0, psp_audio_cachesize * sizeof(sound_cache_entry_t)); + } +} + +void sound_cache_free(char* buffer, bool is_wave) +{ + AGS::Engine::MutexLock _lock(_sound_cache_mutex); + +#ifdef SOUND_CACHE_DEBUG + Debug::Printf("sound_cache_free(%p %d)\n", buffer, (unsigned int)is_wave); +#endif + int i; + for (i = 0; i < psp_audio_cachesize; i++) + { + if (sound_cache_entries[i].data == buffer) + { + if (sound_cache_entries[i].reference > 0) + sound_cache_entries[i].reference--; + +#ifdef SOUND_CACHE_DEBUG + Debug::Printf("..decreased reference count of slot %d to %d\n", i, sound_cache_entries[i].reference); +#endif + return; + } + } + +#ifdef SOUND_CACHE_DEBUG + Debug::Printf("..freeing uncached sound\n"); +#endif + + // Sound is uncached + if (i == psp_audio_cachesize) + { + if (is_wave) + destroy_sample((SAMPLE*)buffer); + else + free(buffer); + } +} + + +char* get_cached_sound(const AssetPath &asset_name, bool is_wave, size_t &size) +{ + AGS::Engine::MutexLock _lock(_sound_cache_mutex); + +#ifdef SOUND_CACHE_DEBUG + Debug::Printf("get_cached_sound(%s %d)\n", asset_name.first.GetCStr(), (unsigned int)is_wave); +#endif + + size = 0; + + int i; + for (i = 0; i < psp_audio_cachesize; i++) + { + if (sound_cache_entries[i].data == nullptr) + continue; + + if (strcmp(asset_name.second, sound_cache_entries[i].file_name) == 0) + { +#ifdef SOUND_CACHE_DEBUG + Debug::Printf("..found in slot %d\n", i); +#endif + sound_cache_entries[i].reference++; + sound_cache_entries[i].last_used = sound_cache_counter++; + size = sound_cache_entries[i].size; + + return sound_cache_entries[i].data; + } + } + + // Not found + PACKFILE *mp3in = nullptr; + SAMPLE* wave = nullptr; + + if (is_wave) + { + PACKFILE *wavin = PackfileFromAsset(asset_name, size); + if (wavin != nullptr) + { + wave = load_wav_pf(wavin); + pack_fclose(wavin); + } + } + else + { + mp3in = PackfileFromAsset(asset_name, size); + if (mp3in == nullptr) + { + return nullptr; + } + } + + // Find free slot + for (i = 0; i < psp_audio_cachesize; i++) + { + if (sound_cache_entries[i].data == nullptr) + break; + } + + // No free slot? + if (i == psp_audio_cachesize) + { + unsigned int oldest = sound_cache_counter; + int index = -1; + + for (i = 0; i < psp_audio_cachesize; i++) + { + if (sound_cache_entries[i].reference == 0) + { + if (sound_cache_entries[i].last_used < oldest) + { + oldest = sound_cache_entries[i].last_used; + index = i; + } + } + } + + i = index; + } + + // Load new file + char* newdata; + + if (is_wave) + { + size = 0; // ??? CHECKME + newdata = (char*)wave; + } + else + { + newdata = (char *)malloc(size); + + if (newdata == nullptr) + { + pack_fclose(mp3in); + return nullptr; + } + + pack_fread(newdata, size, mp3in); + pack_fclose(mp3in); + } + + if (i == -1) + { + // No cache slot empty, return uncached data +#ifdef SOUND_CACHE_DEBUG + Debug::Printf("..loading uncached\n"); +#endif + return newdata; + } + else + { + // Add to cache, free old sound first +#ifdef SOUND_CACHE_DEBUG + Debug::Printf("..loading cached in slot %d\n", i); +#endif + + if (sound_cache_entries[i].data) { + if (sound_cache_entries[i].is_wave) + destroy_sample((SAMPLE*)sound_cache_entries[i].data); + else + free(sound_cache_entries[i].data); + } + + sound_cache_entries[i].size = size; + sound_cache_entries[i].data = newdata; + + if (sound_cache_entries[i].file_name) + free(sound_cache_entries[i].file_name); + sound_cache_entries[i].file_name = (char*)malloc(strlen(asset_name.second) + 1); + strcpy(sound_cache_entries[i].file_name, asset_name.second); + sound_cache_entries[i].reference = 1; + sound_cache_entries[i].last_used = sound_cache_counter++; + sound_cache_entries[i].is_wave = is_wave; + + return sound_cache_entries[i].data; + } + +} diff --git a/engines/ags/engine/media/audio/soundcache.h b/engines/ags/engine/media/audio/soundcache.h new file mode 100644 index 00000000000..ebdb67c69a9 --- /dev/null +++ b/engines/ags/engine/media/audio/soundcache.h @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_SOUNDCACHE_H +#define __AC_SOUNDCACHE_H + +#include "ac/asset_helper.h" + +// PSP: A simple sound cache. The size can be configured in the config file. +// The data rate while reading from disk on the PSP is usually between 500 to 900 kiB/s, +// caching the last used sound files therefore improves game performance. + +//#define SOUND_CACHE_DEBUG + +typedef struct +{ + char* file_name; + int number; + int free; + unsigned int last_used; + unsigned int size; + char* data; + int reference; + bool is_wave; +} sound_cache_entry_t; + +extern int psp_use_sound_cache; +extern int psp_sound_cache_max_size; +extern int psp_audio_cachesize; +extern int psp_midi_preload_patches; + +void clear_sound_cache(); +void sound_cache_free(char* buffer, bool is_wave); +char* get_cached_sound(const AssetPath &asset_name, bool is_wave, size_t &size); + + +#endif // __AC_SOUNDCACHE_H diff --git a/engines/ags/engine/media/audio/soundclip.cpp b/engines/ags/engine/media/audio/soundclip.cpp new file mode 100644 index 00000000000..844eb0fa918 --- /dev/null +++ b/engines/ags/engine/media/audio/soundclip.cpp @@ -0,0 +1,80 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/wgt2allg.h" +#include "media/audio/audio.h" +#include "media/audio/audiodefines.h" +#include "media/audio/soundclip.h" +#include "media/audio/audiointernaldefs.h" + +int SOUNDCLIP::play_from(int position) +{ + int retVal = play(); + if ((retVal != 0) && (position > 0)) + { + seek(position); + } + return retVal; +} + +void SOUNDCLIP::set_panning(int newPanning) { + if (!is_playing()) { return; } + + int voice = get_voice(); + if (voice >= 0) { + voice_set_pan(voice, newPanning); + panning = newPanning; + } +} + +void SOUNDCLIP::pause() { + if (state_ != SoundClipPlaying) { return; } + + int voice = get_voice(); + if (voice >= 0) { + voice_stop(voice); + state_ = SoundClipPaused; + } +} + +void SOUNDCLIP::resume() { + if (state_ != SoundClipPaused) { return; } + + int voice = get_voice(); + if (voice >= 0) { + voice_start(voice); + state_ = SoundClipPlaying; + } +} + +SOUNDCLIP::SOUNDCLIP() { + state_ = SoundClipInitial; + priority = 50; + panning = 128; + panningAsPercentage = 0; + speed = 1000; + sourceClipType = 0; + sourceClip = nullptr; + vol = 0; + volAsPercentage = 0; + volModifier = 0; + muted = false; + repeat = false; + xSource = -1; + ySource = -1; + maximumPossibleDistanceAway = 0; + directionalVolModifier = 0; +} + +SOUNDCLIP::~SOUNDCLIP() = default; diff --git a/engines/ags/engine/media/audio/soundclip.h b/engines/ags/engine/media/audio/soundclip.h new file mode 100644 index 00000000000..36a1166929d --- /dev/null +++ b/engines/ags/engine/media/audio/soundclip.h @@ -0,0 +1,170 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// ACSOUND - AGS sound system wrapper +// +//============================================================================= + +#ifndef __AC_SOUNDCLIP_H +#define __AC_SOUNDCLIP_H + +#include "util/mutex.h" + +// JJS: This is needed for the derieved classes +extern volatile int psp_audio_multithreaded; + +// TODO: one of the biggest problems with sound clips currently is that it +// provides several methods of applying volume, which may ignore or override +// each other, and does not shape a consistent interface. +// Improving this situation is only possible with massive refactory of +// sound clip use, taking backwards-compatible audio system in account. + +enum SoundClipState { SoundClipInitial, SoundClipPlaying, SoundClipPaused, SoundClipStopped }; + +struct SOUNDCLIP +{ + int priority; + int sourceClipType; + // absolute volume, set by implementations only! + int vol; + // current relative volume, in percents + int volAsPercentage; + // volModifier is used when there's a need to temporarily change and + // the restore the clip's absolute volume (vol) + int volModifier; + int panning; + int panningAsPercentage; + int xSource, ySource; + int maximumPossibleDistanceAway; + int directionalVolModifier; + bool repeat; + void *sourceClip; + + virtual void poll() = 0; + virtual void destroy() = 0; + // apply volume directly to playback; volume is given in units of 255 + // NOTE: this completely ignores volAsPercentage and muted property + virtual void set_volume(int) = 0; + virtual void seek(int) = 0; + virtual int get_pos() = 0; // return 0 to indicate seek not supported + virtual int get_pos_ms() = 0; // this must always return valid value if poss + virtual int get_length_ms() = 0; // return total track length in ms (or 0) + virtual int get_sound_type() = 0; + virtual int play() = 0; + + virtual int play_from(int position); + + virtual void set_panning(int newPanning); + virtual void set_speed(int new_speed) { speed = new_speed; } + + virtual void pause(); + virtual void resume(); + + inline bool is_playing() const { return state_ == SoundClipPlaying || state_ == SoundClipPaused; } + + inline int get_speed() const + { + return speed; + } + + // Gets clip's volume property, as percentage (0 - 100); + // note this may not be the real volume of playback (which could e.g. be muted) + inline int get_volume() const + { + return volAsPercentage; + } + + inline bool is_muted() const + { + return muted; + } + + // Sets the current volume property, as percentage (0 - 100). + inline void set_volume_percent(int volume) + { + volAsPercentage = volume; + if (!muted) + set_volume((volume * 255) / 100); + } + + // Explicitly defines both percentage and absolute volume value, + // without calculating it from given percentage. + // NOTE: this overrides the mute + inline void set_volume_direct(int vol_percent, int vol_absolute) + { + muted = false; + volAsPercentage = vol_percent; + set_volume(vol_absolute); + } + + // Mutes sound clip, while preserving current volume property + // for the future reference; when unmuted, that property is + // used to restart previous volume. + inline void set_mute(bool enable) + { + muted = enable; + if (enable) + set_volume(0); + else + set_volume((volAsPercentage * 255) / 100); + } + + // Apply arbitrary permanent volume modifier, in absolute units (0 - 255); + // this is distinct value that is used in conjunction with current volume + // (can be both positive and negative). + inline void apply_volume_modifier(int mod) + { + volModifier = mod; + adjust_volume(); + } + + // Apply permanent directional volume modifier, in absolute units (0 - 255) + // this is distinct value that is used in conjunction with current volume + // (can be both positive and negative). + inline void apply_directional_modifier(int mod) + { + directionalVolModifier = mod; + adjust_volume(); + } + + virtual void adjust_volume() = 0; + + SOUNDCLIP(); + virtual ~SOUNDCLIP(); + + +protected: + + SoundClipState state_; + + // mute mode overrides the volume; if set, any volume assigned is stored + // in properties, but not applied to playback itself + bool muted; + + // speed of playback, in clip ms per real second + int speed; + + // Return the allegro voice number (or -1 if none) + // Used by generic pause/resume functions. + virtual int get_voice() = 0; + + // helper function for calculating volume with applied modifiers + inline int get_final_volume() const + { + int final_vol = vol + volModifier + directionalVolModifier; + return final_vol >= 0 ? final_vol : 0; + } +}; + +#endif // __AC_SOUNDCLIP_H diff --git a/engines/ags/engine/media/video/VMR9Graph.h b/engines/ags/engine/media/video/VMR9Graph.h new file mode 100644 index 00000000000..4b91ac02ff3 --- /dev/null +++ b/engines/ags/engine/media/video/VMR9Graph.h @@ -0,0 +1,150 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// VMR9Graph.h: interface for the CVMR9Graph class. +// +//============================================================================= + +#if !defined(AFX_VMR9GRAPH_H__449FDB5B_6719_4134_B5A7_B651C08D109E__INCLUDED_) +#define AFX_VMR9GRAPH_H__449FDB5B_6719_4134_B5A7_B651C08D109E__INCLUDED_ + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include +#include + +//#pragma comment( lib, "strmiids.lib" ) +//#pragma comment( lib, "Quartz.lib" ) +//#pragma comment( lib, "d3d9.lib" ) +//#pragma comment( lib, "d3dx9.lib" ) + +#define WM_MEDIA_NOTIF (WM_APP + 777) + +class CVMR9Graph +{ + // Constructor / destructor +public: + CVMR9Graph(); + CVMR9Graph(HWND MediaWindow, IDirect3DDevice9 *device, int NumberOfStream = 4); + ~CVMR9Graph(); + + // Methods +public: + // Graph configuration + void SetNumberOfLayer(int nNumberOfLayer); + BOOL SetMediaWindow(HWND MediaWindow); + BOOL SetMediaFile(const char* pszFileName, bool withSound, int nLayer = 0); + BOOL PreserveAspectRatio(BOOL bPreserve = TRUE); + IBaseFilter* AddFilter(const char* pszName, const GUID& clsid); + + // Graph control + BOOL PlayGraph(); + BOOL StopGraph(); + BOOL ResetGraph(); + OAFilterState GetState(); + IMediaEvent* GetPtrMediaEvent(); + IMediaControl* GetPtrMediaControl(); + IMediaSeeking* GetPtrMediaSeeking(); + IBasicAudio* GetPtrBasicAudio(); + + + // Layer control + BOOL GetVideoRect(LPRECT pRect); + int GetAlphaLayer(int nLayer); + BOOL SetAlphaLayer(int nLayer, int nAlpha); + DWORD GetLayerZOrder(int nLayer); + BOOL SetLayerZOrder(int nLayer, DWORD dwZOrder); + BOOL SetLayerRect(int nLayer, RECT layerRect); + + // Bitmap control + BOOL SetBitmapParams(int nAlpha, COLORREF cTransColor, RECT bitmapRect); + + // Reflected from window + BOOL Repaint(); + BOOL Resize(); + + // helper + LPCTSTR GetLastError(); + + // Internal + BOOL BuildAndRenderGraph(bool withSound); + +protected: + // INIT helper methods + void InitDefaultValues(); + void ReleaseAllInterfaces(); + + // GRAPH methods + BOOL BuildFilterGraph(bool withSound); + BOOL BuildVMR(); + BOOL BuildSoundRenderer(); + BOOL RenderGraph(); + + // DIRECT3D methods + BOOL BuildDirect3d(); + + + // LAYER helper methods + BOOL IsValidLayer(int nLayer); + VMR9NormalizedRect NormalizeRect(LPRECT pRect); + + // DSOW helper methods + HRESULT AddToRot(IUnknown *pUnkGraph); + void RemoveFromRot(); + IPin* GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir); + void ReportError(const char* pszError, HRESULT hrCode); + HRESULT GetNextFilter(IBaseFilter *pFilter, PIN_DIRECTION Dir, IBaseFilter **ppNext); + BOOL RemoveFilterChain(IBaseFilter* pFilter, IBaseFilter* pStopFilter); + HRESULT AddFilterByClsid(IGraphBuilder *pGraph, LPCWSTR wszName, const GUID& clsid, IBaseFilter **ppF); + + // Attributes +public: + bool UseAVISound; + +protected: + DWORD m_dwRotId; + char m_pszErrorDescription[1024+MAX_ERROR_TEXT_LEN]; + int m_nNumberOfStream; + const char* m_pszFileName; + long m_oldWndProc; + // MEDIA WINDOW + HWND m_hMediaWindow; + // SRC interfaces array + IBaseFilter* m_srcFilterArray[10]; + // SOUND interfaces + IBaseFilter* m_pDirectSoundFilter; + // GRAPH interfaces + IUnknown* m_pGraphUnknown; + IGraphBuilder* m_pGraphBuilder; + IFilterGraph* m_pFilterGraph; + IFilterGraph2* m_pFilterGraph2; + IMediaControl* m_pMediaControl; + IMediaSeeking* m_pMediaSeeking; + //IMediaEvent* m_pMediaEvent; + IMediaEventEx* m_pMediaEventEx; + // VMR9 interfaces + IBaseFilter* m_pVMRBaseFilter; + IVMRFilterConfig9* m_pVMRFilterConfig; + IVMRMixerBitmap9* m_pVMRMixerBitmap; + IVMRMixerControl9* m_pVMRMixerControl; + IVMRMonitorConfig9* m_pVMRMonitorConfig; + IVMRWindowlessControl9* m_pVMRWindowlessControl; + // DIRECT3D interfaces + //IDirect3DDevice9* m_pD3DDevice; + IDirect3DSurface9* m_pD3DSurface; +}; + +#endif // !defined(AFX_VMR9GRAPH_H__449FDB5B_6719_4134_B5A7_B651C08D109E__INCLUDED_) diff --git a/engines/ags/engine/media/video/video.cpp b/engines/ags/engine/media/video/video.cpp new file mode 100644 index 00000000000..e2e1ebfc62c --- /dev/null +++ b/engines/ags/engine/media/video/video.cpp @@ -0,0 +1,431 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "media/video/video.h" + +#ifndef AGS_NO_VIDEO_PLAYER + +#include "apeg.h" +#include "core/platform.h" +#define AGS_FLI_FROM_PACK_FILE ((ALLEGRO_DATE >= 20190303) || \ + AGS_PLATFORM_OS_WINDOWS || AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_MACOS) + +#include "debug/debug_log.h" +#include "debug/out.h" +#include "ac/asset_helper.h" +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/game_version.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_display.h" +#include "ac/mouse.h" +#include "ac/sys_events.h" +#include "ac/runtime_defines.h" +#include "ac/system.h" +#include "core/assetmanager.h" +#include "gfx/bitmap.h" +#include "gfx/ddb.h" +#include "gfx/graphicsdriver.h" +#include "main/game_run.h" +#include "util/stream.h" +#include "media/audio/audio_system.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + + +extern GameSetupStruct game; +extern IGraphicsDriver *gfxDriver; +extern int psp_video_framedrop; + +enum VideoPlaybackType +{ + kVideoNone, + kVideoFlic, + kVideoTheora +}; + +VideoPlaybackType video_type = kVideoNone; + +// FLIC player start +Bitmap *fli_buffer = nullptr; +short fliwidth,fliheight; +int canabort=0, stretch_flc = 1; +Bitmap *hicol_buf=nullptr; +IDriverDependantBitmap *fli_ddb = nullptr; +Bitmap *fli_target = nullptr; +int fliTargetWidth, fliTargetHeight; +int check_if_user_input_should_cancel_video() +{ + int key, mbut, mwheelz; + if (run_service_key_controls(key)) { + if ((key==27) && (canabort==1)) + return 1; + if (canabort >= 2) + return 1; // skip on any key + } + if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0 && canabort == 3) { + return 1; // skip on mouse click + } + return 0; +} + +#if AGS_PLATFORM_OS_WINDOWS +int __cdecl fli_callback() { +#else +extern "C" int fli_callback() { +#endif + Bitmap *usebuf = fli_buffer; + + update_audio_system_on_game_loop (); + + if (game.color_depth > 1) { + hicol_buf->Blit(fli_buffer,0,0,0,0,fliwidth,fliheight); + usebuf=hicol_buf; + } + + const Rect &view = play.GetMainViewport(); + if (stretch_flc == 0) + fli_target->Blit(usebuf, 0,0, view.GetWidth()/2-fliwidth/2, view.GetHeight()/2-fliheight/2, view.GetWidth(), view.GetHeight()); + else + fli_target->StretchBlt(usebuf, RectWH(0,0,fliwidth,fliheight), RectWH(0,0, view.GetWidth(), view.GetHeight())); + + gfxDriver->UpdateDDBFromBitmap(fli_ddb, fli_target, false); + gfxDriver->DrawSprite(0, 0, fli_ddb); + render_to_screen(); + + return check_if_user_input_should_cancel_video(); +} + +void play_flc_file(int numb,int playflags) { + color oldpal[256]; + + // AGS 2.x: If the screen is faded out, fade in again when playing a movie. + if (loaded_game_file_version <= kGameVersion_272) + play.screen_is_faded_out = 0; + + if (play.fast_forward) + return; + + get_palette_range(oldpal, 0, 255); + + int clearScreenAtStart = 1; + canabort = playflags % 10; + playflags -= canabort; + + if (canabort == 2) // convert to PlayVideo-compatible setting + canabort = 3; + + if (playflags % 100 == 0) + stretch_flc = 1; + else + stretch_flc = 0; + + if (playflags / 100) + clearScreenAtStart = 0; + + String flicname = String::FromFormat("flic%d.flc", numb); + Stream *in = AssetManager::OpenAsset(flicname); + if (!in) + { + flicname.Format("flic%d.fli", numb); + in = AssetManager::OpenAsset(flicname); + } + if (!in) + { + debug_script_warn("FLIC animation flic%d.flc nor flic%d.fli not found", numb, numb); + return; + } + + in->Seek(8); + fliwidth = in->ReadInt16(); + fliheight = in->ReadInt16(); + delete in; + + if (game.color_depth > 1) { + hicol_buf=BitmapHelper::CreateBitmap(fliwidth,fliheight,game.GetColorDepth()); + hicol_buf->Clear(); + } + // override the stretch option if necessary + const Rect &view = play.GetMainViewport(); + if ((fliwidth == view.GetWidth()) && (fliheight == view.GetHeight())) + stretch_flc = 0; + else if ((fliwidth > view.GetWidth()) || (fliheight >view.GetHeight())) + stretch_flc = 1; + fli_buffer=BitmapHelper::CreateBitmap(fliwidth,fliheight,8); + if (fli_buffer==nullptr) quit("Not enough memory to play animation"); + fli_buffer->Clear(); + + if (clearScreenAtStart) + { + if (gfxDriver->UsesMemoryBackBuffer()) + { + Bitmap *screen_bmp = gfxDriver->GetMemoryBackBuffer(); + screen_bmp->Clear(); + } + render_to_screen(); + } + + video_type = kVideoFlic; + fli_target = BitmapHelper::CreateBitmap(view.GetWidth(), view.GetHeight(), game.GetColorDepth()); + fli_ddb = gfxDriver->CreateDDBFromBitmap(fli_target, false, true); + + // TODO: find a better solution. + // Make only certain versions of the engineuse play_fli_pf from the patched version of Allegro for now. + // Add more versions as their Allegro lib becomes patched too, or they use newer version of Allegro 4. + // Ports can still play FLI if separate file is put into game's directory. +#if AGS_FLI_FROM_PACK_FILE + size_t asset_size; + PACKFILE *pf = PackfileFromAsset(AssetPath("", flicname), asset_size); + if (play_fli_pf(pf, (BITMAP*)fli_buffer->GetAllegroBitmap(), fli_callback)==FLI_ERROR) +#else + if (play_fli(flicname, (BITMAP*)fli_buffer->GetAllegroBitmap(), 0, fli_callback)==FLI_ERROR) +#endif + { + // This is not a fatal error that should prevent the game from continuing + Debug::Printf("FLI/FLC animation play error"); + } +#if AGS_FLI_FROM_PACK_FILE + pack_fclose(pf); +#endif + + video_type = kVideoNone; + delete fli_buffer; + fli_buffer = nullptr; + // NOTE: the screen bitmap could change in the meanwhile, if the display mode has changed + if (gfxDriver->UsesMemoryBackBuffer()) + { + Bitmap *screen_bmp = gfxDriver->GetMemoryBackBuffer(); + screen_bmp->Clear(); + } + set_palette_range(oldpal, 0, 255, 0); + render_to_screen(); + + delete fli_target; + gfxDriver->DestroyDDB(fli_ddb); + fli_target = nullptr; + fli_ddb = nullptr; + + + delete hicol_buf; + hicol_buf=nullptr; + // SetVirtualScreen(screen); wputblock(0,0,backbuffer,0); + while (ags_mgetbutton()!=NONE) { } // clear any queued mouse events. + invalidate_screen(); +} + +// FLIC player end + +// Theora player begin +// TODO: find a way to take Bitmap here? +Bitmap gl_TheoraBuffer; +int theora_playing_callback(BITMAP *theoraBuffer) +{ + if (theoraBuffer == nullptr) + { + // No video, only sound + return check_if_user_input_should_cancel_video(); + } + + gl_TheoraBuffer.WrapAllegroBitmap(theoraBuffer, true); + + int drawAtX = 0, drawAtY = 0; + const Rect &viewport = play.GetMainViewport(); + if (fli_ddb == nullptr) + { + fli_ddb = gfxDriver->CreateDDBFromBitmap(&gl_TheoraBuffer, false, true); + } + if (stretch_flc) + { + drawAtX = viewport.GetWidth() / 2 - fliTargetWidth / 2; + drawAtY = viewport.GetHeight() / 2 - fliTargetHeight / 2; + if (!gfxDriver->HasAcceleratedTransform()) + { + fli_target->StretchBlt(&gl_TheoraBuffer, RectWH(0, 0, gl_TheoraBuffer.GetWidth(), gl_TheoraBuffer.GetHeight()), + RectWH(drawAtX, drawAtY, fliTargetWidth, fliTargetHeight)); + gfxDriver->UpdateDDBFromBitmap(fli_ddb, fli_target, false); + drawAtX = 0; + drawAtY = 0; + } + else + { + gfxDriver->UpdateDDBFromBitmap(fli_ddb, &gl_TheoraBuffer, false); + fli_ddb->SetStretch(fliTargetWidth, fliTargetHeight, false); + } + } + else + { + gfxDriver->UpdateDDBFromBitmap(fli_ddb, &gl_TheoraBuffer, false); + drawAtX = viewport.GetWidth() / 2 - gl_TheoraBuffer.GetWidth() / 2; + drawAtY = viewport.GetHeight() / 2 - gl_TheoraBuffer.GetHeight() / 2; + } + + gfxDriver->DrawSprite(drawAtX, drawAtY, fli_ddb); + update_audio_system_on_game_loop (); + render_to_screen(); + + return check_if_user_input_should_cancel_video(); +} + +// +// Theora stream reader callbacks. We need these because APEG library does not +// provide means to supply user's PACKFILE directly. +// +// Open stream for reading (return suggested cache buffer size). +int apeg_stream_init(void *ptr) +{ + return ptr != nullptr ? F_BUF_SIZE : 0; +} +// Read requested number of bytes into provided buffer, +// return actual number of bytes managed to read. +int apeg_stream_read(void *buffer, int bytes, void *ptr) +{ + return ((Stream*)ptr)->Read(buffer, bytes); +} +// Skip requested number of bytes +void apeg_stream_skip(int bytes, void *ptr) +{ + ((Stream*)ptr)->Seek(bytes); +} +// + +APEG_STREAM* get_theora_size(Stream *video_stream, int *width, int *height) +{ + APEG_STREAM* oggVid = apeg_open_stream_ex(video_stream); + if (oggVid != nullptr) + { + apeg_get_video_size(oggVid, width, height); + } + else + { + *width = 0; + *height = 0; + } + return oggVid; +} + +// TODO: use shared utility function for placing rect in rect +void calculate_destination_size_maintain_aspect_ratio(int vidWidth, int vidHeight, int *targetWidth, int *targetHeight) +{ + const Rect &viewport = play.GetMainViewport(); + float aspectRatioVideo = (float)vidWidth / (float)vidHeight; + float aspectRatioScreen = (float)viewport.GetWidth() / (float)viewport.GetHeight(); + + if (aspectRatioVideo == aspectRatioScreen) + { + *targetWidth = viewport.GetWidth(); + *targetHeight = viewport.GetHeight(); + } + else if (aspectRatioVideo > aspectRatioScreen) + { + *targetWidth = viewport.GetWidth(); + *targetHeight = (int)(((float)viewport.GetWidth() / aspectRatioVideo) + 0.5f); + } + else + { + *targetHeight = viewport.GetHeight(); + *targetWidth = (float)viewport.GetHeight() * aspectRatioVideo; + } + +} + +void play_theora_video(const char *name, int skip, int flags) +{ + std::unique_ptr video_stream(AssetManager::OpenAsset(name)); + apeg_set_stream_reader(apeg_stream_init, apeg_stream_read, apeg_stream_skip); + apeg_set_display_depth(game.GetColorDepth()); + // we must disable length detection, otherwise it takes ages to start + // playing if the file is large because it seeks through the whole thing + apeg_disable_length_detection(TRUE); + // Disable framedrop because it can lead to the PSP not playing the video at all. + apeg_enable_framedrop(psp_video_framedrop); + update_polled_stuff_if_runtime(); + + stretch_flc = (flags % 10); + canabort = skip; + apeg_ignore_audio((flags >= 10) ? 1 : 0); + + int videoWidth, videoHeight; + APEG_STREAM *oggVid = get_theora_size(video_stream.get(), &videoWidth, &videoHeight); + + if (videoWidth == 0) + { + Display("Unable to load theora video '%s'", name); + return; + } + + if (flags < 10) + { + stop_all_sound_and_music(); + } + + //fli_buffer = BitmapHelper::CreateBitmap_(scsystem.coldepth, videoWidth, videoHeight); + calculate_destination_size_maintain_aspect_ratio(videoWidth, videoHeight, &fliTargetWidth, &fliTargetHeight); + + if ((fliTargetWidth == videoWidth) && (fliTargetHeight == videoHeight) && (stretch_flc)) + { + // don't need to stretch after all + stretch_flc = 0; + } + + if ((stretch_flc) && (!gfxDriver->HasAcceleratedTransform())) + { + fli_target = BitmapHelper::CreateBitmap(play.GetMainViewport().GetWidth(), play.GetMainViewport().GetHeight(), game.GetColorDepth()); + fli_target->Clear(); + fli_ddb = gfxDriver->CreateDDBFromBitmap(fli_target, false, true); + } + else + { + fli_ddb = nullptr; + } + + update_polled_stuff_if_runtime(); + + if (gfxDriver->UsesMemoryBackBuffer()) + gfxDriver->GetMemoryBackBuffer()->Clear(); + + video_type = kVideoTheora; + if (apeg_play_apeg_stream(oggVid, nullptr, 0, theora_playing_callback) == APEG_ERROR) + { + Display("Error playing theora video '%s'", name); + } + apeg_close_stream(oggVid); + video_type = kVideoNone; + + //destroy_bitmap(fli_buffer); + delete fli_target; + gfxDriver->DestroyDDB(fli_ddb); + fli_target = nullptr; + fli_ddb = nullptr; + invalidate_screen(); +} +// Theora player end + +void video_on_gfxmode_changed() +{ + if (video_type == kVideoFlic) + { + // If the FLIC video is playing, restore its palette + set_palette_range(fli_palette, 0, 255, 0); + } +} + +#else + +void play_theora_video(const char *name, int skip, int flags) {} +void play_flc_file(int numb,int playflags) {} +void video_on_gfxmode_changed() {} + +#endif \ No newline at end of file diff --git a/engines/ags/engine/media/video/video.h b/engines/ags/engine/media/video/video.h new file mode 100644 index 00000000000..1110260968c --- /dev/null +++ b/engines/ags/engine/media/video/video.h @@ -0,0 +1,27 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_MEDIA__VIDEO_H +#define __AGS_EE_MEDIA__VIDEO_H + +void play_theora_video(const char *name, int skip, int flags); +void play_flc_file(int numb,int playflags); + +// Update video playback if the display mode has changed +void video_on_gfxmode_changed(); + +#endif // __AGS_EE_MEDIA__VIDEO_H diff --git a/engines/ags/engine/platform/android/acpland.cpp b/engines/ags/engine/platform/android/acpland.cpp new file mode 100644 index 00000000000..596c4aae577 --- /dev/null +++ b/engines/ags/engine/platform/android/acpland.cpp @@ -0,0 +1,757 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_ANDROID + +#include +#include "platform/base/agsplatformdriver.h" +#include "ac/runtime_defines.h" +#include "main/config.h" +#include "plugin/agsplugin.h" +#include +#include +#include +#include +#include +#include "util/string_compat.h" + + + +#include +#include + +using namespace AGS::Common; + +#define ANDROID_CONFIG_FILENAME "android.cfg" + +bool ReadConfiguration(char* filename, bool read_everything); +void ResetConfiguration(); + +struct AGSAndroid : AGSPlatformDriver { + + virtual int CDPlayerCommand(int cmdd, int datt); + virtual void Delay(int millis); + virtual void DisplayAlert(const char*, ...); + virtual const char *GetAppOutputDirectory(); + virtual unsigned long GetDiskFreeSpaceMB(); + virtual const char* GetNoMouseErrorString(); + virtual bool IsBackendResponsibleForMouseScaling() { return true; } + virtual eScriptSystemOSID GetSystemOSID(); + virtual int InitializeCDPlayer(); + virtual void PostAllegroExit(); + virtual void SetGameWindowIcon(); + virtual void ShutdownCDPlayer(); + virtual void WriteStdOut(const char *fmt, ...); + virtual void WriteStdErr(const char *fmt, ...); +}; + + +//int psp_return_to_menu = 1; +int psp_ignore_acsetup_cfg_file = 1; +int psp_clear_cache_on_room_change = 0; +int psp_rotation = 0; +int psp_config_enabled = 0; +char psp_translation[100]; +char* psp_translations[100]; + +// Mouse option from Allegro. +extern int config_mouse_control_mode; + + +// Graphic options from the Allegro library. +extern int psp_gfx_scaling; +extern int psp_gfx_smoothing; + + +// Audio options from the Allegro library. +unsigned int psp_audio_samplerate = 44100; +int psp_audio_enabled = 1; +volatile int psp_audio_multithreaded = 1; +int psp_audio_cachesize = 10; +int psp_midi_enabled = 1; +int psp_midi_preload_patches = 0; + +int psp_video_framedrop = 0; + +int psp_gfx_renderer = 0; +int psp_gfx_super_sampling = 0; +int psp_gfx_smooth_sprites = 0; + +int psp_debug_write_to_logcat = 0; + +int config_mouse_longclick = 0; + +extern int display_fps; +extern int want_exit; +extern void PauseGame(); +extern void UnPauseGame(); +extern int main(int argc,char*argv[]); + +char android_base_directory[256]; +char android_app_directory[256]; +char psp_game_file_name[256]; +char* psp_game_file_name_pointer = psp_game_file_name; + +bool psp_load_latest_savegame = false; +extern char saveGameDirectory[260]; +extern const char *loadSaveGameOnStartup; +char lastSaveGameName[200]; + + +extern JavaVM* android_jni_vm; +JNIEnv *java_environment; +jobject java_object; +jclass java_class; +jmethodID java_messageCallback; +jmethodID java_blockExecution; +jmethodID java_swapBuffers; +jmethodID java_setRotation; +jmethodID java_enableLongclick; + +bool reset_configuration = false; + +extern "C" +{ + +const int CONFIG_IGNORE_ACSETUP = 0; +const int CONFIG_CLEAR_CACHE = 1; +const int CONFIG_AUDIO_RATE = 2; +const int CONFIG_AUDIO_ENABLED = 3; +const int CONFIG_AUDIO_THREADED = 4; +const int CONFIG_AUDIO_CACHESIZE = 5; +const int CONFIG_MIDI_ENABLED = 6; +const int CONFIG_MIDI_PRELOAD = 7; +const int CONFIG_VIDEO_FRAMEDROP = 8; +const int CONFIG_GFX_RENDERER = 9; +const int CONFIG_GFX_SMOOTHING = 10; +const int CONFIG_GFX_SCALING = 11; +const int CONFIG_GFX_SS = 12; +const int CONFIG_ROTATION = 13; +const int CONFIG_ENABLED = 14; +const int CONFIG_DEBUG_FPS = 15; +const int CONFIG_GFX_SMOOTH_SPRITES = 16; +const int CONFIG_TRANSLATION = 17; +const int CONFIG_DEBUG_LOGCAT = 18; +const int CONFIG_MOUSE_METHOD = 19; +const int CONFIG_MOUSE_LONGCLICK = 20; + +extern void android_debug_printf(const char* format, ...); + +JNIEXPORT jboolean JNICALL + Java_com_bigbluecup_android_PreferencesActivity_readConfigFile(JNIEnv* env, jobject object, jstring directory) +{ + const char* cdirectory = env->GetStringUTFChars(directory, NULL); + chdir(cdirectory); + env->ReleaseStringUTFChars(directory, cdirectory); + + ResetConfiguration(); + + return ReadConfiguration(ANDROID_CONFIG_FILENAME, true); +} + + +JNIEXPORT jboolean JNICALL + Java_com_bigbluecup_android_PreferencesActivity_writeConfigFile(JNIEnv* env, jobject object) +{ + FILE* config = fopen(ANDROID_CONFIG_FILENAME, "wb"); + if (config) + { + fprintf(config, "[misc]\n"); + fprintf(config, "config_enabled = %d\n", psp_config_enabled); + fprintf(config, "rotation = %d\n", psp_rotation); + fprintf(config, "translation = %s\n", psp_translation); + + fprintf(config, "[controls]\n"); + fprintf(config, "mouse_method = %d\n", config_mouse_control_mode); + fprintf(config, "mouse_longclick = %d\n", config_mouse_longclick); + + fprintf(config, "[compatibility]\n"); +// fprintf(config, "ignore_acsetup_cfg_file = %d\n", psp_ignore_acsetup_cfg_file); + fprintf(config, "clear_cache_on_room_change = %d\n", psp_clear_cache_on_room_change); + + fprintf(config, "[sound]\n"); + fprintf(config, "samplerate = %d\n", psp_audio_samplerate ); + fprintf(config, "enabled = %d\n", psp_audio_enabled); + fprintf(config, "threaded = %d\n", psp_audio_multithreaded); + fprintf(config, "cache_size = %d\n", psp_audio_cachesize); + + fprintf(config, "[midi]\n"); + fprintf(config, "enabled = %d\n", psp_midi_enabled); + fprintf(config, "preload_patches = %d\n", psp_midi_preload_patches); + + fprintf(config, "[video]\n"); + fprintf(config, "framedrop = %d\n", psp_video_framedrop); + + fprintf(config, "[graphics]\n"); + fprintf(config, "renderer = %d\n", psp_gfx_renderer); + fprintf(config, "smoothing = %d\n", psp_gfx_smoothing); + fprintf(config, "scaling = %d\n", psp_gfx_scaling); + fprintf(config, "super_sampling = %d\n", psp_gfx_super_sampling); + fprintf(config, "smooth_sprites = %d\n", psp_gfx_smooth_sprites); + + fprintf(config, "[debug]\n"); + fprintf(config, "show_fps = %d\n", (display_fps == 2) ? 1 : 0); + fprintf(config, "logging = %d\n", psp_debug_write_to_logcat); + + fclose(config); + + return true; + } + + return false; +} + + +JNIEXPORT jint JNICALL + Java_com_bigbluecup_android_PreferencesActivity_readIntConfigValue(JNIEnv* env, jobject object, jint id) +{ + switch (id) + { + case CONFIG_IGNORE_ACSETUP: + return psp_ignore_acsetup_cfg_file; + break; + case CONFIG_CLEAR_CACHE: + return psp_clear_cache_on_room_change; + break; + case CONFIG_AUDIO_RATE: + return psp_audio_samplerate; + break; + case CONFIG_AUDIO_ENABLED: + return psp_audio_enabled; + break; + case CONFIG_AUDIO_THREADED: + return psp_audio_multithreaded; + break; + case CONFIG_AUDIO_CACHESIZE: + return psp_audio_cachesize; + break; + case CONFIG_MIDI_ENABLED: + return psp_midi_enabled; + break; + case CONFIG_MIDI_PRELOAD: + return psp_midi_preload_patches; + break; + case CONFIG_VIDEO_FRAMEDROP: + return psp_video_framedrop; + break; + case CONFIG_GFX_RENDERER: + return psp_gfx_renderer; + break; + case CONFIG_GFX_SMOOTHING: + return psp_gfx_smoothing; + break; + case CONFIG_GFX_SCALING: + return psp_gfx_scaling; + break; + case CONFIG_GFX_SS: + return psp_gfx_super_sampling; + break; + case CONFIG_GFX_SMOOTH_SPRITES: + return psp_gfx_smooth_sprites; + break; + case CONFIG_ROTATION: + return psp_rotation; + break; + case CONFIG_ENABLED: + return psp_config_enabled; + break; + case CONFIG_DEBUG_FPS: + return (display_fps == 2) ? 1 : 0; + break; + case CONFIG_DEBUG_LOGCAT: + return psp_debug_write_to_logcat; + break; + case CONFIG_MOUSE_METHOD: + return config_mouse_control_mode; + break; + case CONFIG_MOUSE_LONGCLICK: + return config_mouse_longclick; + break; + default: + return 0; + break; + } +} + + +JNIEXPORT jstring JNICALL + Java_com_bigbluecup_android_PreferencesActivity_readStringConfigValue(JNIEnv* env, jobject object, jint id, jstring value) +{ + switch (id) + { + case CONFIG_TRANSLATION: + return env->NewStringUTF(&psp_translation[0]); + break; + } +} + + +JNIEXPORT void JNICALL + Java_com_bigbluecup_android_PreferencesActivity_setIntConfigValue(JNIEnv* env, jobject object, jint id, jint value) +{ + switch (id) + { + case CONFIG_IGNORE_ACSETUP: + psp_ignore_acsetup_cfg_file = value; + break; + case CONFIG_CLEAR_CACHE: + psp_clear_cache_on_room_change = value; + break; + case CONFIG_AUDIO_RATE: + psp_audio_samplerate = value; + break; + case CONFIG_AUDIO_ENABLED: + psp_audio_enabled = value; + break; + case CONFIG_AUDIO_THREADED: + psp_audio_multithreaded = value; + break; + case CONFIG_AUDIO_CACHESIZE: + psp_audio_cachesize = value; + break; + case CONFIG_MIDI_ENABLED: + psp_midi_enabled = value; + break; + case CONFIG_MIDI_PRELOAD: + psp_midi_preload_patches = value; + break; + case CONFIG_VIDEO_FRAMEDROP: + psp_video_framedrop = value; + break; + case CONFIG_GFX_RENDERER: + psp_gfx_renderer = value; + break; + case CONFIG_GFX_SMOOTHING: + psp_gfx_smoothing = value; + break; + case CONFIG_GFX_SCALING: + psp_gfx_scaling = value; + break; + case CONFIG_GFX_SS: + psp_gfx_super_sampling = value; + break; + case CONFIG_GFX_SMOOTH_SPRITES: + psp_gfx_smooth_sprites = value; + break; + case CONFIG_ROTATION: + psp_rotation = value; + break; + case CONFIG_ENABLED: + psp_config_enabled = value; + break; + case CONFIG_DEBUG_FPS: + display_fps = (value == 1) ? 2 : 0; + break; + case CONFIG_DEBUG_LOGCAT: + psp_debug_write_to_logcat = value; + break; + case CONFIG_MOUSE_METHOD: + config_mouse_control_mode = value; + break; + case CONFIG_MOUSE_LONGCLICK: + config_mouse_longclick = value; + break; + default: + break; + } +} + + +JNIEXPORT void JNICALL + Java_com_bigbluecup_android_PreferencesActivity_setStringConfigValue(JNIEnv* env, jobject object, jint id, jstring value) +{ + const char* cstring = env->GetStringUTFChars(value, NULL); + + switch (id) + { + case CONFIG_TRANSLATION: + strcpy(psp_translation, cstring); + break; + default: + break; + } + + env->ReleaseStringUTFChars(value, cstring); +} + + +JNIEXPORT jint JNICALL +Java_com_bigbluecup_android_PreferencesActivity_getAvailableTranslations(JNIEnv* env, jobject object, jobjectArray translations) +{ + int i = 0; + int length; + DIR* dir; + struct dirent* entry; + char buffer[200]; + + dir = opendir("."); + if (dir) + { + while ((entry = readdir(dir)) != 0) + { + length = strlen(entry->d_name); + if (length > 4) + { + if (ags_stricmp(&entry->d_name[length - 4], ".tra") == 0) + { + memset(buffer, 0, 200); + strncpy(buffer, entry->d_name, length - 4); + psp_translations[i] = (char*)malloc(strlen(buffer) + 1); + strcpy(psp_translations[i], buffer); + env->SetObjectArrayElement(translations, i, env->NewStringUTF(&buffer[0])); + i++; + } + } + } + closedir(dir); + } + + return i; +} + + +JNIEXPORT void JNICALL + Java_com_bigbluecup_android_EngineGlue_pauseEngine(JNIEnv* env, jobject object) +{ + PauseGame(); +} + +JNIEXPORT void JNICALL + Java_com_bigbluecup_android_EngineGlue_resumeEngine(JNIEnv* env, jobject object) +{ + UnPauseGame(); +} + + +JNIEXPORT void JNICALL + Java_com_bigbluecup_android_EngineGlue_shutdownEngine(JNIEnv* env, jobject object) +{ + want_exit = 1; +} + + +JNIEXPORT jboolean JNICALL + Java_com_bigbluecup_android_EngineGlue_startEngine(JNIEnv* env, jobject object, jclass stringclass, jstring filename, jstring directory, jstring appDirectory, jboolean loadLastSave) +{ + // Get JNI interfaces. + java_object = env->NewGlobalRef(object); + java_environment = env; + java_class = (jclass)java_environment->NewGlobalRef(java_environment->GetObjectClass(object)); + java_messageCallback = java_environment->GetMethodID(java_class, "showMessage", "(Ljava/lang/String;)V"); + java_blockExecution = java_environment->GetMethodID(java_class, "blockExecution", "()V"); + java_setRotation = java_environment->GetMethodID(java_class, "setRotation", "(I)V"); + java_enableLongclick = java_environment->GetMethodID(java_class, "enableLongclick", "()V"); + + // Initialize JNI for Allegro. + android_allegro_initialize_jni(java_environment, java_class, java_object); + + // Get the file to run from Java. + const char* cpath = java_environment->GetStringUTFChars(filename, NULL); + strcpy(psp_game_file_name, cpath); + java_environment->ReleaseStringUTFChars(filename, cpath); + + // Get the base directory (usually "/sdcard/ags"). + const char* cdirectory = java_environment->GetStringUTFChars(directory, NULL); + chdir(cdirectory); + strcpy(android_base_directory, cdirectory); + java_environment->ReleaseStringUTFChars(directory, cdirectory); + + // Get the app directory (something like "/data/data/com.bigbluecup.android.launcher") + const char* cappDirectory = java_environment->GetStringUTFChars(appDirectory, NULL); + strcpy(android_app_directory, cappDirectory); + java_environment->ReleaseStringUTFChars(appDirectory, cappDirectory); + + // Reset configuration. + ResetConfiguration(); + + // Read general configuration. + ReadConfiguration(ANDROID_CONFIG_FILENAME, true); + + // Get the games path. + char path[256]; + strcpy(path, psp_game_file_name); + int lastindex = strlen(path) - 1; + while (path[lastindex] != '/') + { + path[lastindex] = 0; + lastindex--; + } + chdir(path); + + setenv("ULTRADIR", "..", 1); + + // Read game specific configuration. + ReadConfiguration(ANDROID_CONFIG_FILENAME, false); + + // Set the screen rotation. + if (psp_rotation > 0) + java_environment->CallVoidMethod(java_object, java_setRotation, psp_rotation); + + if (config_mouse_longclick > 0) + java_environment->CallVoidMethod(java_object, java_enableLongclick); + + psp_load_latest_savegame = loadLastSave; + + // Start the engine main function. + main(1, &psp_game_file_name_pointer); + + // Explicitly quit here, otherwise the app will hang forever. + exit(0); + + return true; +} + + +void selectLatestSavegame() +{ + DIR* dir; + struct dirent* entry; + struct stat statBuffer; + char buffer[200]; + time_t lastTime = 0; + + dir = opendir(saveGameDirectory); + + if (dir) + { + while ((entry = readdir(dir)) != 0) + { + if (ags_strnicmp(entry->d_name, "agssave", 7) == 0) + { + if (ags_stricmp(entry->d_name, "agssave.999") != 0) + { + strcpy(buffer, saveGameDirectory); + strcat(buffer, entry->d_name); + stat(buffer, &statBuffer); + if (statBuffer.st_mtime > lastTime) + { + strcpy(lastSaveGameName, buffer); + loadSaveGameOnStartup = lastSaveGameName; + lastTime = statBuffer.st_mtime; + } + } + } + } + closedir(dir); + } +} + +} + + +int ReadInteger(int* variable, const ConfigTree &cfg, char* section, char* name, int minimum, int maximum, int default_value) +{ + if (reset_configuration) + { + *variable = default_value; + return 0; + } + + int temp = INIreadint(cfg, section, name); + + if (temp == -1) + return 0; + + if ((temp < minimum) || (temp > maximum)) + temp = default_value; + + *variable = temp; + + return 1; +} + + + +int ReadString(char* variable, const ConfigTree &cfg, char* section, char* name, char* default_value) +{ + if (reset_configuration) + { + strcpy(variable, default_value); + return 0; + } + + String temp; + if (!INIreaditem(cfg, section, name, temp)) + temp = default_value; + + strcpy(variable, temp); + + return 1; +} + + + +void ResetConfiguration() +{ + reset_configuration = true; + + ReadConfiguration(ANDROID_CONFIG_FILENAME, true); + + reset_configuration = false; +} + + + +bool ReadConfiguration(char* filename, bool read_everything) +{ + ConfigTree cfg; + if (IniUtil::Read(filename, cfg) || reset_configuration) + { +// ReadInteger((int*)&psp_disable_powersaving, "misc", "disable_power_saving", 0, 1, 1); + +// ReadInteger((int*)&psp_return_to_menu, "misc", "return_to_menu", 0, 1, 1); + + ReadString(&psp_translation[0], cfg, "misc", "translation", "default"); + + ReadInteger((int*)&psp_config_enabled, cfg, "misc", "config_enabled", 0, 1, 0); + if (!psp_config_enabled && !read_everything) + return true; + + ReadInteger(&psp_debug_write_to_logcat, cfg, "debug", "logging", 0, 1, 0); + ReadInteger(&display_fps, cfg, "debug", "show_fps", 0, 1, 0); + if (display_fps == 1) + display_fps = 2; + + ReadInteger((int*)&psp_rotation, cfg, "misc", "rotation", 0, 2, 0); + +// ReadInteger((int*)&psp_ignore_acsetup_cfg_file, "compatibility", "ignore_acsetup_cfg_file", 0, 1, 0); + ReadInteger((int*)&psp_clear_cache_on_room_change, cfg, "compatibility", "clear_cache_on_room_change", 0, 1, 0); + + ReadInteger((int*)&psp_audio_samplerate, cfg, "sound", "samplerate", 0, 44100, 44100); + ReadInteger((int*)&psp_audio_enabled, cfg, "sound", "enabled", 0, 1, 1); + ReadInteger((int*)&psp_audio_multithreaded, cfg, "sound", "threaded", 0, 1, 1); + ReadInteger((int*)&psp_audio_cachesize, cfg, "sound", "cache_size", 1, 50, 10); + + ReadInteger((int*)&psp_midi_enabled, cfg, "midi", "enabled", 0, 1, 1); + ReadInteger((int*)&psp_midi_preload_patches, cfg, "midi", "preload_patches", 0, 1, 0); + + ReadInteger((int*)&psp_video_framedrop, cfg, "video", "framedrop", 0, 1, 0); + + ReadInteger((int*)&psp_gfx_renderer, cfg, "graphics", "renderer", 0, 2, 0); + ReadInteger((int*)&psp_gfx_smoothing, cfg, "graphics", "smoothing", 0, 1, 1); + ReadInteger((int*)&psp_gfx_scaling, cfg, "graphics", "scaling", 0, 2, 1); + ReadInteger((int*)&psp_gfx_super_sampling, cfg, "graphics", "super_sampling", 0, 1, 0); + ReadInteger((int*)&psp_gfx_smooth_sprites, cfg, "graphics", "smooth_sprites", 0, 1, 0); + + ReadInteger((int*)&config_mouse_control_mode, cfg, "controls", "mouse_method", 0, 1, 0); + ReadInteger((int*)&config_mouse_longclick, cfg, "controls", "mouse_longclick", 0, 1, 1); + + return true; + } + + return false; +} + + + +int AGSAndroid::CDPlayerCommand(int cmdd, int datt) { + return 1;//cd_player_control(cmdd, datt); +} + +void AGSAndroid::DisplayAlert(const char *text, ...) { + char displbuf[2000]; + va_list ap; + va_start(ap, text); + vsprintf(displbuf, text, ap); + va_end(ap); + + // It is possible that this is called from a thread that is not yet known + // to the Java VM. So attach it first before displaying the message. + JNIEnv* thread_env; + android_jni_vm->AttachCurrentThread(&thread_env, NULL); + + __android_log_print(ANDROID_LOG_DEBUG, "AGSNative", "%s", displbuf); + + jstring java_string = thread_env->NewStringUTF(displbuf); + thread_env->CallVoidMethod(java_object, java_messageCallback, java_string); + usleep(1000 * 1000); + thread_env->CallVoidMethod(java_object, java_blockExecution); + +// android_jni_vm->DetachCurrentThread(); +} + +void AGSAndroid::Delay(int millis) { + usleep(millis * 1000); +} + +unsigned long AGSAndroid::GetDiskFreeSpaceMB() { + // placeholder + return 100; +} + +const char* AGSAndroid::GetNoMouseErrorString() { + return "This game requires a mouse. You need to configure and setup your mouse to play this game.\n"; +} + +eScriptSystemOSID AGSAndroid::GetSystemOSID() { + return eOS_Android; +} + +int AGSAndroid::InitializeCDPlayer() { + return 1;//cd_player_init(); +} + +void AGSAndroid::PostAllegroExit() { + java_environment->DeleteGlobalRef(java_class); +} + +void AGSAndroid::SetGameWindowIcon() { + // do nothing +} + +void AGSAndroid::WriteStdOut(const char *fmt, ...) +{ + // TODO: this check should probably be done once when setting up output targets for logging + if (psp_debug_write_to_logcat) + { + va_list args; + va_start(args, fmt); + __android_log_vprint(ANDROID_LOG_DEBUG, "AGSNative", fmt, args); + // NOTE: __android_log_* functions add trailing '\n' + va_end(args); + } +} + +void AGSAndroid::WriteStdErr(const char *fmt, ...) +{ + // TODO: find out if Android needs separate implementation for stderr + if (psp_debug_write_to_logcat) + { + va_list args; + va_start(args, fmt); + __android_log_vprint(ANDROID_LOG_DEBUG, "AGSNative", fmt, args); + // NOTE: __android_log_* functions add trailing '\n' + va_end(args); + } +} + +void AGSAndroid::ShutdownCDPlayer() { + //cd_exit(); +} + +const char *AGSAndroid::GetAppOutputDirectory() +{ + return android_base_directory; +} + +AGSPlatformDriver* AGSPlatformDriver::GetDriver() { + if (instance == NULL) + instance = new AGSAndroid(); + + return instance; +} + +#endif diff --git a/engines/ags/engine/platform/base/agsplatformdriver.cpp b/engines/ags/engine/platform/base/agsplatformdriver.cpp new file mode 100644 index 00000000000..970611540d7 --- /dev/null +++ b/engines/ags/engine/platform/base/agsplatformdriver.cpp @@ -0,0 +1,236 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AGS Platform-specific functions +// +//============================================================================= + +#include +#include "util/wgt2allg.h" +#include "platform/base/agsplatformdriver.h" +#include "ac/common.h" +#include "ac/runtime_defines.h" +#include "util/string_utils.h" +#include "util/stream.h" +#include "gfx/bitmap.h" +#include "plugin/agsplugin.h" +#include "ac/timer.h" +#include "media/audio/audio_system.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +#if defined (AGS_HAS_CD_AUDIO) +#include "libcda.h" +#endif + +// We don't have many places where we delay longer than a frame, but where we +// do, we should give the audio layer a chance to update. +// 16 milliseconds is rough period for 60fps +const auto MaximumDelayBetweenPolling = std::chrono::milliseconds(16); + +AGSPlatformDriver* AGSPlatformDriver::instance = nullptr; +AGSPlatformDriver *platform = nullptr; + +// ******** DEFAULT IMPLEMENTATIONS ******* + +void AGSPlatformDriver::AboutToQuitGame() { } +void AGSPlatformDriver::PostAllegroInit(bool windowed) { } +void AGSPlatformDriver::AttachToParentConsole() { } +void AGSPlatformDriver::DisplaySwitchOut() { } +void AGSPlatformDriver::DisplaySwitchIn() { } +void AGSPlatformDriver::PauseApplication() { } +void AGSPlatformDriver::ResumeApplication() { } +void AGSPlatformDriver::GetSystemDisplayModes(std::vector &dms) { } +bool AGSPlatformDriver::EnterFullscreenMode(const DisplayMode &dm) { return true; } +bool AGSPlatformDriver::ExitFullscreenMode() { return true; } +void AGSPlatformDriver::AdjustWindowStyleForFullscreen() { } +void AGSPlatformDriver::AdjustWindowStyleForWindowed() { } +void AGSPlatformDriver::RegisterGameWithGameExplorer() { } +void AGSPlatformDriver::UnRegisterGameWithGameExplorer() { } +void AGSPlatformDriver::PlayVideo(const char* name, int skip, int flags) {} + +const char* AGSPlatformDriver::GetAllegroFailUserHint() +{ + return "Make sure you have latest version of Allegro 4 libraries installed, and your system is running in graphical mode."; +} + +const char *AGSPlatformDriver::GetDiskWriteAccessTroubleshootingText() +{ + return "Make sure you have write permissions, and also check the disk's free space."; +} + +void AGSPlatformDriver::GetSystemTime(ScriptDateTime *sdt) { + time_t t = time(nullptr); + + //note: subject to year 2038 problem due to shoving time_t in an integer + sdt->rawUnixTime = static_cast(t); + + struct tm *newtime = localtime(&t); + sdt->hour = newtime->tm_hour; + sdt->minute = newtime->tm_min; + sdt->second = newtime->tm_sec; + sdt->day = newtime->tm_mday; + sdt->month = newtime->tm_mon + 1; + sdt->year = newtime->tm_year + 1900; +} + +void AGSPlatformDriver::WriteStdOut(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + printf("\n"); + fflush(stdout); +} + +void AGSPlatformDriver::WriteStdErr(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + fprintf(stderr, "\n"); + fflush(stdout); +} + +void AGSPlatformDriver::YieldCPU() { + // NOTE: this is called yield, but if we actually yield instead of delay, + // we get a massive increase in CPU usage. + this->Delay(1); + //std::this_thread::yield(); +} + +void AGSPlatformDriver::InitialiseAbufAtStartup() +{ + // because loading the game file accesses abuf, it must exist + // No no no, David Blain, no magic here :P + //abuf = BitmapHelper::CreateBitmap(10,10,8); +} + +void AGSPlatformDriver::FinishedUsingGraphicsMode() +{ + // don't need to do anything on any OS except DOS +} + +SetupReturnValue AGSPlatformDriver::RunSetup(const ConfigTree &cfg_in, ConfigTree &cfg_out) +{ + return kSetup_Cancel; +} + +void AGSPlatformDriver::SetGameWindowIcon() { + // do nothing +} + +int AGSPlatformDriver::ConvertKeycodeToScanCode(int keycode) +{ + keycode -= ('A' - KEY_A); + return keycode; +} + +bool AGSPlatformDriver::LockMouseToWindow() { return false; } +void AGSPlatformDriver::UnlockMouse() { } + +//----------------------------------------------- +// IOutputHandler implementation +//----------------------------------------------- +void AGSPlatformDriver::PrintMessage(const Common::DebugMessage &msg) +{ + if (_logToStdErr) + { + if (msg.GroupName.IsEmpty()) + WriteStdErr("%s", msg.Text.GetCStr()); + else + WriteStdErr("%s : %s", msg.GroupName.GetCStr(), msg.Text.GetCStr()); + } + else + { + if (msg.GroupName.IsEmpty()) + WriteStdOut("%s", msg.Text.GetCStr()); + else + WriteStdOut("%s : %s", msg.GroupName.GetCStr(), msg.Text.GetCStr()); + } +} + +// ********** CD Player Functions common to Win and Linux ******** + +#if defined (AGS_HAS_CD_AUDIO) + +// from ac_cdplayer +extern int use_cdplayer; +extern int need_to_stop_cd; + +int numcddrives=0; + +int cd_player_init() { + int erro = cd_init(); + if (erro) return -1; + numcddrives=1; + use_cdplayer=1; + return 0; +} + +int cd_player_control(int cmdd, int datt) { + // WINDOWS & LINUX VERSION + if (cmdd==1) { + if (cd_current_track() > 0) return 1; + return 0; + } + else if (cmdd==2) { + cd_play_from(datt); + need_to_stop_cd=1; + } + else if (cmdd==3) + cd_pause(); + else if (cmdd==4) + cd_resume(); + else if (cmdd==5) { + int first,last; + if (cd_get_tracks(&first,&last)==0) + return (last-first)+1; + else return 0; + } + else if (cmdd==6) + cd_eject(); + else if (cmdd==7) + cd_close(); + else if (cmdd==8) + return numcddrives; + else if (cmdd==9) ; + else quit("!CDAudio: Unknown command code"); + + return 0; +} + +#endif // AGS_HAS_CD_AUDIO + +void AGSPlatformDriver::Delay(int millis) { + auto now = AGS_Clock::now(); + auto delayUntil = now + std::chrono::milliseconds(millis); + + for (;;) { + if (now >= delayUntil) { break; } + + auto duration = std::min(delayUntil - now, MaximumDelayBetweenPolling); + std::this_thread::sleep_for(duration); + now = AGS_Clock::now(); // update now + + if (now >= delayUntil) { break; } + + // don't allow it to check for debug messages, since this Delay() + // call might be from within a debugger polling loop + update_polled_mp3(); + now = AGS_Clock::now(); // update now + } +} diff --git a/engines/ags/engine/platform/base/agsplatformdriver.h b/engines/ags/engine/platform/base/agsplatformdriver.h new file mode 100644 index 00000000000..208d9afca62 --- /dev/null +++ b/engines/ags/engine/platform/base/agsplatformdriver.h @@ -0,0 +1,170 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AGS Cross-Platform Header +// +//============================================================================= + +#ifndef __AGS_EE_PLATFORM__AGSPLATFORMDRIVER_H +#define __AGS_EE_PLATFORM__AGSPLATFORMDRIVER_H + +#include +#include +#include "ac/datetime.h" +#include "debug/outputhandler.h" +#include "util/ini_util.h" + +namespace AGS +{ + namespace Common { class Stream; } + namespace Engine { struct DisplayMode; } +} +using namespace AGS; // FIXME later + +enum eScriptSystemOSID +{ + eOS_DOS = 1, + eOS_Win, + eOS_Linux, + eOS_Mac, + eOS_Android, + eOS_iOS, + eOS_PSP +}; + +enum SetupReturnValue +{ + kSetup_Cancel, + kSetup_Done, + kSetup_RunGame +}; + +struct AGSPlatformDriver + // be used as a output target for logging system + : public AGS::Common::IOutputHandler +{ + virtual void AboutToQuitGame(); + virtual void Delay(int millis); + virtual void DisplayAlert(const char*, ...) = 0; + virtual void AttachToParentConsole(); + virtual int GetLastSystemError() { return errno; } + // Get root directory for storing per-game shared data + virtual const char *GetAllUsersDataDirectory() { return "."; } + // Get root directory for storing per-game saved games + virtual const char *GetUserSavedgamesDirectory() { return "."; } + // Get root directory for storing per-game user configuration files + virtual const char *GetUserConfigDirectory() { return "."; } + // Get directory for storing all-games user configuration files + virtual const char *GetUserGlobalConfigDirectory() { return "."; } + // Get default directory for program output (logs) + virtual const char *GetAppOutputDirectory() { return "."; } + // Returns array of characters illegal to use in file names + virtual const char *GetIllegalFileChars() { return "\\/"; } + virtual const char *GetDiskWriteAccessTroubleshootingText(); + virtual const char *GetGraphicsTroubleshootingText() { return ""; } + virtual unsigned long GetDiskFreeSpaceMB() = 0; + virtual const char* GetNoMouseErrorString() = 0; + // Tells whether build is capable of controlling mouse movement properly + virtual bool IsMouseControlSupported(bool windowed) { return false; } + // Tells whether this platform's backend library deals with mouse cursor + // virtual->real coordinate transformation itself (otherwise AGS engine should do it) + virtual bool IsBackendResponsibleForMouseScaling() { return false; } + virtual const char* GetAllegroFailUserHint(); + virtual eScriptSystemOSID GetSystemOSID() = 0; + virtual void GetSystemTime(ScriptDateTime*); + virtual void PlayVideo(const char* name, int skip, int flags); + virtual void InitialiseAbufAtStartup(); + virtual void PostAllegroInit(bool windowed); + virtual void PostAllegroExit() = 0; + virtual void FinishedUsingGraphicsMode(); + virtual SetupReturnValue RunSetup(const Common::ConfigTree &cfg_in, Common::ConfigTree &cfg_out); + virtual void SetGameWindowIcon(); + // Formats message and writes to standard platform's output; + // Always adds trailing '\n' after formatted string + virtual void WriteStdOut(const char *fmt, ...); + // Formats message and writes to platform's error output; + // Always adds trailing '\n' after formatted string + virtual void WriteStdErr(const char *fmt, ...); + virtual void YieldCPU(); + // Called when the game window is being switch out from + virtual void DisplaySwitchOut(); + // Called when the game window is being switch back to + virtual void DisplaySwitchIn(); + // Called when the application is being paused completely (e.g. when player alt+tabbed from it). + // This function should suspend any platform-specific realtime processing. + virtual void PauseApplication(); + // Called when the application is being resumed. + virtual void ResumeApplication(); + // Returns a list of supported display modes + virtual void GetSystemDisplayModes(std::vector &dms); + // Switch to system fullscreen mode; store previous mode parameters + virtual bool EnterFullscreenMode(const Engine::DisplayMode &dm); + // Return back to the mode was before switching to fullscreen + virtual bool ExitFullscreenMode(); + // Adjust application window's parameters to suit fullscreen mode + virtual void AdjustWindowStyleForFullscreen(); + // Adjust application window's parameters to suit windowed mode + virtual void AdjustWindowStyleForWindowed(); + virtual void RegisterGameWithGameExplorer(); + virtual void UnRegisterGameWithGameExplorer(); + virtual int ConvertKeycodeToScanCode(int keyCode); + // Adjust window size to ensure it is in the supported limits + virtual void ValidateWindowSize(int &x, int &y, bool borderless) const {} + + virtual int InitializeCDPlayer() = 0; // return 0 on success + virtual int CDPlayerCommand(int cmdd, int datt) = 0; + virtual void ShutdownCDPlayer() = 0; + + virtual bool LockMouseToWindow(); + virtual void UnlockMouse(); + + static AGSPlatformDriver *GetDriver(); + + // Set whether PrintMessage should output to stdout or stderr + void SetOutputToErr(bool on) { _logToStdErr = on; } + // Set whether DisplayAlert is allowed to show modal GUIs on some systems; + // it will print to either stdout or stderr otherwise, depending on above flag + void SetGUIMode(bool on) { _guiMode = on; } + + //----------------------------------------------- + // IOutputHandler implementation + //----------------------------------------------- + // Writes to the standard platform's output, prepending "AGS: " prefix to the message + void PrintMessage(const AGS::Common::DebugMessage &msg) override; + +protected: + // TODO: this is a quick solution for IOutputHandler implementation + // logging either to stdout or stderr. Normally there should be + // separate implementation, one for each kind of output, but + // with both going through PlatformDriver need to figure a better + // design first. + bool _logToStdErr = false; + // Defines whether engine is allowed to display important warnings + // and errors by showing a message box kind of GUI. + bool _guiMode = false; + +private: + static AGSPlatformDriver *instance; +}; + +#if defined (AGS_HAS_CD_AUDIO) +int cd_player_init(); +int cd_player_control(int cmdd, int datt); +#endif + +// [IKM] What is a need to have this global var if you can get AGSPlatformDriver +// instance by calling AGSPlatformDriver::GetDriver()? +extern AGSPlatformDriver *platform; + +#endif // __AGS_EE_PLATFORM__AGSPLATFORMDRIVER_H diff --git a/engines/ags/engine/platform/ios/acplios.cpp b/engines/ags/engine/platform/ios/acplios.cpp new file mode 100644 index 00000000000..96bbe432509 --- /dev/null +++ b/engines/ags/engine/platform/ios/acplios.cpp @@ -0,0 +1,644 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_IOS + +#include +#include +#include +#include + +#include +#include "platform/base/agsplatformdriver.h" +#include "ac/runtime_defines.h" +#include "main/config.h" +#include "plugin/agsplugin.h" +#include "util/string_utils.h" + +using namespace AGS::Common; + +#define IOS_CONFIG_FILENAME "ios.cfg" + +extern char* ios_document_directory; + +bool ReadConfiguration(char* filename, bool read_everything); +void ResetConfiguration(); + +//int psp_return_to_menu = 1; +int psp_ignore_acsetup_cfg_file = 1; +int psp_clear_cache_on_room_change = 0; +int psp_rotation = 0; +int psp_config_enabled = 0; +char psp_translation[100]; +char* psp_translations[100]; + +// Mouse option from Allegro. +extern int config_mouse_control_mode; + + +// Graphic options from the Allegro library. +extern int psp_gfx_scaling; +extern int psp_gfx_smoothing; + + +// Audio options from the Allegro library. +unsigned int psp_audio_samplerate = 44100; +int psp_audio_enabled = 1; +volatile int psp_audio_multithreaded = 1; +int psp_audio_cachesize = 10; +int psp_midi_enabled = 1; +int psp_midi_preload_patches = 0; + +int psp_video_framedrop = 0; + +int psp_gfx_renderer = 0; +int psp_gfx_super_sampling = 0; +int psp_gfx_smooth_sprites = 0; + +int psp_debug_write_to_logcat = 0; + +int config_mouse_longclick = 0; + +extern int display_fps; +extern int want_exit; +extern void PauseGame(); +extern void UnPauseGame(); +extern int main(int argc,char*argv[]); + +char psp_game_file_name[256]; +char* psp_game_file_name_pointer = psp_game_file_name; + +bool psp_load_latest_savegame = false; +extern char saveGameDirectory[260]; +extern const char *loadSaveGameOnStartup; +char lastSaveGameName[200]; + +bool reset_configuration = false; + +const int CONFIG_IGNORE_ACSETUP = 0; +const int CONFIG_CLEAR_CACHE = 1; +const int CONFIG_AUDIO_RATE = 2; +const int CONFIG_AUDIO_ENABLED = 3; +const int CONFIG_AUDIO_THREADED = 4; +const int CONFIG_AUDIO_CACHESIZE = 5; +const int CONFIG_MIDI_ENABLED = 6; +const int CONFIG_MIDI_PRELOAD = 7; +const int CONFIG_VIDEO_FRAMEDROP = 8; +const int CONFIG_GFX_RENDERER = 9; +const int CONFIG_GFX_SMOOTHING = 10; +const int CONFIG_GFX_SCALING = 11; +const int CONFIG_GFX_SS = 12; +const int CONFIG_ROTATION = 13; +const int CONFIG_ENABLED = 14; +const int CONFIG_DEBUG_FPS = 15; +const int CONFIG_GFX_SMOOTH_SPRITES = 16; +const int CONFIG_TRANSLATION = 17; +const int CONFIG_DEBUG_LOGCAT = 18; +const int CONFIG_MOUSE_METHOD = 19; +const int CONFIG_MOUSE_LONGCLICK = 20; + + + +struct AGSIOS : AGSPlatformDriver { + + virtual int CDPlayerCommand(int cmdd, int datt); + virtual void Delay(int millis); + virtual void DisplayAlert(const char*, ...); + virtual const char *GetAppOutputDirectory(); + virtual unsigned long GetDiskFreeSpaceMB(); + virtual const char* GetNoMouseErrorString(); + virtual bool IsBackendResponsibleForMouseScaling() { return true; } + virtual eScriptSystemOSID GetSystemOSID(); + virtual int InitializeCDPlayer(); + virtual void PostAllegroExit(); + virtual void SetGameWindowIcon(); + virtual void ShutdownCDPlayer(); +}; + + + +bool readConfigFile(char* directory) +{ + chdir(directory); + + ResetConfiguration(); + + return ReadConfiguration(IOS_CONFIG_FILENAME, true); +} + + +bool writeConfigFile() +{ + FILE* config = fopen(IOS_CONFIG_FILENAME, "wb"); + if (config) + { + fprintf(config, "[misc]\n"); + fprintf(config, "config_enabled = %d\n", psp_config_enabled); + fprintf(config, "rotation = %d\n", psp_rotation); + fprintf(config, "translation = %s\n", psp_translation); + + fprintf(config, "[controls]\n"); + fprintf(config, "mouse_method = %d\n", config_mouse_control_mode); + fprintf(config, "mouse_longclick = %d\n", config_mouse_longclick); + + fprintf(config, "[compatibility]\n"); + fprintf(config, "clear_cache_on_room_change = %d\n", psp_clear_cache_on_room_change); + + fprintf(config, "[sound]\n"); + fprintf(config, "samplerate = %d\n", psp_audio_samplerate ); + fprintf(config, "enabled = %d\n", psp_audio_enabled); + fprintf(config, "threaded = %d\n", psp_audio_multithreaded); + fprintf(config, "cache_size = %d\n", psp_audio_cachesize); + + fprintf(config, "[midi]\n"); + fprintf(config, "enabled = %d\n", psp_midi_enabled); + fprintf(config, "preload_patches = %d\n", psp_midi_preload_patches); + + fprintf(config, "[video]\n"); + fprintf(config, "framedrop = %d\n", psp_video_framedrop); + + fprintf(config, "[graphics]\n"); + fprintf(config, "renderer = %d\n", psp_gfx_renderer); + fprintf(config, "smoothing = %d\n", psp_gfx_smoothing); + fprintf(config, "scaling = %d\n", psp_gfx_scaling); + fprintf(config, "super_sampling = %d\n", psp_gfx_super_sampling); + fprintf(config, "smooth_sprites = %d\n", psp_gfx_smooth_sprites); + + fprintf(config, "[debug]\n"); + fprintf(config, "show_fps = %d\n", (display_fps == 2) ? 1 : 0); + fprintf(config, "logging = %d\n", psp_debug_write_to_logcat); + + fclose(config); + + return true; + } + + return false; +} + + +int readIntConfigValue(int id) +{ + switch (id) + { + case CONFIG_IGNORE_ACSETUP: + return psp_ignore_acsetup_cfg_file; + break; + case CONFIG_CLEAR_CACHE: + return psp_clear_cache_on_room_change; + break; + case CONFIG_AUDIO_RATE: + return psp_audio_samplerate; + break; + case CONFIG_AUDIO_ENABLED: + return psp_audio_enabled; + break; + case CONFIG_AUDIO_THREADED: + return psp_audio_multithreaded; + break; + case CONFIG_AUDIO_CACHESIZE: + return psp_audio_cachesize; + break; + case CONFIG_MIDI_ENABLED: + return psp_midi_enabled; + break; + case CONFIG_MIDI_PRELOAD: + return psp_midi_preload_patches; + break; + case CONFIG_VIDEO_FRAMEDROP: + return psp_video_framedrop; + break; + case CONFIG_GFX_RENDERER: + return psp_gfx_renderer; + break; + case CONFIG_GFX_SMOOTHING: + return psp_gfx_smoothing; + break; + case CONFIG_GFX_SCALING: + return psp_gfx_scaling; + break; + case CONFIG_GFX_SS: + return psp_gfx_super_sampling; + break; + case CONFIG_GFX_SMOOTH_SPRITES: + return psp_gfx_smooth_sprites; + break; + case CONFIG_ROTATION: + return psp_rotation; + break; + case CONFIG_ENABLED: + return psp_config_enabled; + break; + case CONFIG_DEBUG_FPS: + return (display_fps == 2) ? 1 : 0; + break; + case CONFIG_DEBUG_LOGCAT: + return psp_debug_write_to_logcat; + break; + case CONFIG_MOUSE_METHOD: + return config_mouse_control_mode; + break; + case CONFIG_MOUSE_LONGCLICK: + return config_mouse_longclick; + break; + default: + return 0; + break; + } +} + + +char* readStringConfigValue(int id) +{ + switch (id) + { + case CONFIG_TRANSLATION: + return &psp_translation[0]; + break; + } +} + + +void setIntConfigValue(int id, int value) +{ + switch (id) + { + case CONFIG_IGNORE_ACSETUP: + psp_ignore_acsetup_cfg_file = value; + break; + case CONFIG_CLEAR_CACHE: + psp_clear_cache_on_room_change = value; + break; + case CONFIG_AUDIO_RATE: + psp_audio_samplerate = value; + break; + case CONFIG_AUDIO_ENABLED: + psp_audio_enabled = value; + break; + case CONFIG_AUDIO_THREADED: + psp_audio_multithreaded = value; + break; + case CONFIG_AUDIO_CACHESIZE: + psp_audio_cachesize = value; + break; + case CONFIG_MIDI_ENABLED: + psp_midi_enabled = value; + break; + case CONFIG_MIDI_PRELOAD: + psp_midi_preload_patches = value; + break; + case CONFIG_VIDEO_FRAMEDROP: + psp_video_framedrop = value; + break; + case CONFIG_GFX_RENDERER: + psp_gfx_renderer = value; + break; + case CONFIG_GFX_SMOOTHING: + psp_gfx_smoothing = value; + break; + case CONFIG_GFX_SCALING: + psp_gfx_scaling = value; + break; + case CONFIG_GFX_SS: + psp_gfx_super_sampling = value; + break; + case CONFIG_GFX_SMOOTH_SPRITES: + psp_gfx_smooth_sprites = value; + break; + case CONFIG_ROTATION: + psp_rotation = value; + break; + case CONFIG_ENABLED: + psp_config_enabled = value; + break; + case CONFIG_DEBUG_FPS: + display_fps = (value == 1) ? 2 : 0; + break; + case CONFIG_DEBUG_LOGCAT: + psp_debug_write_to_logcat = value; + break; + case CONFIG_MOUSE_METHOD: + config_mouse_control_mode = value; + break; + case CONFIG_MOUSE_LONGCLICK: + config_mouse_longclick = value; + break; + default: + break; + } +} + + +void setStringConfigValue(int id, char* value) +{ + switch (id) + { + case CONFIG_TRANSLATION: + strcpy(psp_translation, value); + break; + default: + break; + } +} + +/* +int getAvailableTranslations(char* translations) +{ + int i = 0; + int length; + DIR* dir; + struct dirent* entry; + char buffer[200]; + + dir = opendir("."); + if (dir) + { + while ((entry = readdir(dir)) != 0) + { + length = strlen(entry->d_name); + if (length > 4) + { + if (ags_stricmp(&entry->d_name[length - 4], ".tra") == 0) + { + memset(buffer, 0, 200); + strncpy(buffer, entry->d_name, length - 4); + psp_translations[i] = (char*)malloc(strlen(buffer) + 1); + strcpy(psp_translations[i], buffer); + env->SetObjectArrayElement(translations, i, env->NewStringUTF(&buffer[0])); + i++; + } + } + } + closedir(dir); + } + + return i; +} +*/ + + + + +void selectLatestSavegame() +{ + DIR* dir; + struct dirent* entry; + struct stat statBuffer; + char buffer[200]; + time_t lastTime = 0; + + dir = opendir(saveGameDirectory); + + if (dir) + { + while ((entry = readdir(dir)) != 0) + { + if (ags_strnicmp(entry->d_name, "agssave", 7) == 0) + { + if (ags_stricmp(entry->d_name, "agssave.999") != 0) + { + strcpy(buffer, saveGameDirectory); + strcat(buffer, entry->d_name); + stat(buffer, &statBuffer); + if (statBuffer.st_mtime > lastTime) + { + strcpy(lastSaveGameName, buffer); + loadSaveGameOnStartup = lastSaveGameName; + lastTime = statBuffer.st_mtime; + } + } + } + } + closedir(dir); + } +} + + +int ReadInteger(int* variable, const ConfigTree &cfg, char* section, char* name, int minimum, int maximum, int default_value) +{ + if (reset_configuration) + { + *variable = default_value; + return 0; + } + + int temp = INIreadint(cfg, section, name); + + if (temp == -1) + return 0; + + if ((temp < minimum) || (temp > maximum)) + temp = default_value; + + *variable = temp; + + return 1; +} + + + +int ReadString(char* variable, const ConfigTree &cfg, char* section, char* name, char* default_value) +{ + if (reset_configuration) + { + strcpy(variable, default_value); + return 0; + } + + String temp; + if (!INIreaditem(cfg, section, name, temp)) + temp = default_value; + + strcpy(variable, temp); + + return 1; +} + + + +void ResetConfiguration() +{ + reset_configuration = true; + + ReadConfiguration(IOS_CONFIG_FILENAME, true); + + reset_configuration = false; +} + + +bool ReadConfiguration(char* filename, bool read_everything) +{ + ConfigTree cfg; + if (IniUtil::Read(filename, cfg) || reset_configuration) + { +// ReadInteger((int*)&psp_disable_powersaving, "misc", "disable_power_saving", 0, 1, 1); + +// ReadInteger((int*)&psp_return_to_menu, "misc", "return_to_menu", 0, 1, 1); + + ReadString(&psp_translation[0], cfg, "misc", "translation", "default"); + + ReadInteger((int*)&psp_config_enabled, cfg, "misc", "config_enabled", 0, 1, 0); + if (!psp_config_enabled && !read_everything) + return true; + + ReadInteger(&psp_debug_write_to_logcat, cfg, "debug", "logging", 0, 1, 0); + ReadInteger(&display_fps, cfg, "debug", "show_fps", 0, 1, 0); + if (display_fps == 1) + display_fps = 2; + + ReadInteger((int*)&psp_rotation, cfg, "misc", "rotation", 0, 2, 0); + +// ReadInteger((int*)&psp_ignore_acsetup_cfg_file, "compatibility", "ignore_acsetup_cfg_file", 0, 1, 0); + ReadInteger((int*)&psp_clear_cache_on_room_change, cfg, "compatibility", "clear_cache_on_room_change", 0, 1, 0); + + ReadInteger((int*)&psp_audio_samplerate, cfg, "sound", "samplerate", 0, 44100, 44100); + ReadInteger((int*)&psp_audio_enabled, cfg, "sound", "enabled", 0, 1, 1); + ReadInteger((int*)&psp_audio_multithreaded, cfg, "sound", "threaded", 0, 1, 1); + ReadInteger((int*)&psp_audio_cachesize, cfg, "sound", "cache_size", 1, 50, 10); + + ReadInteger((int*)&psp_midi_enabled, cfg, "midi", "enabled", 0, 1, 1); + ReadInteger((int*)&psp_midi_preload_patches, cfg, "midi", "preload_patches", 0, 1, 0); + + ReadInteger((int*)&psp_video_framedrop, cfg, "video", "framedrop", 0, 1, 0); + + ReadInteger((int*)&psp_gfx_renderer, cfg, "graphics", "renderer", 0, 2, 0); + ReadInteger((int*)&psp_gfx_smoothing, cfg, "graphics", "smoothing", 0, 1, 1); + ReadInteger((int*)&psp_gfx_scaling, cfg, "graphics", "scaling", 0, 2, 1); + ReadInteger((int*)&psp_gfx_super_sampling, cfg, "graphics", "super_sampling", 0, 1, 0); + ReadInteger((int*)&psp_gfx_smooth_sprites, cfg, "graphics", "smooth_sprites", 0, 1, 0); + + ReadInteger((int*)&config_mouse_control_mode, cfg, "controls", "mouse_method", 0, 1, 0); + ReadInteger((int*)&config_mouse_longclick, cfg, "controls", "mouse_longclick", 0, 1, 1); + + return true; + } + + return false; +} + + + +extern void ios_show_message_box(char* buffer); +volatile int ios_wait_for_ui = 0; + + +void startEngine(char* filename, char* directory, int loadLastSave) +{ + strcpy(psp_game_file_name, filename); + + // Get the base directory (usually "/sdcard/ags"). + chdir(directory); + + // Reset configuration. + ResetConfiguration(); + + // Read general configuration. + ReadConfiguration(IOS_CONFIG_FILENAME, true); + + // Get the games path. + char path[256]; + strcpy(path, psp_game_file_name); + int lastindex = strlen(path) - 1; + while (path[lastindex] != '/') + { + path[lastindex] = 0; + lastindex--; + } + chdir(path); + + setenv("ULTRADIR", "..", 1); + + // Read game specific configuration. + ReadConfiguration(IOS_CONFIG_FILENAME, false); + + psp_load_latest_savegame = loadLastSave; + + // Start the engine main function. + main(1, &psp_game_file_name_pointer); + + // Explicitly quit here, otherwise the app will hang forever. + exit(0); +} + + + + +int AGSIOS::CDPlayerCommand(int cmdd, int datt) { + return 0;//cd_player_control(cmdd, datt); +} + + + +void AGSIOS::DisplayAlert(const char *text, ...) { + char displbuf[2000]; + va_list ap; + va_start(ap, text); + vsprintf(displbuf, text, ap); + va_end(ap); + printf("%s", displbuf); + ios_show_message_box(displbuf); + + while (ios_wait_for_ui) + usleep(200); +} + +void AGSIOS::Delay(int millis) { + usleep(millis); +} + +unsigned long AGSIOS::GetDiskFreeSpaceMB() { + // placeholder + return 100; +} + +const char* AGSIOS::GetNoMouseErrorString() { + return "This game requires a mouse. You need to configure and setup your mouse to play this game.\n"; +} + +eScriptSystemOSID AGSIOS::GetSystemOSID() { + return eOS_iOS; +} + +int AGSIOS::InitializeCDPlayer() { + return 0;//cd_player_init(); +} + +void AGSIOS::PostAllegroExit() { + // do nothing +} + +void AGSIOS::SetGameWindowIcon() { + // do nothing +} + + + +void AGSIOS::ShutdownCDPlayer() { + //cd_exit(); +} + +const char *AGSIOS::GetAppOutputDirectory() +{ + return ios_document_directory; +} + +AGSPlatformDriver* AGSPlatformDriver::GetDriver() { + if (instance == NULL) + instance = new AGSIOS(); + return instance; +} + +#endif diff --git a/engines/ags/engine/platform/linux/acpllnx.cpp b/engines/ags/engine/platform/linux/acpllnx.cpp new file mode 100644 index 00000000000..07297a9fdff --- /dev/null +++ b/engines/ags/engine/platform/linux/acpllnx.cpp @@ -0,0 +1,211 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_LINUX + +// ********* LINUX PLACEHOLDER DRIVER ********* + +#include +#include +#include +#include "ac/runtime_defines.h" +#include "gfx/gfxdefines.h" +#include "platform/base/agsplatformdriver.h" +#include "plugin/agsplugin.h" +#include "util/string.h" +#include + +#include +#include + +using AGS::Common::String; + + +// Replace the default Allegro icon. The original defintion is in the +// Allegro 4.4 source under "src/x/xwin.c". +#include "icon.xpm" +void* allegro_icon = icon_xpm; +String CommonDataDirectory; +String UserDataDirectory; + +struct AGSLinux : AGSPlatformDriver { + + int CDPlayerCommand(int cmdd, int datt) override; + void DisplayAlert(const char*, ...) override; + const char *GetAllUsersDataDirectory() override; + const char *GetUserSavedgamesDirectory() override; + const char *GetUserConfigDirectory() override; + const char *GetUserGlobalConfigDirectory() override; + const char *GetAppOutputDirectory() override; + unsigned long GetDiskFreeSpaceMB() override; + const char* GetNoMouseErrorString() override; + const char* GetAllegroFailUserHint() override; + eScriptSystemOSID GetSystemOSID() override; + int InitializeCDPlayer() override; + void PostAllegroExit() override; + void SetGameWindowIcon() override; + void ShutdownCDPlayer() override; + bool LockMouseToWindow() override; + void UnlockMouse() override; + void GetSystemDisplayModes(std::vector &dms) override; +}; + + +int AGSLinux::CDPlayerCommand(int cmdd, int datt) { + return cd_player_control(cmdd, datt); +} + +void AGSLinux::DisplayAlert(const char *text, ...) { + char displbuf[2000]; + va_list ap; + va_start(ap, text); + vsprintf(displbuf, text, ap); + va_end(ap); + if (_logToStdErr) + fprintf(stderr, "%s\n", displbuf); + else + fprintf(stdout, "%s\n", displbuf); +} + +size_t BuildXDGPath(char *destPath, size_t destSize) +{ + // Check to see if XDG_DATA_HOME is set in the enviroment + const char* home_dir = getenv("XDG_DATA_HOME"); + size_t l = 0; + if (home_dir) + { + l = snprintf(destPath, destSize, "%s", home_dir); + } + else + { + // No evironment variable, so we fall back to home dir in /etc/passwd + struct passwd *p = getpwuid(getuid()); + l = snprintf(destPath, destSize, "%s/.local", p->pw_dir); + if (mkdir(destPath, 0755) != 0 && errno != EEXIST) + return 0; + l += snprintf(destPath + l, destSize - l, "/share"); + if (mkdir(destPath, 0755) != 0 && errno != EEXIST) + return 0; + } + return l; +} + +void DetermineDataDirectories() +{ + if (!UserDataDirectory.IsEmpty()) + return; + char xdg_path[256]; + if (BuildXDGPath(xdg_path, sizeof(xdg_path)) == 0) + sprintf(xdg_path, "%s", "/tmp"); + UserDataDirectory.Format("%s/ags", xdg_path); + mkdir(UserDataDirectory.GetCStr(), 0755); + CommonDataDirectory.Format("%s/ags-common", xdg_path); + mkdir(CommonDataDirectory.GetCStr(), 0755); +} + +const char *AGSLinux::GetAllUsersDataDirectory() +{ + DetermineDataDirectories(); + return CommonDataDirectory; +} + +const char *AGSLinux::GetUserSavedgamesDirectory() +{ + DetermineDataDirectories(); + return UserDataDirectory; +} + +const char *AGSLinux::GetUserConfigDirectory() +{ + return GetUserSavedgamesDirectory(); +} + +const char *AGSLinux::GetUserGlobalConfigDirectory() +{ + return GetUserSavedgamesDirectory(); +} + +const char *AGSLinux::GetAppOutputDirectory() +{ + DetermineDataDirectories(); + return UserDataDirectory; +} + +unsigned long AGSLinux::GetDiskFreeSpaceMB() { + // placeholder + return 100; +} + +const char* AGSLinux::GetNoMouseErrorString() { + return "This game requires a mouse. You need to configure and setup your mouse to play this game.\n"; +} + +const char* AGSLinux::GetAllegroFailUserHint() +{ + return "Make sure you have latest version of Allegro 4 libraries installed, and X server is running."; +} + +eScriptSystemOSID AGSLinux::GetSystemOSID() { + return eOS_Linux; +} + +int AGSLinux::InitializeCDPlayer() { + return cd_player_init(); +} + +void AGSLinux::PostAllegroExit() { + // do nothing +} + +void AGSLinux::SetGameWindowIcon() { + // do nothing +} + +void AGSLinux::ShutdownCDPlayer() { + cd_exit(); +} + +AGSPlatformDriver* AGSPlatformDriver::GetDriver() { + if (instance == nullptr) + instance = new AGSLinux(); + return instance; +} + +bool AGSLinux::LockMouseToWindow() +{ + return XGrabPointer(_xwin.display, _xwin.window, False, + PointerMotionMask | ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, _xwin.window, None, CurrentTime) == GrabSuccess; +} + +void AGSLinux::UnlockMouse() +{ + XUngrabPointer(_xwin.display, CurrentTime); +} + +void AGSLinux::GetSystemDisplayModes(std::vector &dms) +{ + dms.clear(); + GFX_MODE_LIST *gmlist = get_gfx_mode_list(GFX_XWINDOWS_FULLSCREEN); + for (int i = 0; i < gmlist->num_modes; ++i) + { + const GFX_MODE &m = gmlist->mode[i]; + dms.push_back(Engine::DisplayMode(Engine::GraphicResolution(m.width, m.height, m.bpp))); + } + destroy_gfx_mode_list(gmlist); +} + +#endif diff --git a/engines/ags/engine/platform/linux/icon.xpm b/engines/ags/engine/platform/linux/icon.xpm new file mode 100644 index 00000000000..61854643f83 --- /dev/null +++ b/engines/ags/engine/platform/linux/icon.xpm @@ -0,0 +1,1103 @@ +/* XPM */ +static const char * icon_xpm[] = { +"48 48 1052 2", +" c None", +". c #5258BE", +"+ c #4449B4", +"@ c #2E33AA", +"# c #2226A6", +"$ c #2527A8", +"% c #282CAA", +"& c #3539AF", +"* c #464CB5", +"= c #4C51B6", +"- c #4D53BE", +"; c #2B2EA8", +"> c #090B8C", +", c #010180", +"' c #000079", +") c #00007B", +"! c #010187", +"~ c #060894", +"{ c #1B1FA7", +"] c #3138B2", +"^ c #2D33B0", +"/ c #262CAE", +"( c #3036B0", +"_ c #2E33B0", +": c #282EAC", +"< c #2F34AD", +"[ c #4C51BC", +"} c #121398", +"| c #00007D", +"1 c #000072", +"2 c #04046C", +"3 c #06066C", +"4 c #0A0A74", +"5 c #0C0C81", +"6 c #0C0D8D", +"7 c #0E1199", +"8 c #292EAC", +"9 c #474EB8", +"0 c #474EB9", +"a c #444BB9", +"b c #5861C2", +"c c #666FCA", +"d c #6770CC", +"e c #555FC4", +"f c #292EB1", +"g c #1F20A6", +"h c #585DBB", +"i c #1E20A6", +"j c #000081", +"k c #060473", +"l c #070674", +"m c #090977", +"n c #090975", +"o c #090974", +"p c #0A0A79", +"q c #0C0C83", +"r c #0F1198", +"s c #2C31AA", +"t c #4B51B6", +"u c #474EB6", +"v c #3F44B5", +"w c #5058BC", +"x c #636BC4", +"y c #6C75CB", +"z c #717AD1", +"A c #5C66CD", +"B c #2B30BA", +"C c #0C0EA2", +"D c #2023A8", +"E c #070A9B", +"F c #010179", +"G c #0C0A75", +"H c #0A0A7D", +"I c #09097A", +"J c #090978", +"K c #090976", +"L c #0C0C86", +"M c #0C0C8D", +"N c #0F1196", +"O c #2B2FA7", +"P c #4A51B3", +"Q c #484FB4", +"R c #3B41B2", +"S c #4A52BA", +"T c #6069C2", +"U c #6A73CA", +"V c #6F78CF", +"W c #5E66CC", +"X c #3A40C1", +"Y c #2A2DB3", +"Z c #11139C", +"` c #1F22A6", +" . c #2C2FB2", +".. c #00007C", +"+. c #09096F", +"@. c #0B0C7E", +"#. c #0A0A7E", +"$. c #09097C", +"%. c #08086F", +"&. c #080870", +"*. c #0B0B77", +"=. c #0C0C7D", +"-. c #0C0D84", +";. c #1C1F92", +">. c #31369E", +",. c #3338A3", +"'. c #282CA5", +"). c #363BAC", +"!. c #4E55B8", +"~. c #5E67C2", +"{. c #6A73CC", +"]. c #5D65CD", +"^. c #3C42C1", +"/. c #2B31B7", +"(. c #2A2FA1", +"_. c #090A94", +":. c #4247B3", +"<. c #0D0F9B", +"[. c #04056D", +"}. c #0C0C82", +"|. c #0A0A80", +"1. c #080874", +"2. c #07076A", +"3. c #070765", +"4. c #060663", +"5. c #060665", +"6. c #0B0B6C", +"7. c #0C0C74", +"8. c #0C0E7B", +"9. c #131585", +"0. c #1D1F91", +"a. c #1F2295", +"b. c #1B1D99", +"c. c #22249E", +"d. c #3134A7", +"e. c #3F45B0", +"f. c #4E55BA", +"g. c #5056C3", +"h. c #3A40C2", +"i. c #2B30B9", +"j. c #2A30A5", +"k. c #12158D", +"l. c #1E21A5", +"m. c #101299", +"n. c #06066F", +"o. c #09097D", +"p. c #060664", +"q. c #05055E", +"r. c #060662", +"s. c #060661", +"t. c #0A0A69", +"u. c #0C0C6F", +"v. c #0C0E76", +"w. c #101280", +"x. c #14168A", +"y. c #181990", +"z. c #151695", +"A. c #1A1A9B", +"B. c #2527A1", +"C. c #2E33A9", +"D. c #363BAE", +"E. c #353BB2", +"F. c #2C31B4", +"G. c #272BB6", +"H. c #272AA6", +"I. c #14178C", +"J. c #1B1DA3", +"K. c #1B1EAB", +"L. c #05057C", +"M. c #050560", +"N. c #050552", +"O. c #050556", +"P. c #06065C", +"Q. c #06065E", +"R. c #06065F", +"S. c #080862", +"T. c #090967", +"U. c #0B0D6E", +"V. c #0D0F77", +"W. c #101281", +"X. c #111388", +"Y. c #0F118E", +"Z. c #131495", +"`. c #1A1C9C", +" + c #2325A3", +".+ c #292DAA", +"++ c #2A2DAD", +"@+ c #2225AC", +"#+ c #1D20AA", +"$+ c #20229F", +"%+ c #131597", +"&+ c #1B1CAE", +"*+ c #3F46C0", +"=+ c #131598", +"-+ c #00003C", +";+ c #03033F", +">+ c #050550", +",+ c #050554", +"'+ c #050558", +")+ c #05055B", +"!+ c #05055A", +"~+ c #05055C", +"{+ c #080860", +"]+ c #080863", +"^+ c #0A0967", +"/+ c #0C0E6F", +"(+ c #0F1178", +"_+ c #0E107F", +":+ c #0D0F85", +"<+ c #0E108D", +"[+ c #141694", +"}+ c #1A1C9B", +"|+ c #2022A2", +"1+ c #2024A6", +"2+ c #1E21A6", +"3+ c #181BA0", +"4+ c #181A98", +"5+ c #0E10A6", +"6+ c #1B1DB5", +"7+ c #525CB3", +"8+ c #6069D4", +"9+ c #14168E", +"0+ c #000036", +"a+ c #00002F", +"b+ c #040442", +"c+ c #05054D", +"d+ c #060657", +"e+ c #070759", +"f+ c #07075C", +"g+ c #08085F", +"h+ c #0A0A66", +"i+ c #0C0D6D", +"j+ c #0C0F74", +"k+ c #0C0F7A", +"l+ c #0E1081", +"m+ c #13158E", +"n+ c #181B95", +"o+ c #1B1C97", +"p+ c #1B1C96", +"q+ c #171899", +"r+ c #1719A8", +"s+ c #0B0CAE", +"t+ c #1A1AB8", +"u+ c #2C329B", +"v+ c #7A86CB", +"w+ c #7985E4", +"x+ c #262BB0", +"y+ c #000059", +"z+ c #01002A", +"A+ c #020236", +"B+ c #040441", +"C+ c #05054A", +"D+ c #05054C", +"E+ c #05054E", +"F+ c #060651", +"G+ c #080856", +"H+ c #09085B", +"I+ c #0A0A5F", +"J+ c #010565", +"K+ c #03076C", +"L+ c #0C0F6B", +"M+ c #0F126C", +"N+ c #121374", +"O+ c #141685", +"P+ c #17199B", +"Q+ c #1719AC", +"R+ c #1314AE", +"S+ c #0607A9", +"T+ c #1E1FB2", +"U+ c #34356C", +"V+ c #0D0D68", +"W+ c #0E1180", +"X+ c #262798", +"Y+ c #262A98", +"Z+ c #4249AF", +"`+ c #7D88CF", +" @ c #919DE8", +".@ c #5C64DB", +"+@ c #2326A8", +"@@ c #070775", +"#@ c #00004C", +"$@ c #000034", +"%@ c #00002B", +"&@ c #000028", +"*@ c #010029", +"=@ c #01002C", +"-@ c #030230", +";@ c #020334", +">@ c #010239", +",@ c #2C183D", +"'@ c #251642", +")@ c #02075B", +"!@ c #0F1276", +"~@ c #151793", +"{@ c #1518AC", +"]@ c #1314B2", +"^@ c #1011A4", +"/@ c #0D0D9C", +"(@ c #0D0DA8", +"_@ c #0D0EA2", +":@ c #070954", +"<@ c #000054", +"[@ c #171A81", +"}@ c #3941AA", +"|@ c #3942B7", +"1@ c #232BA8", +"2@ c #575CAF", +"3@ c #2E319C", +"4@ c #3B41AC", +"5@ c #5259BC", +"6@ c #7580CE", +"7@ c #9AA9E3", +"8@ c #95A3EB", +"9@ c #6973DF", +"0@ c #3A40C7", +"a@ c #2125AD", +"b@ c #161797", +"c@ c #0F1188", +"d@ c #0B0D7A", +"e@ c #0A0B71", +"f@ c #0A0B70", +"g@ c #0A0B75", +"h@ c #0B0D82", +"i@ c #3B2A90", +"j@ c #744763", +"k@ c #322684", +"l@ c #131BC1", +"m@ c #1618B6", +"n@ c #0F10A4", +"o@ c #0C0B93", +"p@ c #0D0D94", +"q@ c #0D0DAA", +"r@ c #0D0DA0", +"s@ c #090B5C", +"t@ c #171A7F", +"u@ c #3F47B7", +"v@ c #383FC1", +"w@ c #2A2FBA", +"x@ c #3037BC", +"y@ c #1F24A6", +"z@ c #6569B4", +"A@ c #2A2E99", +"B@ c #3940AA", +"C@ c #575FBE", +"D@ c #6D76CC", +"E@ c #828DD8", +"F@ c #919FE1", +"G@ c #95A3E3", +"H@ c #8995E1", +"I@ c #7984DC", +"J@ c #6C76D9", +"K@ c #5B64D3", +"L@ c #4F57CF", +"M@ c #464CCD", +"N@ c #3F45C7", +"O@ c #3C42C3", +"P@ c #3D42C1", +"Q@ c #2F3ECA", +"R@ c #51489D", +"S@ c #58426E", +"T@ c #1C239E", +"U@ c #12148D", +"V@ c #090981", +"W@ c #0A0A88", +"X@ c #0D0D95", +"Y@ c #0D0EAB", +"Z@ c #0E109E", +"`@ c #10126B", +" # c #20239F", +".# c #2D31BC", +"+# c #2529B9", +"@# c #2528BA", +"## c #1F23B1", +"$# c #24269F", +"%# c #242896", +"&# c #383EA9", +"*# c #565DBC", +"=# c #717BCF", +"-# c #8E9BDC", +";# c #97A5E1", +"># c #8895DE", +",# c #727CD8", +"'# c #646CD3", +")# c #626BCF", +"!# c #636DCA", +"~# c #626BC3", +"{# c #5E66BA", +"]# c #5960B5", +"^# c #525AB0", +"/# c #4E54AB", +"(# c #3F4AAA", +"_# c #4D4889", +":# c #4E3961", +"<# c #141A81", +"[# c #0C117D", +"}# c #0B0C78", +"|# c #0D0D96", +"1# c #0E109C", +"2# c #14167C", +"3# c #2125AF", +"4# c #2226B9", +"5# c #1D21B5", +"6# c #252AB9", +"7# c #2B2FBF", +"8# c #262BBA", +"9# c #0A0D9D", +"0# c #252994", +"a# c #333AA6", +"b# c #545BBC", +"c# c #747ECF", +"d# c #96A3DF", +"e# c #A3B2E6", +"f# c #96A4E2", +"g# c #7984DA", +"h# c #6068D2", +"i# c #535BCA", +"j# c #4E55C2", +"k# c #484FB9", +"l# c #454CB1", +"m# c #4147AB", +"n# c #3D44A7", +"o# c #343CA0", +"p# c #20289D", +"q# c #372E7E", +"r# c #422C57", +"s# c #080B76", +"t# c #090972", +"u# c #09097B", +"v# c #0F10AC", +"w# c #0F119E", +"x# c #161986", +"y# c #4145AC", +"z# c #1D1D75", +"A# c #181B87", +"B# c #2C32B8", +"C# c #1316AC", +"D# c #4244A5", +"E# c #2E3198", +"F# c #2E34A4", +"G# c #7580CF", +"H# c #9AA7E0", +"I# c #ACB8E8", +"J# c #9DAAE3", +"K# c #7D87DA", +"L# c #636AD2", +"M# c #545CCA", +"N# c #4A51C0", +"O# c #4046B6", +"P# c #333AAC", +"Q# c #2A30A4", +"R# c #22269E", +"S# c #1A1D97", +"T# c #090E95", +"U# c #291E76", +"V# c #3F2851", +"W# c #040770", +"X# c #060871", +"Y# c #090971", +"Z# c #090985", +"`# c #0C0C91", +" $ c #1010AC", +".$ c #0F119C", +"+$ c #171A88", +"@$ c #1417A6", +"#$ c #292FB7", +"$$ c #323381", +"%$ c #090B7F", +"&$ c #2027B6", +"*$ c #2A2CA1", +"=$ c #3C409F", +"-$ c #272CA1", +";$ c #7681D1", +">$ c #9EAAE0", +",$ c #B1BDE9", +"'$ c #A2AFE5", +")$ c #808CDB", +"!$ c #646DD2", +"~$ c #535AC9", +"{$ c #474EBF", +"]$ c #383FB4", +"^$ c #282DA8", +"/$ c #1A1C9E", +"($ c #131399", +"_$ c #0F1092", +":$ c #020790", +"<$ c #271871", +"[$ c #3E254C", +"}$ c #03066B", +"|$ c #05076C", +"1$ c #09096D", +"2$ c #0C0C90", +"3$ c #1213AF", +"4$ c #0E0E98", +"5$ c #0A0E81", +"6$ c #2224AA", +"7$ c #4D5098", +"8$ c #07088C", +"9$ c #3236AD", +"0$ c #3940BC", +"a$ c #1B1E9F", +"b$ c #34389B", +"c$ c #292FA2", +"d$ c #545ABC", +"e$ c #7782D0", +"f$ c #A0ACE2", +"g$ c #B5C0EA", +"h$ c #A6B2E6", +"i$ c #838FDC", +"j$ c #666FD1", +"k$ c #454CBF", +"l$ c #353BB3", +"m$ c #2325A7", +"n$ c #16179D", +"o$ c #101297", +"p$ c #0C0F90", +"q$ c #00018D", +"r$ c #20126F", +"s$ c #372048", +"t$ c #000067", +"u$ c #020468", +"v$ c #070769", +"w$ c #09097F", +"x$ c #0C0B8E", +"y$ c #1112AE", +"z$ c #13149B", +"A$ c #181B7F", +"B$ c #434588", +"C$ c #3D3F8E", +"D$ c #101197", +"E$ c #3035B2", +"F$ c #4D54C3", +"G$ c #1D20A1", +"H$ c #36399B", +"I$ c #2A30A2", +"J$ c #535BBC", +"K$ c #7783D0", +"L$ c #A1ADE1", +"M$ c #B8C1EB", +"N$ c #A9B5E7", +"O$ c #8592DD", +"P$ c #6770D2", +"Q$ c #343BB3", +"R$ c #15189D", +"S$ c #0E1296", +"T$ c #06078E", +"U$ c #00038A", +"V$ c #6C5D7D", +"W$ c #967A53", +"X$ c #333068", +"Y$ c #000065", +"Z$ c #020366", +"`$ c #09097E", +" % c #0B0B8D", +".% c #1011B3", +"+% c #282988", +"@% c #3C3D8F", +"#% c #13149E", +"$% c #2F35B7", +"%% c #5861C8", +"&% c #2427A4", +"*% c #474AA4", +"=% c #262BA1", +"-% c #535ABC", +";% c #A1AEE1", +">% c #B9C0EB", +",% c #ABB7E7", +"'% c #8794DD", +")% c #6972D2", +"!% c #555CC9", +"~% c #454DBF", +"{% c #343CB3", +"]% c #2327A7", +"^% c #0E0F96", +"/% c #000089", +"(% c #62609E", +"_% c #CDC18D", +":% c #A49567", +"<% c #E7D376", +"[% c #57506A", +"}% c #000064", +"|% c #040471", +"1% c #0C0C8E", +"2% c #1213B7", +"3% c #272A7A", +"4% c #1314A2", +"5% c #343ABC", +"6% c #626DCC", +"7% c #2529A6", +"8% c #4245A1", +"9% c #262BA0", +"0% c #5259BB", +"a% c #7681CF", +"b% c #A1ACE1", +"c% c #B9C1EB", +"d% c #ADB9E7", +"e% c #8A97DD", +"f% c #6B75D3", +"g% c #575FC9", +"h% c #363CB4", +"i% c #2427A8", +"j% c #17199E", +"k% c #000092", +"l% c #33329C", +"m% c #ECE4B3", +"n% c #F9E78D", +"o% c #E8D67D", +"p% c #EEDA7C", +"q% c #F5E17D", +"r% c #61596F", +"s% c #00006F", +"t% c #06057E", +"u% c #0D0E91", +"v% c #1313B7", +"w% c #131563", +"x% c #1315A6", +"y% c #3D45C1", +"z% c #6873D1", +"A% c #22259D", +"B% c #5356A8", +"C% c #22289D", +"D% c #5058B9", +"E% c #9FACE0", +"F% c #B9C1EA", +"G% c #AEB9E6", +"H% c #8B98DD", +"I% c #6C76D2", +"J% c #5760C9", +"K% c #484FBE", +"L% c #373EB4", +"M% c #262AA8", +"N% c #0E0F9D", +"O% c #151899", +"P% c #CBCABE", +"Q% c #FFFAAA", +"R% c #E7D484", +"S% c #E4D17C", +"T% c #E0CE81", +"U% c #FBE884", +"V% c #AE9F74", +"W% c #05056F", +"X% c #00017E", +"Y% c #1416B8", +"Z% c #111371", +"`% c #1417A7", +" & c #5159C8", +".& c #616CCF", +"+& c #161980", +"@& c #7B7EBC", +"#& c #1B2196", +"$& c #4E55B7", +"%& c #737ECC", +"&& c #B8C2EA", +"*& c #AFBAE8", +"=& c #8D99DC", +"-& c #6D77D1", +";& c #474FBE", +">& c #262CA9", +",& c #05089A", +"'& c #3636A0", +")& c #EEEABC", +"!& c #F7E99D", +"~& c #E5D381", +"{& c #E4D27F", +"]& c #E3D386", +"^& c #F2DF86", +"/& c #BAAA74", +"(& c #0E0D71", +"_& c #00007F", +":& c #0F1099", +"<& c #090949", +"[& c #08092A", +"}& c #121388", +"|& c #6C79D2", +"1& c #4B53C5", +"2& c #070959", +"3& c #212696", +"4& c #4C53B3", +"5& c #717CC9", +"6& c #9CA9DC", +"7& c #B8C1E8", +"8& c #B0BAE4", +"9& c #6D77D0", +"0& c #575FC8", +"a& c #474FBF", +"b& c #383FB5", +"c& c #272CAA", +"d& c #06099B", +"e& c #3030A0", +"f& c #E6DFB4", +"g& c #F5E594", +"h& c #E5D586", +"i& c #E4D48C", +"j& c #EDDA85", +"k& c #CDB96E", +"l& c #181671", +"m& c #000080", +"n& c #10109A", +"o& c #1314B7", +"p& c #121391", +"q& c #07081B", +"r& c #02030E", +"s& c #101268", +"t& c #101295", +"u& c #3A41BC", +"v& c #727FD4", +"w& c #2227AB", +"x& c #272B97", +"y& c #4A4FB0", +"z& c #6F78C5", +"A& c #99A6DA", +"B& c #B5BFE6", +"C& c #AEB8E4", +"D& c #8D99DA", +"E& c #6D76CE", +"F& c #575EC5", +"G& c #474EBD", +"H& c #090B9B", +"I& c #2D2D9E", +"J& c #E4DAAB", +"K& c #F5E38D", +"L& c #E5D384", +"M& c #E9D98F", +"N& c #ECDE96", +"O& c #EFDE88", +"P& c #E2D06E", +"Q& c #2A286F", +"R& c #000082", +"S& c #11119D", +"T& c #1415B2", +"U& c #141599", +"V& c #0E1051", +"W& c #03051D", +"X& c #0D105C", +"Y& c #13148B", +"Z& c #1B1CA4", +"`& c #626BCD", +" * c #484FC4", +".* c #0C0D7A", +"+* c #232895", +"@* c #454CAD", +"#* c #6C75C1", +"$* c #95A2D6", +"%* c #B1BCE4", +"&* c #ACB6E4", +"** c #8C98D8", +"=* c #6C75CC", +"-* c #565DC3", +";* c #474EBC", +">* c #282CA9", +",* c #090C9D", +"'* c #27299B", +")* c #DFD4A3", +"!* c #EDDD90", +"~* c #E7D78F", +"{* c #D3BF7D", +"]* c #CFB96D", +"^* c #C5AD4E", +"/* c #3F3A70", +"(* c #000085", +"_* c #11119F", +":* c #161897", +"<* c #161981", +"[* c #0B0D5D", +"}* c #0F127A", +"|* c #16189A", +"1* c #474DBD", +"2* c #555DC9", +"3* c #181CA7", +"4* c #060735", +"5* c #242799", +"6* c #4248AA", +"7* c #6771BD", +"8* c #909CD1", +"9* c #ACB6E0", +"0* c #A8B3E1", +"a* c #8A97D6", +"b* c #6B75CA", +"c* c #555DC0", +"d* c #474EBA", +"e* c #383FB2", +"f* c #282DA9", +"g* c #0B0E9D", +"h* c #222499", +"i* c #DED69F", +"j* c #B69B52", +"k* c #B49B5F", +"l* c #B0985E", +"m* c #967A44", +"n* c #92763C", +"o* c #B6983B", +"p* c #574F73", +"q* c #000087", +"r* c #1111A1", +"s* c #1316B1", +"t* c #181B97", +"u* c #1A1D8D", +"v* c #0D0F68", +"w* c #141786", +"x* c #383FB0", +"y* c #4B53C4", +"z* c #2225B5", +"A* c #0B0C5E", +"B* c #000000", +"C* c #212799", +"D* c #3E45A7", +"E* c #626AB9", +"F* c #8894CD", +"G* c #A0ADDB", +"H* c #A0ADDA", +"I* c #8794D2", +"J* c #6973C5", +"K* c #454BB6", +"L* c #383EAF", +"M* c #282CA6", +"N* c #0B0E9B", +"O* c #242596", +"P* c #D8CA8F", +"Q* c #9D7D3A", +"R* c #7D5C2E", +"S* c #967944", +"T* c #C3AB6A", +"U* c #987A3D", +"V* c #A7852C", +"W* c #5C5063", +"X* c #00008C", +"Y* c #1111A3", +"Z* c #1315AF", +"`* c #181993", +" = c #1F22A1", +".= c #1A1C8A", +"+= c #2A2FA5", +"@= c #2F36BA", +"#= c #1B1EAD", +"$= c #0B0D65", +"%= c #010108", +"&= c #212599", +"*= c #3A40A5", +"== c #5A63B4", +"-= c #7D88C5", +";= c #94A0D3", +">= c #95A4D6", +",= c #818ECD", +"'= c #6670C0", +")= c #525AB9", +"!= c #444AB2", +"~= c #363DAC", +"{= c #272CA4", +"]= c #0B0D99", +"^= c #2A2A96", +"/= c #BEAA72", +"(= c #825D21", +"_= c #81602E", +":= c #8E6D36", +"<= c #926F33", +"[= c #C0A147", +"}= c #C9AB41", +"|= c #554C66", +"1= c #00008B", +"2= c #1011A6", +"3= c #1313AE", +"4= c #12137E", +"5= c #1D1EA0", +"6= c #2022AD", +"7= c #1C1FA7", +"8= c #12138B", +"9= c #080947", +"0= c #000002", +"a= c #1E2292", +"b= c #343AA3", +"c= c #525AAE", +"d= c #6F78BD", +"e= c #8591C8", +"f= c #8A95CD", +"g= c #7983C7", +"h= c #636BBD", +"i= c #5057B5", +"j= c #4349B0", +"k= c #363CA9", +"l= c #282BA1", +"m= c #0A0C97", +"n= c #252895", +"o= c #BDA666", +"p= c #DCC261", +"q= c #DED282", +"r= c #B9A86E", +"s= c #92855F", +"t= c #58514E", +"u= c #222051", +"v= c #040470", +"w= c #08088B", +"x= c #100FA9", +"y= c #1012A8", +"z= c #0D0E5B", +"A= c #0D0E5F", +"B= c #0C0E5C", +"C= c #08093F", +"D= c #01010E", +"E= c #191C7E", +"F= c #3035A5", +"G= c #4A50A9", +"H= c #626BB6", +"I= c #757FBF", +"J= c #7A85C2", +"K= c #6F78BF", +"L= c #5D64B6", +"M= c #4D54B1", +"N= c #4146AC", +"O= c #343AA5", +"P= c #252A9E", +"Q= c #131595", +"R= c #0D0F8C", +"S= c #686286", +"T= c #625C78", +"U= c #1F1D63", +"V= c #000057", +"W= c #00005A", +"X= c #00016C", +"Y= c #07087E", +"Z= c #0E0EB1", +"`= c #0B0B7C", +" - c #000102", +".- c #121463", +"+- c #4248A4", +"@- c #565DAE", +"#- c #646CB7", +"$- c #6870B9", +"%- c #636AB7", +"&- c #555CB1", +"*- c #484EAB", +"=- c #3E44A7", +"-- c #3238A3", +";- c #24289C", +">- c #171993", +",- c #0C0E8A", +"'- c #000073", +")- c #00006B", +"!- c #000167", +"~- c #050567", +"{- c #07086C", +"]- c #0A0A75", +"^- c #0A0A83", +"/- c #0D0DB6", +"(- c #07074F", +"_- c #0E104E", +":- c #2529A1", +"<- c #393FA0", +"[- c #4A51A8", +"}- c #565EAF", +"|- c #5B63B2", +"1- c #575FB1", +"2- c #4D54AD", +"3- c #4248A7", +"4- c #393EA4", +"5- c #2F34A0", +"6- c #222598", +"7- c #141691", +"8- c #0E1089", +"9- c #0B0C7F", +"0- c #08086C", +"a- c #060667", +"b- c #060668", +"c- c #0B0B88", +"d- c #0C0DB7", +"e- c #050537", +"f- c #03041D", +"g- c #1C2093", +"h- c #3138A4", +"i- c #4047A2", +"j- c #4950A9", +"k- c #4E55AC", +"l- c #4C53AB", +"m- c #454BA9", +"n- c #3B42A5", +"o- c #3339A1", +"p- c #2A2F9C", +"q- c #1E2096", +"r- c #13158F", +"s- c #0D0F86", +"t- c #0B0B7E", +"u- c #0B0B8C", +"v- c #0D0DAE", +"w- c #0A0B93", +"x- c #01010D", +"y- c #0E0F4E", +"z- c #272BA8", +"A- c #353BA1", +"B- c #3D45A1", +"C- c #4248A5", +"D- c #4147A7", +"E- c #3C42A5", +"F- c #353AA1", +"G- c #2E339E", +"H- c #252999", +"I- c #1C1E94", +"J- c #13148C", +"K- c #0D0E83", +"L- c #0C0B7B", +"M- c #09096C", +"N- c #0C0C9E", +"O- c #040539", +"P- c #0E1150", +"Q- c #23279E", +"R- c #2F34A3", +"S- c #353C9F", +"T- c #3238A1", +"U- c #2D339D", +"V- c #272A9A", +"W- c #1F2196", +"X- c #171991", +"Y- c #111288", +"Z- c #0C0D80", +"`- c #090970", +" ; c #090982", +".; c #0B0B99", +"+; c #0A0B99", +"@; c #04043B", +"#; c #060726", +"$; c #151870", +"%; c #212597", +"&; c #282C9F", +"*; c #262B9E", +"=; c #24279B", +"-; c #202397", +";; c #1B1C93", +">; c #0E1186", +",; c #0C0D7F", +"'; c #090980", +"); c #080889", +"!; c #090987", +"~; c #070761", +"{; c #020215", +"]; c #050622", +"^; c #0D0E4A", +"/; c #131467", +"(; c #16177F", +"_; c #15188A", +":; c #0F138A", +"<; c #0C0E85", +"[; c #0B0B81", +"}; c #08087A", +"|; c #07076F", +"1; c #050540", +"2; c #030318", +"3; c #01020F", +"4; c #02031B", +"5; c #040524", +"6; c #040526", +"7; c #030427", +"8; c #030327", +"9; c #030322", +"0; c #020318", +"a; c #010100", +" ", +" ", +" ", +" . + @ # $ % & * = ", +" - ; > , ' ) ! ~ { ] ^ / ( _ : < ", +" [ } | 1 2 3 4 5 6 7 8 9 0 a b c d e f g h ", +" i j k l m n o p q 6 r s t u v w x y z A B C D ", +" E F G H I J K n I L M N O P Q R S T U V W X Y Z ` ", +" ...+.@.#.$.I o %.&.*.=.-.;.>.,.'.).!.~.{.].^./.(._.:. ", +" <.[.K }.|.1.2.3.4.5.6.7.8.9.0.a.b.c.d.e.f.g.h.i.j.k.l. ", +" m.n.o.m p.q.r.r.s.r.t.u.v.w.x.y.z.A.B.C.D.E.F.G.H.I.J. ", +" K.L.M.N.O.P.Q.R.q.R.S.T.U.V.W.X.Y.Z.`. +.+++@+#+$+%+&+ ", +" *+=+-+;+>+,+'+)+!+~+{+]+^+/+(+_+:+<+[+}+|+1+2+3+4+5+6+ ", +" 7+8+9+0+a+b+c+,+O.d+e+f+g+h+i+j+k+l+X.m+n+o+p+q+r+s+t+ ", +" u+v+w+x+y+a+z+A+B+C+D+E+F+G+H+I+J+K+L+M+N+O+P+Q+R+S+T+ U+V+W+X+ ", +" Y+Z+`+ @.@+@@@#@$@%@&@*@=@-@;@>@,@'@)@!@~@{@]@^@/@(@_@:@<@[@}@|@1@2@ ", +" 3@4@5@6@7@8@9@0@a@b@c@d@e@f@g@h@i@j@k@l@m@n@o@M p@q@r@s@t@u@v@w@x@y@z@ ", +" A@B@C@D@E@F@G@H@I@J@K@L@M@N@O@P@Q@R@S@T@U@5 V@W@X@Y@Z@`@ #.#+#+#@###$# ", +" %#&#*#=#-#;#>#,#'#)#!#~#{#]#^#/#(#_#:#<#[#}#$.W@|#Y@1#2#3#4#5#6#7#8#9# ", +" 0#a#b#c#d#e#f#g#h#i#j#k#l#m#n#o#p#q#r#s#g@t#u#W@X@v#w#x###+#y#z#A#B#C#D# ", +" E#F#b#G#H#I#J#K#L#M#N#O#P#Q#R#S#T#U#V#W#X#Y#J Z#`# $.$+$@$#$ $$%$C.&$*$ ", +" =$-$b#;$>$,$'$)$!$~${$]$^$/$($_$:$<$[$}$|$1$n V@2$3$4$5$6$ 7$8$9$0$a$ ", +" b$c$d$e$f$g$h$i$j$~$k$l$m$n$o$p$q$r$s$t$u$v$t#w$x$y$z$A$B$ C$D$E$F$G$ ", +" H$I$J$K$L$M$N$O$P$~$k$Q$# R$S$T$U$V$W$X$Y$Z$Y#`$ %.%+% @%#%$%%%&% ", +" *%=%-%e$;%>%,%'%)%!%~%{%]%R$^%/%(%_%:%<%[%}%|%o.1%2% 3%4%5%6%7% ", +" 8%9%0%a%b%c%d%e%f%g%{$h%i%j%k%l%m%n%o%p%q%r%s%t%u%v% w%x%y%z%A% ", +" B%C%D%6@E%F%G%H%I%J%K%L%M%N%O%P%Q%R%S%T%U%V%W%X%^%Y% Z%`% &.&+& ", +" @&#&$&%&>$&&*&=&-&g%;&]$>&,&'&)&!&~&{&]&^&/&(&_&:&Y%<& [&}&K.|&1&2& ", +" 3&4&5&6&7&8&=&9&0&a&b&c&d&e&f&g&~&h&i&j&k&l&m&n&o&p&q& r&s&t&u&v&w& ", +" x&y&z&A&B&C&D&E&F&G&]$% H&I&J&K&L&M&N&O&P&Q&R&S&T&U&V&W&X&Y&Z&`& *.* ", +" +*@*#*$*%*&***=*-*;*]$>*,*'*)*n%!*~*{*]*^*/*(*_*T&:*<*[*}*|*1*2*3*4* ", +" 5*6*7*8*9*0*a*b*c*d*e*f*g*h*i*j*k*l*m*n*o*p*q*r*s*t*u*v*w*x*y*z*A*B*B* ", +" C*D*E*F*G*H*I*J*b#K*L*M*N*O*P*Q*R*S*T*U*V*W*X*Y*Z*`* =.=+=@=#=$=%=B* ", +" &=*===-=;=>=,='=)=!=~={=]=^=/=(=_=:=<=[=}=|=1=2=3=4=5=6=7=8=9=0=B* ", +" a=b=c=d=e=f=g=h=i=j=k=l=m=n=o=p=q=r=s=t=u=v=w=x=y=z=A=B=C=D=B*B* ", +" E=F=G=H=I=J=K=L=M=N=O=P=Q=R=S=T=U=V=<@W=X=Y=1%Z=`= -B*B*B*B*B* ", +" .-Q#+-@-#-$-%-&-*-=---;->-,-_&'-)-!-~-{-]-^-X@/-(-B*B*B*B* ", +" _-:-<-[-}-|-1-2-3-4-5-6-7-8-9-n 0-a-b-1$J c-/@d-e-B*B*B* ", +" f-g-h-i-j-k-l-m-n-o-p-q-r-s-t-o 0-v$0-t#`$u-v-w-x-B*B* ", +" y-z-A-B-C-D-E-F-G-H-I-J-K-L-t#M-1$+.J ^-N-v-O-B* ", +" P-Q-R-S-A-T-U-V-W-X-Y-Z-}#t#+.`-K ;.;+;@;B* ", +" #;$;%;&;*;=;-;;;9+>;,;*.K I ';);!;~;{;B* ", +" ];^;/;(;_;J-:;<;[;#.};|;P.1;2;B* ", +" 3;4;5;6;7;8;9;0;x-a; ", +" ", +" ", +" ", +" "}; diff --git a/engines/ags/engine/platform/osx/acplmac.cpp b/engines/ags/engine/platform/osx/acplmac.cpp new file mode 100644 index 00000000000..64c590a6517 --- /dev/null +++ b/engines/ags/engine/platform/osx/acplmac.cpp @@ -0,0 +1,149 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_MACOS + +// ********* MacOS PLACEHOLDER DRIVER ********* + +//#include "util/wgt2allg.h" +//#include "gfx/ali3d.h" +//#include "ac/runtime_defines.h" +//#include "main/config.h" +//#include "plugin/agsplugin.h" +//#include +//#include +//#include +#include "platform/base/agsplatformdriver.h" +#include "util/directory.h" +#include "ac/common.h" +#include "main/main.h" + +void AGSMacInitPaths(char gamename[256], char appdata[PATH_MAX]); +void AGSMacGetBundleDir(char gamepath[PATH_MAX]); +//bool PlayMovie(char const *name, int skipType); + +static char libraryApplicationSupport[PATH_MAX]; +static char commonDataPath[PATH_MAX]; + +struct AGSMac : AGSPlatformDriver { + AGSMac(); + + virtual int CDPlayerCommand(int cmdd, int datt) override; + virtual void DisplayAlert(const char*, ...) override; + virtual unsigned long GetDiskFreeSpaceMB() override; + virtual const char* GetNoMouseErrorString() override; + virtual eScriptSystemOSID GetSystemOSID() override; + virtual int InitializeCDPlayer() override; + virtual void PostAllegroExit() override; + virtual void SetGameWindowIcon() override; + virtual void ShutdownCDPlayer() override; + + virtual const char *GetUserSavedgamesDirectory() override; + virtual const char *GetAllUsersDataDirectory() override; + virtual const char *GetUserConfigDirectory() override; + virtual const char *GetAppOutputDirectory() override; + virtual const char *GetIllegalFileChars() override; +}; + +AGSMac::AGSMac() +{ + AGSMacInitPaths(psp_game_file_name, libraryApplicationSupport); + + snprintf(commonDataPath, PATH_MAX, "%s/uk.co.adventuregamestudio", libraryApplicationSupport); + AGS::Common::Directory::CreateDirectory(commonDataPath); + + strcpy(psp_translation, "default"); +} + +int AGSMac::CDPlayerCommand(int cmdd, int datt) { + return 0;//cd_player_control(cmdd, datt); +} + +void AGSMac::DisplayAlert(const char *text, ...) { + char displbuf[2000]; + va_list ap; + va_start(ap, text); + vsprintf(displbuf, text, ap); + va_end(ap); + if (_logToStdErr) + fprintf(stderr, "%s\n", displbuf); + else + fprintf(stdout, "%s\n", displbuf); +} + +unsigned long AGSMac::GetDiskFreeSpaceMB() { + // placeholder + return 100; +} + +const char* AGSMac::GetNoMouseErrorString() { + return "This game requires a mouse. You need to configure and setup your mouse to play this game.\n"; +} + +eScriptSystemOSID AGSMac::GetSystemOSID() { + // override performed if `override.os` is set in config. + return eOS_Mac; +} + +int AGSMac::InitializeCDPlayer() { + //return cd_player_init(); + return 0; +} + +void AGSMac::PostAllegroExit() { + // do nothing +} + +void AGSMac::SetGameWindowIcon() { + // do nothing +} + +void AGSMac::ShutdownCDPlayer() { + //cd_exit(); +} + +const char* AGSMac::GetAllUsersDataDirectory() +{ + return commonDataPath; +} + +const char *AGSMac::GetUserSavedgamesDirectory() +{ + return libraryApplicationSupport; +} + +const char *AGSMac::GetUserConfigDirectory() +{ + return libraryApplicationSupport; +} + +const char *AGSMac::GetAppOutputDirectory() +{ + return commonDataPath; +} + +const char *AGSMac::GetIllegalFileChars() +{ + return "\\/:?\"<>|*"; // keep same as Windows so we can sync. +} + +AGSPlatformDriver* AGSPlatformDriver::GetDriver() { + if (instance == NULL) + instance = new AGSMac(); + return instance; +} + +#endif diff --git a/engines/ags/engine/platform/osx/alplmac.mm b/engines/ags/engine/platform/osx/alplmac.mm new file mode 100644 index 00000000000..c245aec1b93 --- /dev/null +++ b/engines/ags/engine/platform/osx/alplmac.mm @@ -0,0 +1,30 @@ +#import + +#include + +void AGSMacInitPaths(char gamename[256], char appdata[PATH_MAX]) +{ + strcpy(gamename, "game.ags"); + + @autoreleasepool { + NSBundle *bundle = [NSBundle mainBundle]; + NSString *resourcedir = [bundle resourcePath]; + [[NSFileManager defaultManager] changeCurrentDirectoryPath:resourcedir]; + + NSURL *path = [[NSFileManager defaultManager] URLForDirectory:NSApplicationSupportDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:NULL]; + + snprintf(appdata, PATH_MAX, "%s", [[path path] UTF8String]); + } +} + +// e.g. "/Users//Library/Application Support/Steam/steamapps/common/" +void AGSMacGetBundleDir(char gamepath[PATH_MAX]) +{ + @autoreleasepool { + NSBundle *bundle = [NSBundle mainBundle]; + NSString *bundleDir = [bundle bundlePath]; + + NSString *parentDir = [bundleDir stringByDeletingLastPathComponent]; + strcpy(gamepath, [parentDir UTF8String]); + } +} diff --git a/engines/ags/engine/platform/util/libc.c b/engines/ags/engine/platform/util/libc.c new file mode 100644 index 00000000000..f88f89cc4bd --- /dev/null +++ b/engines/ags/engine/platform/util/libc.c @@ -0,0 +1,54 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Implementations for missing libc functions +// +//============================================================================= + +#include "core/platform.h" + +#if ! AGS_PLATFORM_OS_WINDOWS + +#include +#include +#include +#include +#include + +size_t mbstowcs(wchar_t *wcstr, const char *mbstr, size_t max) +{ + int count = 0; + + while ((count < max) && (*mbstr != 0)) + { + *wcstr++ = *mbstr++; + count++; + } + return count; + +} + +size_t wcstombs(char* mbstr, const wchar_t *wcstr, size_t max) +{ + int count = 0; + + while ((count < max) && (*wcstr != 0)) + { + *mbstr++ = *wcstr++; + count++; + } + return count; +} + +#endif // ! AGS_PLATFORM_OS_WINDOWS diff --git a/engines/ags/engine/platform/util/pe.c b/engines/ags/engine/platform/util/pe.c new file mode 100644 index 00000000000..de7f6528778 --- /dev/null +++ b/engines/ags/engine/platform/util/pe.c @@ -0,0 +1,311 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Functions for reading version information from PE (Windows EXE) files +// +//============================================================================= + +#if defined(ANDROID) || defined(PSP) + +#include "pe.h" + +#include +#include +#include "util/stdio_compat.h" + + +// Simplified structs for PE files + +typedef struct { + char padding[60]; + unsigned int e_lfanew; +} IMAGE_DOS_HEADER; + +typedef struct { + char padding[2]; + unsigned short NumberOfSections; + char padding1[16]; +} IMAGE_FILE_HEADER; + +typedef struct { + unsigned int Signature; + IMAGE_FILE_HEADER FileHeader; + char padding[224]; +} IMAGE_NT_HEADERS; + +typedef struct { + char Name[8]; + char padding[4]; + unsigned int VirtualAddress; + char padding1[4]; + unsigned int PointerToRawData; + char padding2[16]; +} IMAGE_SECTION_HEADER; + + +// These structs are original + +typedef struct { + unsigned int Characteristics; + unsigned int TimeDateStamp; + unsigned short MajorVersion; + unsigned short MinorVersion; + unsigned short NumberOfNamedEntries; + unsigned short NumberOfIdEntries; +} IMAGE_RESOURCE_DIRECTORY; + +typedef struct { + union { + struct { + unsigned int NameOffset:31; + unsigned int NameIsString:1; + }; + unsigned int Name; + unsigned short Id; + }; + union { + unsigned int OffsetToData; + struct { + unsigned int OffsetToDirectory:31; + unsigned int DataIsDirectory:1; + }; + }; +} IMAGE_RESOURCE_DIRECTORY_ENTRY; + +typedef struct { + unsigned int OffsetToData; + unsigned int Size; + unsigned int CodePage; + unsigned int Reserved; +} IMAGE_RESOURCE_DATA_ENTRY; + + + +// Version information + +typedef struct { + unsigned int dwSignature; + unsigned int dwStrucVersion; + unsigned int dwFileVersionMS; + unsigned int dwFileVersionLS; + unsigned int dwProductVersionMS; + unsigned int dwProductVersionLS; + unsigned int dwFileFlagsMask; + unsigned int dwFileFlags; + unsigned int dwFileOS; + unsigned int dwFileType; + unsigned int dwFileSubtype; + unsigned int dwFileDateMS; + unsigned int dwFileDateLS; +} VS_FIXEDFILEINFO; + +typedef struct { + unsigned short wLength; + unsigned short wValueLength; + unsigned short wType; +} STRINGFILEINFO_HEADER; + + + +IMAGE_DOS_HEADER dos_header; +IMAGE_NT_HEADERS nt_headers; +IMAGE_SECTION_HEADER section_header; +IMAGE_RESOURCE_DIRECTORY resource_directory; +IMAGE_RESOURCE_DIRECTORY_ENTRY resource_directory_entry; +IMAGE_RESOURCE_DATA_ENTRY resource_data_entry; + +unsigned int resource_virtual_address; +unsigned int resource_start; + + + + +void fillBufferFromWidechar(unsigned short* inputBuffer, char* outputText) +{ + unsigned short* input = inputBuffer; + char* output = outputText; + + while (*input) + *output++ = *input++; + + *output = '\0'; +} + + + +int getVersionString(char* version_data, unsigned int size, char* buffer, char* name) +{ + char* current = version_data; + char* last = version_data + size; + + char temp[200]; + + // Skip header + current += 0x28; + + // Skip VS_FIXEDFILEINFO + current += sizeof(VS_FIXEDFILEINFO); + + // Now comes either "StringFileInfo" or "VarFileInfo" + STRINGFILEINFO_HEADER* stringfileinfo_header = (STRINGFILEINFO_HEADER*)current; + current += sizeof(STRINGFILEINFO_HEADER); + fillBufferFromWidechar((unsigned short*)current, temp); + if (strcmp(temp, "VarFileInfo") == 0) + { + current += (stringfileinfo_header->wLength - sizeof(STRINGFILEINFO_HEADER)); + + // Skip "StringFileInfo" header too + stringfileinfo_header = (STRINGFILEINFO_HEADER*)current; + current += 0x3C; + } + else + current += (0x3C - sizeof(STRINGFILEINFO_HEADER)); + + while (current < last) + { + STRINGFILEINFO_HEADER* header = (STRINGFILEINFO_HEADER*)current; + current += sizeof(STRINGFILEINFO_HEADER); + + // Read name + fillBufferFromWidechar((unsigned short*)current, temp); + + if (strcmp(temp, name) == 0) + { + current += (2 + 2 * strlen(temp)); + + // Next value is 32 bit aligned + current = (char*)((unsigned long)(current + 3) & (~3)); + + // Read value + fillBufferFromWidechar((unsigned short*)current, buffer); + + return 1; + } + else + current += (header->wLength - sizeof(STRINGFILEINFO_HEADER)); + + // Next value is 32 bit aligned + current = (char*)((unsigned long)(current + 3) & (~3)); + } + + return 0; +} + + + +int seekToResource(FILE* pe, int id) +{ + int i; + + // Read in resource directory + fread(&resource_directory, sizeof(IMAGE_RESOURCE_DIRECTORY), 1, pe); + + // Loop through root node entries till we find the entry with the given id + for (i = 0; i < resource_directory.NumberOfIdEntries + resource_directory.NumberOfNamedEntries; i++) + { + // Read in resource node + fread(&resource_directory_entry, sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY), 1, pe); + + if ((!resource_directory_entry.NameIsString) && (resource_directory_entry.Id == id)) + { + // Seek to end of subdirectory + ags_fseek(pe, resource_start + resource_directory_entry.OffsetToDirectory, SEEK_SET); + return 1; + } + } + + return 0; +} + + + +int getVersionInformation(char* filename, version_info_t* version_info) +{ + FILE* pe = fopen(filename, "rb"); + + if (!pe) + return 0; + + // Read in the DOS header, get the offset to the PE header and seek + fread(&dos_header, sizeof(IMAGE_DOS_HEADER), 1, pe); + ags_fseek(pe, dos_header.e_lfanew, SEEK_SET); + + // Read in the PE header + fread(&nt_headers, sizeof(IMAGE_NT_HEADERS), 1, pe); + + // Loop through sections till we find the resource section + int i; + for (i = 0; i < nt_headers.FileHeader.NumberOfSections; i++) + { + fread(§ion_header, sizeof(IMAGE_SECTION_HEADER), 1, pe); + + if (strcmp(".rsrc", (char*)section_header.Name) == 0) + break; + } + + if (i == nt_headers.FileHeader.NumberOfSections) + goto error_exit; + + // Save virtual address of the resource section + resource_virtual_address = section_header.VirtualAddress; + + // Seek to the resource section + ags_fseek(pe, section_header.PointerToRawData, SEEK_SET); + + // Save file offset to the resource section + resource_start = section_header.PointerToRawData; + + // Search for the version resource in the resource tree + if (!seekToResource(pe, 16)) + goto error_exit; + + // Enter the first subdirectory in the version resource + if (!seekToResource(pe, 1)) + goto error_exit; + + // Hopefully found the resource + fread(&resource_directory, sizeof(IMAGE_RESOURCE_DATA_ENTRY), 1, pe); + + // Read in resource node + fread(&resource_directory_entry, sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY), 1, pe); + + // Seek to the resource data + ags_fseek(pe, resource_start + resource_directory_entry.OffsetToData, SEEK_SET); + + // Read in version info + fread(&resource_data_entry, sizeof(IMAGE_RESOURCE_DATA_ENTRY), 1, pe); + + // Finally we got a virtual address of the resource, now seek to it + ags_fseek(pe, resource_start + resource_data_entry.OffsetToData - resource_virtual_address, SEEK_SET); + + // Read version resource + char* version_data = (char*)malloc(resource_data_entry.Size); + fread(version_data, resource_data_entry.Size, 1, pe); + + memset(version_info, 0, sizeof(version_info_t)); + getVersionString(version_data, resource_data_entry.Size, version_info->version, "FileVersion"); + getVersionString(version_data, resource_data_entry.Size, version_info->description, "FileDescription"); + getVersionString(version_data, resource_data_entry.Size, version_info->internal_name, "InternalName"); + + free(version_data); + fclose(pe); + + return 1; + +error_exit: + fclose(pe); + return 0; +} + +#endif \ No newline at end of file diff --git a/engines/ags/engine/platform/util/pe.h b/engines/ags/engine/platform/util/pe.h new file mode 100644 index 00000000000..97b5e646462 --- /dev/null +++ b/engines/ags/engine/platform/util/pe.h @@ -0,0 +1,34 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_PLATFORM_UTIL_PE_H +#define __AGS_EE_PLATFORM_UTIL_PE_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + char version[15]; + char description[100]; + char internal_name[100]; +} version_info_t; + +int getVersionInformation(char* filename, version_info_t* version_info); + +#ifdef __cplusplus +} +#endif + +#endif // __AGS_EE_PLATFORM_UTIL_PE_H diff --git a/engines/ags/engine/platform/windows/acplwin.cpp b/engines/ags/engine/platform/windows/acplwin.cpp new file mode 100644 index 00000000000..8e3b91d6252 --- /dev/null +++ b/engines/ags/engine/platform/windows/acplwin.cpp @@ -0,0 +1,1149 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS + +// ********* WINDOWS ********* + +#include +#include +#include +#include "ac/common.h" +#include "ac/draw.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_display.h" +#include "ac/runtime_defines.h" +#include "ac/string.h" +#include "debug/out.h" +#include "gfx/graphicsdriver.h" +#include "gfx/bitmap.h" +#include "main/engine.h" +#include "platform/base/agsplatformdriver.h" +#include "platform/windows/setup/winsetup.h" +#include "plugin/agsplugin.h" +#include "util/file.h" +#include "util/stream.h" +#include "util/string_compat.h" +#include "media/audio/audio_system.h" + +#ifndef AGS_NO_VIDEO_PLAYER +extern void dxmedia_abort_video(); +extern void dxmedia_pause_video(); +extern void dxmedia_resume_video(); +extern char lastError[200]; +#endif + +using namespace AGS::Common; +using namespace AGS::Engine; + +extern GameSetupStruct game; +extern GameSetup usetup; +extern int our_eip; +extern IGraphicsDriver *gfxDriver; +extern color palette[256]; + +#include +#include +#include +#include +#include +#include + +#include + + +#ifndef CSIDL_LOCAL_APPDATA +#define CSIDL_LOCAL_APPDATA 0x001C +#define CSIDL_COMMON_APPDATA 0x0023 +#endif + +typedef struct BMP_EXTRA_INFO { + LPDIRECTDRAWSURFACE2 surf; + struct BMP_EXTRA_INFO *next; + struct BMP_EXTRA_INFO *prev; + int flags; + int lock_nesting; +} BMP_EXTRA_INFO; + +// from Allegro DDraw driver +extern "C" extern LPDIRECTDRAW2 directdraw; +extern "C" extern LPDIRECTSOUND directsound; +extern "C" extern LPDIRECTINPUTDEVICE mouse_dinput_device; +extern "C" extern LPDIRECTINPUTDEVICE key_dinput_device; + +char win32SavedGamesDirectory[MAX_PATH] = "\0"; +char win32AppDataDirectory[MAX_PATH] = "\0"; +String win32OutputDirectory; + +const unsigned int win32TimerPeriod = 1; + +extern SetupReturnValue acwsetup(const ConfigTree &cfg_in, ConfigTree &cfg_out, const String &game_data_dir, const char*, const char*); + +struct AGSWin32 : AGSPlatformDriver { + AGSWin32(); + ~AGSWin32(); + + virtual void AboutToQuitGame(); + virtual int CDPlayerCommand(int cmdd, int datt); + virtual void AttachToParentConsole(); + virtual void DisplayAlert(const char*, ...); + virtual int GetLastSystemError(); + virtual const char *GetAllUsersDataDirectory(); + virtual const char *GetUserSavedgamesDirectory(); + virtual const char *GetUserConfigDirectory(); + virtual const char *GetUserGlobalConfigDirectory(); + virtual const char *GetAppOutputDirectory(); + virtual const char *GetIllegalFileChars(); + virtual const char *GetGraphicsTroubleshootingText(); + virtual unsigned long GetDiskFreeSpaceMB(); + virtual const char* GetNoMouseErrorString(); + virtual bool IsMouseControlSupported(bool windowed); + virtual const char* GetAllegroFailUserHint(); + virtual eScriptSystemOSID GetSystemOSID(); + virtual int InitializeCDPlayer(); + virtual void PostAllegroInit(bool windowed); + virtual void PostAllegroExit(); + virtual SetupReturnValue RunSetup(const ConfigTree &cfg_in, ConfigTree &cfg_out); + virtual void SetGameWindowIcon(); + virtual void ShutdownCDPlayer(); + virtual void WriteStdOut(const char *fmt, ...); + virtual void WriteStdErr(const char *fmt, ...); + virtual void DisplaySwitchOut(); + virtual void DisplaySwitchIn(); + virtual void PauseApplication(); + virtual void ResumeApplication(); + virtual void GetSystemDisplayModes(std::vector &dms); + virtual bool EnterFullscreenMode(const Engine::DisplayMode &dm); + virtual bool ExitFullscreenMode(); + virtual void AdjustWindowStyleForFullscreen(); + virtual void AdjustWindowStyleForWindowed(); + virtual void RegisterGameWithGameExplorer(); + virtual void UnRegisterGameWithGameExplorer(); + virtual int ConvertKeycodeToScanCode(int keyCode); + virtual void ValidateWindowSize(int &x, int &y, bool borderless) const; + virtual bool LockMouseToWindow(); + virtual void UnlockMouse(); + +#ifndef AGS_NO_VIDEO_PLAYER + virtual void PlayVideo(const char* name, int skip, int flags); +#endif + + +private: + void add_game_to_game_explorer(IGameExplorer* pFwGameExplorer, GUID *guid, const char *guidAsText, bool allUsers); + void remove_game_from_game_explorer(IGameExplorer* pFwGameExplorer, GUID *guid, const char *guidAsText, bool allUsers); + void add_tasks_for_game(const char *guidAsText, const char *gameEXE, const char *workingFolder, bool allUsers); + void get_tasks_directory(char *directoryNameBuffer, const char *guidAsText, bool allUsers); + void update_game_explorer(bool add); + void create_shortcut(const char *pathToEXE, const char *workingFolder, const char *arguments, const char *shortcutPath); + void register_file_extension(const char *exePath); + void unregister_file_extension(); + + bool SetSystemDisplayMode(const DisplayMode &dm, bool fullscreen); + + bool _isDebuggerPresent; // indicates if the win app is running in the context of a debugger + DisplayMode _preFullscreenMode; // saved display mode before switching system to fullscreen + bool _isAttachedToParentConsole; // indicates if the win app is attached to the parent console +}; + +AGSWin32::AGSWin32() : + _isDebuggerPresent(::IsDebuggerPresent() != FALSE), + _isAttachedToParentConsole(false) +{ + // Do nothing. +} + +AGSWin32::~AGSWin32() { + if (_isAttachedToParentConsole) + { + ::FreeConsole(); + } +} + +void check_parental_controls() { + /* this doesn't work, it always just returns access depedning + on whether unrated games are allowed because of digital signature + BOOL bHasAccess = FALSE; + IGameExplorer* pFwGameExplorer = NULL; + + CoInitialize(NULL); + // Create an instance of the Game Explorer Interface + HRESULT hr = CoCreateInstance( __uuidof(GameExplorer), NULL, CLSCTX_INPROC_SERVER, __uuidof(IGameExplorer), (void**)&pFwGameExplorer); + if( FAILED(hr) || pFwGameExplorer == NULL ) { + // not Vista, do nothing + } + else { + char theexename[MAX_PATH] = "e:\\code\\ags\\acwin\\release\\acwin.exe"; + WCHAR wstrBinPath[MAX_PATH] = {0}; + MultiByteToWideChar(CP_ACP, 0, theexename, MAX_PATH, wstrBinPath, MAX_PATH); + BSTR bstrGDFBinPath = SysAllocString(wstrBinPath); + + hr = pFwGameExplorer->VerifyAccess( bstrGDFBinPath, &bHasAccess ); + SysFreeString(bstrGDFBinPath); + + if( FAILED(hr) || !bHasAccess ) { + char buff[300]; + sprintf(buff, "Parental controls block: %X b: %d", hr, bHasAccess); + quit(buff); + } + else { + platform->DisplayAlert("Parental controls: Access granted."); + } + + } + + if( pFwGameExplorer ) pFwGameExplorer->Release(); + CoUninitialize(); + */ +} + +void AGSWin32::create_shortcut(const char *pathToEXE, const char *workingFolder, const char *arguments, const char *shortcutPath) +{ + IShellLink* pShellLink = NULL; + HRESULT hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)&pShellLink); + + if ((SUCCEEDED(hr)) && (pShellLink != NULL)) + { + IPersistFile *pPersistFile = NULL; + if (FAILED(pShellLink->QueryInterface(IID_IPersistFile, (void**)&pPersistFile))) + { + this->DisplayAlert("Unable to add game tasks: QueryInterface for IPersistFile failed"); + pShellLink->Release(); + return; + } + + // Set the path to the shortcut target and add the description + if (FAILED(pShellLink->SetPath(pathToEXE))) + { + this->DisplayAlert("Unable to add game tasks: SetPath failed"); + } + else if (FAILED(pShellLink->SetWorkingDirectory(workingFolder))) + { + this->DisplayAlert("Unable to add game tasks: SetWorkingDirectory failed"); + } + else if ((arguments != NULL) && (FAILED(pShellLink->SetArguments(arguments)))) + { + this->DisplayAlert("Unable to add game tasks: SetArguments failed"); + } + else + { + WCHAR wstrTemp[MAX_PATH] = {0}; + MultiByteToWideChar(CP_ACP, 0, shortcutPath, -1, wstrTemp, MAX_PATH); + + if (FAILED(pPersistFile->Save(wstrTemp, TRUE))) + { + this->DisplayAlert("Unable to add game tasks: IPersistFile::Save failed"); + } + } + + pPersistFile->Release(); + } + + if (pShellLink) pShellLink->Release(); +} + +void CopyStringAndRemoveInvalidFilenameChars(const char *source, char *destinationBuffer) +{ + int destIdx = 0; + for (int i = 0; i < (int)strlen(source); i++) + { + if ((source[i] != '/') && + (source[i] != '\\') && + (source[i] != ':') && + (source[i] != '*') && + (source[i] != '?') && + (source[i] != '"') && + (source[i] != '<') && + (source[i] != '>') && + (source[i] != '|') && + (source[i] >= 32)) + { + destinationBuffer[destIdx] = source[i]; + destIdx++; + } + } + destinationBuffer[destIdx] = 0; +} + +void AGSWin32::get_tasks_directory(char *pathBuffer, const char *guidAsText, bool allUsers) +{ + if (SHGetSpecialFolderPath(NULL, pathBuffer, allUsers ? CSIDL_COMMON_APPDATA : CSIDL_LOCAL_APPDATA, FALSE) == FALSE) + { + this->DisplayAlert("Unable to register game: SHGetSpecialFolderPath failed"); + return; + } + + if (pathBuffer[strlen(pathBuffer) - 1] != '\\') + { + strcat(pathBuffer, "\\"); + } + + strcat(pathBuffer, "Microsoft\\Windows\\GameExplorer\\"); + strcat(pathBuffer, guidAsText); + mkdir(pathBuffer); + strcat(pathBuffer, "\\"); + strcat(pathBuffer, "PlayTasks"); + mkdir(pathBuffer); +} + +void AGSWin32::add_tasks_for_game(const char *guidAsText, const char *gameEXE, const char *workingFolder, bool allUsers) +{ + char pathBuffer[MAX_PATH]; + get_tasks_directory(pathBuffer, guidAsText, allUsers); + strcat(pathBuffer, "\\"); + strcat(pathBuffer, "0"); + mkdir(pathBuffer); + + // Remove any existing "Play.lnk" from a previous version + char shortcutLocation[MAX_PATH]; + sprintf(shortcutLocation, "%s\\Play.lnk", pathBuffer); + ::remove(shortcutLocation); + + // Generate the shortcut file name (because it can appear on + // the start menu's Recent area) + char sanitisedGameName[MAX_PATH]; + CopyStringAndRemoveInvalidFilenameChars(game.gamename, sanitisedGameName); + if (sanitisedGameName[0] == 0) + strcpy(sanitisedGameName, "Play"); + sprintf(shortcutLocation, "%s\\%s.lnk", pathBuffer, sanitisedGameName); + + create_shortcut(gameEXE, workingFolder, NULL, shortcutLocation); + + pathBuffer[strlen(pathBuffer) - 1] = '1'; + mkdir(pathBuffer); + + sprintf(shortcutLocation, "%s\\Setup game.lnk", pathBuffer); + create_shortcut(gameEXE, workingFolder, "--setup", shortcutLocation); +} + +void AGSWin32::add_game_to_game_explorer(IGameExplorer* pFwGameExplorer, GUID *guid, const char *guidAsText, bool allUsers) +{ + WCHAR wstrTemp[MAX_PATH] = {0}; + bool hadError = false; + + char theexename[MAX_PATH]; + GetModuleFileName(NULL, theexename, MAX_PATH); + + MultiByteToWideChar(CP_ACP, 0, theexename, MAX_PATH, wstrTemp, MAX_PATH); + BSTR bstrGDFBinPath = SysAllocString(wstrTemp); + + char gameDirectory[MAX_PATH]; + strcpy(gameDirectory, theexename); + strrchr(gameDirectory, '\\')[0] = 0; + + MultiByteToWideChar(CP_ACP, 0, gameDirectory, MAX_PATH, wstrTemp, MAX_PATH); + BSTR bstrGameDirectory = SysAllocString(wstrTemp); + + HRESULT hr = pFwGameExplorer->AddGame(bstrGDFBinPath, bstrGameDirectory, allUsers ? GIS_ALL_USERS : GIS_CURRENT_USER, guid); + if ((FAILED(hr)) || (hr == S_FALSE)) + { + if (hr == 0x80070715) + { + // No GDF XML -- do nothing. This means the game was compiled + // without Game Explorer support. + hadError = true; + } + else + { + // Game already exists or error + HRESULT updateHr = pFwGameExplorer->UpdateGame(*guid); + if (FAILED(updateHr)) + { + this->DisplayAlert("Failed to add the game to the game explorer: %08X, %08X", hr, updateHr); + hadError = true; + } + } + } + else + { + add_tasks_for_game(guidAsText, theexename, gameDirectory, allUsers); + } + + BOOL bHasAccess = FALSE; + hr = pFwGameExplorer->VerifyAccess( bstrGDFBinPath, &bHasAccess ); + + if (( FAILED(hr) || !bHasAccess ) && (!hadError)) + { + this->DisplayAlert("Windows Parental Controls will not allow you to run this game."); + } + + SysFreeString(bstrGDFBinPath); + SysFreeString(bstrGameDirectory); +} + +#define FA_SEARCH -1 +void delete_files_in_directory(const char *directoryName, const char *fileMask) +{ + char srchBuffer[MAX_PATH]; + sprintf(srchBuffer, "%s\\%s", directoryName, fileMask); + al_ffblk dfb; + int dun = al_findfirst(srchBuffer, &dfb, FA_SEARCH); + while (!dun) { + ::remove(dfb.name); + dun = al_findnext(&dfb); + } + al_findclose(&dfb); +} + +void AGSWin32::remove_game_from_game_explorer(IGameExplorer* pFwGameExplorer, GUID *guid, const char *guidAsText, bool allUsers) +{ + HRESULT hr = pFwGameExplorer->RemoveGame(*guid); + if (FAILED(hr)) + { + this->DisplayAlert("Failed to un-register game: 0x%08X", hr); + } +} + +void AGSWin32::update_game_explorer(bool add) +{ + IGameExplorer* pFwGameExplorer = NULL; + + CoInitialize(NULL); + // Create an instance of the Game Explorer Interface + HRESULT hr = CoCreateInstance( __uuidof(GameExplorer), NULL, CLSCTX_INPROC_SERVER, __uuidof(IGameExplorer), (void**)&pFwGameExplorer); + if( FAILED(hr) || pFwGameExplorer == NULL ) + { + Debug::Printf(kDbgMsg_Warn, "Game Explorer not found to register game, Windows Vista required"); + } + else + { + ags_strupr(game.guid); + WCHAR wstrTemp[MAX_PATH] = {0}; + GUID guid = GUID_NULL; + MultiByteToWideChar(CP_ACP, 0, game.guid, MAX_GUID_LENGTH, wstrTemp, MAX_GUID_LENGTH); + if (IIDFromString(wstrTemp, &guid) != S_OK) + { + this->DisplayAlert("Failed to register game: IIDFromString failed"); + } + else if (add) + { + add_game_to_game_explorer(pFwGameExplorer, &guid, game.guid, true); + } + else + { + remove_game_from_game_explorer(pFwGameExplorer, &guid, game.guid, true); + } + } + + if( pFwGameExplorer ) pFwGameExplorer->Release(); + CoUninitialize(); +} + +void AGSWin32::unregister_file_extension() +{ + char keyname[MAX_PATH]; + sprintf(keyname, ".%s", game.saveGameFileExtension); + if (SHDeleteKey(HKEY_CLASSES_ROOT, keyname) != ERROR_SUCCESS) + { + this->DisplayAlert("Unable to un-register the file extension. Make sure you are running this with admin rights."); + return; + } + + sprintf(keyname, "AGS.SaveGames.%s", game.saveGameFileExtension); + SHDeleteKey(HKEY_CLASSES_ROOT, keyname); + + sprintf(keyname, "Software\\Microsoft\\Windows\\CurrentVersion\\PropertySystem\\PropertyHandlers\\.%s", game.saveGameFileExtension); + SHDeleteKey(HKEY_LOCAL_MACHINE, keyname); + + // Tell Explorer to refresh its file association data + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); +} + +void AGSWin32::register_file_extension(const char *exePath) +{ + DWORD valType, valBufLen = MAX_PATH; + valType = REG_SZ; + char valBuf[MAX_PATH], keyname[MAX_PATH]; + char saveGameRegistryType[MAX_PATH]; + sprintf(saveGameRegistryType, "AGS.SaveGames.%s", game.saveGameFileExtension); + + // write HKEY_CLASSES_ROOT\.Extension = AGS.SaveGames.Extension + strcpy(valBuf, saveGameRegistryType); + sprintf(keyname, ".%s", game.saveGameFileExtension); + if (RegSetValue(HKEY_CLASSES_ROOT, keyname, valType, valBuf, valBufLen)) + { + this->DisplayAlert("Unable to register file type. Make sure you are running this with Administrator rights."); + return; + } + + // create HKEY_CLASSES_ROOT\AGS.SaveGames.Extension + strcpy(keyname, saveGameRegistryType); + sprintf(valBuf, "%s Saved Game", game.gamename); + RegSetValue (HKEY_CLASSES_ROOT, keyname, REG_SZ, valBuf, strlen(valBuf)); + + // write HKEY_CLASSES_ROOT\AGS.SaveGames.Extension\DefaultIcon + sprintf(keyname, "%s\\DefaultIcon", saveGameRegistryType); + sprintf(valBuf, "\"%s\", 0", exePath); + RegSetValue (HKEY_CLASSES_ROOT, keyname, REG_SZ, valBuf, strlen(valBuf)); + + // write HKEY_CLASSES_ROOT\AGS.SaveGames.Extension\Shell\Open\Command + sprintf(keyname, "%s\\Shell\\Open\\Command", saveGameRegistryType); + sprintf(valBuf, "\"%s\" -loadSavedGame \"%%1\"", exePath); + RegSetValue (HKEY_CLASSES_ROOT, keyname, REG_SZ, valBuf, strlen(valBuf)); + + // ** BELOW IS VISTA-ONLY + + // write HKEY_CLASSES_ROOT\AGS.SaveGames.Extension, PreviewTitle + strcpy(keyname, saveGameRegistryType); + strcpy(valBuf, "prop:System.Game.RichSaveName;System.Game.RichApplicationName"); + SHSetValue(HKEY_CLASSES_ROOT, keyname, "PreviewTitle", REG_SZ, valBuf, strlen(valBuf)); + + // write HKEY_CLASSES_ROOT\AGS.SaveGames.Extension, PreviewDetails + strcpy(keyname, saveGameRegistryType); + strcpy(valBuf, "prop:System.Game.RichLevel;System.DateChanged;System.Game.RichComment;System.DisplayName;System.DisplayType"); + SHSetValue(HKEY_CLASSES_ROOT, keyname, "PreviewDetails", REG_SZ, valBuf, strlen(valBuf)); + + // write HKEY_CLASSES_ROOT\.Extension\ShellEx\{BB2E617C-0920-11D1-9A0B-00C04FC2D6C1} + sprintf(keyname, ".%s\\ShellEx\\{BB2E617C-0920-11D1-9A0B-00C04FC2D6C1}", game.saveGameFileExtension); + strcpy(valBuf, "{4E5BFBF8-F59A-4E87-9805-1F9B42CC254A}"); + RegSetValue (HKEY_CLASSES_ROOT, keyname, REG_SZ, valBuf, strlen(valBuf)); + + // write HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\PropertySystem\PropertyHandlers\.Extension + sprintf(keyname, "Software\\Microsoft\\Windows\\CurrentVersion\\PropertySystem\\PropertyHandlers\\.%s", game.saveGameFileExtension); + strcpy(valBuf, "{ECDD6472-2B9B-4B4B-AE36-F316DF3C8D60}"); + RegSetValue (HKEY_LOCAL_MACHINE, keyname, REG_SZ, valBuf, strlen(valBuf)); + + // Tell Explorer to refresh its file association data + SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL); +} + +void AGSWin32::RegisterGameWithGameExplorer() +{ + update_game_explorer(true); + + if (game.saveGameFileExtension[0] != 0) + { + char theexename[MAX_PATH]; + GetModuleFileName(NULL, theexename, MAX_PATH); + + register_file_extension(theexename); + } +} + +void AGSWin32::UnRegisterGameWithGameExplorer() +{ + update_game_explorer(false); + + if (game.saveGameFileExtension[0] != 0) + { + unregister_file_extension(); + } +} + +void AGSWin32::PostAllegroInit(bool windowed) +{ + check_parental_controls(); + + // Set the Windows timer resolution to 1 ms so that calls to + // Sleep() don't take more time than specified + MMRESULT result = timeBeginPeriod(win32TimerPeriod); + if (result != TIMERR_NOERROR) + Debug::Printf(kDbgMsg_Error, "Failed to set the timer resolution to %d ms", win32TimerPeriod); +} + +typedef UINT (CALLBACK* Dynamic_SHGetKnownFolderPathType) (GUID& rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath); +GUID FOLDERID_SAVEDGAMES = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}}; +#define _WIN32_WINNT_VISTA 0x0600 +#define VER_MINORVERSION 0x0000001 +#define VER_MAJORVERSION 0x0000002 +#define VER_SERVICEPACKMAJOR 0x0000020 +#define VER_GREATER_EQUAL 3 + +// These helpers copied from VersionHelpers.h in the Windows 8.1 SDK +bool IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) +{ + OSVERSIONINFOEXW osvi = { sizeof(osvi), 0, 0, 0, 0,{ 0 }, 0, 0 }; + DWORDLONG const dwlConditionMask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + + osvi.dwMajorVersion = wMajorVersion; + osvi.dwMinorVersion = wMinorVersion; + osvi.wServicePackMajor = wServicePackMajor; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE; +} + +bool IsWindowsVistaOrGreater() { + return IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), LOBYTE(_WIN32_WINNT_VISTA), 0); +} + +void determine_app_data_folder() +{ + if (win32AppDataDirectory[0] != 0) + { + // already discovered + return; + } + + WCHAR unicodePath[MAX_PATH]; + WCHAR unicodeShortPath[MAX_PATH]; + SHGetSpecialFolderPathW(NULL, unicodePath, CSIDL_COMMON_APPDATA, FALSE); + if (GetShortPathNameW(unicodePath, unicodeShortPath, MAX_PATH) == 0) + { + platform->DisplayAlert("Unable to get App Data dir: GetShortPathNameW failed"); + return; + } + WideCharToMultiByte(CP_ACP, 0, unicodeShortPath, -1, win32AppDataDirectory, MAX_PATH, NULL, NULL); + + strcat(win32AppDataDirectory, "\\Adventure Game Studio"); + mkdir(win32AppDataDirectory); +} + +void determine_saved_games_folder() +{ + if (win32SavedGamesDirectory[0] != 0) + { + // already discovered + return; + } + + WCHAR unicodeSaveGameDir[MAX_PATH] = L""; + WCHAR unicodeShortSaveGameDir[MAX_PATH] = L""; + + if (IsWindowsVistaOrGreater()) + { + HINSTANCE hShellDLL = LoadLibrary("shell32.dll"); + Dynamic_SHGetKnownFolderPathType Dynamic_SHGetKnownFolderPath = (Dynamic_SHGetKnownFolderPathType)GetProcAddress(hShellDLL, "SHGetKnownFolderPath"); + + if (Dynamic_SHGetKnownFolderPath != NULL) + { + PWSTR path = NULL; + if (SUCCEEDED(Dynamic_SHGetKnownFolderPath(FOLDERID_SAVEDGAMES, 0, NULL, &path))) + { + if (GetShortPathNameW(path, unicodeShortSaveGameDir, MAX_PATH) > 0) { + WideCharToMultiByte(CP_ACP, 0, unicodeShortSaveGameDir, -1, win32SavedGamesDirectory, MAX_PATH, NULL, NULL); + } + CoTaskMemFree(path); + } + } + + FreeLibrary(hShellDLL); + } + else + { + // Windows XP didn't have a "My Saved Games" folder, so create one under "My Documents" + SHGetSpecialFolderPathW(NULL, unicodeSaveGameDir, CSIDL_PERSONAL, FALSE); + // workaround for case where My Documents path has unicode chars (eg. + // with Russian Windows) -- so use Short File Name instead + if (GetShortPathNameW(unicodeSaveGameDir, unicodeShortSaveGameDir, MAX_PATH) > 0) + { + WideCharToMultiByte(CP_ACP, 0, unicodeShortSaveGameDir, -1, win32SavedGamesDirectory, MAX_PATH, NULL, NULL); + strcat(win32SavedGamesDirectory, "\\My Saved Games"); + mkdir(win32SavedGamesDirectory); + } + } + + // Fallback to a subdirectory of the app data directory + if (win32SavedGamesDirectory[0] == '\0') + { + determine_app_data_folder(); + strcpy(win32SavedGamesDirectory, win32AppDataDirectory); + strcat(win32SavedGamesDirectory, "\\Saved Games"); + mkdir(win32SavedGamesDirectory); + } +} + +void DetermineAppOutputDirectory() +{ + if (!win32OutputDirectory.IsEmpty()) + { + return; + } + + determine_saved_games_folder(); + bool log_to_saves_dir = false; + if (win32SavedGamesDirectory[0]) + { + win32OutputDirectory = win32SavedGamesDirectory; + win32OutputDirectory.Append("\\.ags"); + log_to_saves_dir = mkdir(win32OutputDirectory) == 0 || errno == EEXIST; + } + + if (!log_to_saves_dir) + { + char theexename[MAX_PATH + 1] = {0}; + GetModuleFileName(NULL, theexename, MAX_PATH); + PathRemoveFileSpec(theexename); + win32OutputDirectory = theexename; + } +} + +const char* AGSWin32::GetAllUsersDataDirectory() +{ + determine_app_data_folder(); + return &win32AppDataDirectory[0]; +} + +const char *AGSWin32::GetUserSavedgamesDirectory() +{ + determine_saved_games_folder(); + return win32SavedGamesDirectory; +} + +const char *AGSWin32::GetUserConfigDirectory() +{ + determine_saved_games_folder(); + return win32SavedGamesDirectory; +} + +const char *AGSWin32::GetUserGlobalConfigDirectory() +{ + DetermineAppOutputDirectory(); + return win32OutputDirectory; +} + +const char *AGSWin32::GetAppOutputDirectory() +{ + DetermineAppOutputDirectory(); + return win32OutputDirectory; +} + +const char *AGSWin32::GetIllegalFileChars() +{ + return "\\/:?\"<>|*"; +} + +const char *AGSWin32::GetGraphicsTroubleshootingText() +{ + return "\n\nPossible causes:\n" + "* your graphics card drivers do not support requested resolution. " + "Run the game setup program and try another resolution.\n" + "* the graphics driver you have selected does not work. Try switching to another graphics driver.\n" + "* the graphics filter you have selected does not work. Try another filter.\n" + "* your graphics card drivers are out of date. " + "Try downloading updated graphics card drivers from your manufacturer's website.\n" + "* there is a problem with your graphics card driver configuration. " + "Run DXDiag using the Run command (Start->Run, type \"dxdiag.exe\") and correct any problems reported there."; +} + +void AGSWin32::DisplaySwitchOut() +{ + // If we have explicitly set up fullscreen mode then minimize the window + if (_preFullscreenMode.IsValid()) + ShowWindow(win_get_window(), SW_MINIMIZE); +} + +void AGSWin32::DisplaySwitchIn() { + // If we have explicitly set up fullscreen mode then restore the window + if (_preFullscreenMode.IsValid()) + ShowWindow(win_get_window(), SW_RESTORE); +} + +void AGSWin32::PauseApplication() +{ +#ifndef AGS_NO_VIDEO_PLAYER + dxmedia_pause_video(); +#endif +} + +void AGSWin32::ResumeApplication() +{ +#ifndef AGS_NO_VIDEO_PLAYER + dxmedia_resume_video(); +#endif +} + +void AGSWin32::GetSystemDisplayModes(std::vector &dms) +{ + dms.clear(); + GFX_MODE_LIST *gmlist = get_gfx_mode_list(GFX_DIRECTX); + for (int i = 0; i < gmlist->num_modes; ++i) + { + const GFX_MODE &m = gmlist->mode[i]; + dms.push_back(DisplayMode(GraphicResolution(m.width, m.height, m.bpp))); + } + destroy_gfx_mode_list(gmlist); +} + +bool AGSWin32::SetSystemDisplayMode(const DisplayMode &dm, bool fullscreen) +{ + DEVMODE devmode; + memset(&devmode, 0, sizeof(devmode)); + devmode.dmSize = sizeof(devmode); + devmode.dmPelsWidth = dm.Width; + devmode.dmPelsHeight = dm.Height; + devmode.dmBitsPerPel = dm.ColorDepth; + devmode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + return ChangeDisplaySettings(&devmode, fullscreen ? CDS_FULLSCREEN : 0) == DISP_CHANGE_SUCCESSFUL; +} + +bool AGSWin32::EnterFullscreenMode(const DisplayMode &dm) +{ + // Remember current mode + get_desktop_resolution(&_preFullscreenMode.Width, &_preFullscreenMode.Height); + _preFullscreenMode.ColorDepth = desktop_color_depth(); + + // Set requested desktop mode + return SetSystemDisplayMode(dm, true); +} + +bool AGSWin32::ExitFullscreenMode() +{ + if (!_preFullscreenMode.IsValid()) + return false; + + DisplayMode dm = _preFullscreenMode; + _preFullscreenMode = DisplayMode(); + return SetSystemDisplayMode(dm, false); +} + +void AGSWin32::AdjustWindowStyleForFullscreen() +{ + // Remove the border in full-screen mode + Size sz; + get_desktop_resolution(&sz.Width, &sz.Height); + HWND allegro_wnd = win_get_window(); + LONG winstyle = GetWindowLong(allegro_wnd, GWL_STYLE); + SetWindowLong(allegro_wnd, GWL_STYLE, (winstyle & ~WS_OVERLAPPEDWINDOW) | WS_POPUP); + SetWindowPos(allegro_wnd, HWND_TOP, 0, 0, sz.Width, sz.Height, 0); +} + +void AGSWin32::AdjustWindowStyleForWindowed() +{ + // Make a regular window with a border + HWND allegro_wnd = win_get_window(); + LONG winstyle = GetWindowLong(allegro_wnd, GWL_STYLE); + SetWindowLong(allegro_wnd, GWL_STYLE, (winstyle & ~WS_POPUP) | (WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX)); + // Make window go on top, but at the same time remove WS_EX_TOPMOST style (applied by Direct3D fullscreen mode) + SetWindowPos(allegro_wnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED); +} + +int AGSWin32::CDPlayerCommand(int cmdd, int datt) { +#if defined (AGS_HAS_CD_AUDIO) + return cd_player_control(cmdd, datt); +#else + return -1; +#endif +} + +void AGSWin32::AttachToParentConsole() { + if (_isAttachedToParentConsole) + return; + + _isAttachedToParentConsole = ::AttachConsole(ATTACH_PARENT_PROCESS) != FALSE; + if (_isAttachedToParentConsole) + { + // Require that both STDOUT and STDERR are valid handles from the parent process. + if (::GetStdHandle(STD_OUTPUT_HANDLE) != INVALID_HANDLE_VALUE && + ::GetStdHandle(STD_ERROR_HANDLE) != INVALID_HANDLE_VALUE) + { + // Re-open STDOUT and STDERR to the parent's. + FILE* fp = NULL; + freopen_s(&fp, "CONOUT$", "w", stdout); + setvbuf(stdout, NULL, _IONBF, 0); + + freopen_s(&fp, "CONOUT$", "w", stderr); + setvbuf(stderr, NULL, _IONBF, 0); + } + else + { + ::FreeConsole(); + _isAttachedToParentConsole = false; + } + } +} + +void AGSWin32::DisplayAlert(const char *text, ...) { + char displbuf[2500]; + va_list ap; + va_start(ap, text); + vsprintf(displbuf, text, ap); + va_end(ap); + if (_guiMode) + MessageBox(win_get_window(), displbuf, "Adventure Game Studio", MB_OK | MB_ICONEXCLAMATION); + + // Always write to either stderr or stdout, even if message boxes are enabled. + if (_logToStdErr) + AGSWin32::WriteStdErr("%s", displbuf); + else + AGSWin32::WriteStdOut("%s", displbuf); +} + +int AGSWin32::GetLastSystemError() +{ + return ::GetLastError(); +} + +unsigned long AGSWin32::GetDiskFreeSpaceMB() { + DWORD returnMb = 0; + BOOL fResult; + our_eip = -1891; + + // On Win9x, the last 3 params cannot be null, so need to supply values for all + __int64 i64FreeBytesToCaller, i64Unused1, i64Unused2; + + // Win95 OSR2 or higher - use GetDiskFreeSpaceEx, since the + // normal GetDiskFreeSpace returns erroneous values if the + // free space is > 2 GB + fResult = GetDiskFreeSpaceEx(NULL, + (PULARGE_INTEGER)&i64FreeBytesToCaller, + (PULARGE_INTEGER)&i64Unused1, + (PULARGE_INTEGER)&i64Unused2); + + our_eip = -1893; + + // convert down to MB so we can fit it in a 32-bit long + i64FreeBytesToCaller /= 1000000; + returnMb = i64FreeBytesToCaller; + + return returnMb; +} + +const char* AGSWin32::GetNoMouseErrorString() { + return "No mouse was detected on your system, or your mouse is not configured to work with DirectInput. You must have a mouse to play this game."; +} + +bool AGSWin32::IsMouseControlSupported(bool windowed) +{ + return true; // supported for both fullscreen and windowed modes +} + +const char* AGSWin32::GetAllegroFailUserHint() +{ + return "Make sure you have DirectX 5 or above installed."; +} + +eScriptSystemOSID AGSWin32::GetSystemOSID() { + return eOS_Win; +} + +int AGSWin32::InitializeCDPlayer() { +#if defined (AGS_HAS_CD_AUDIO) + return cd_player_init(); +#else + return -1; +#endif +} + +#ifndef AGS_NO_VIDEO_PLAYER + +void AGSWin32::PlayVideo(const char *name, int skip, int flags) { + + char useloc[250]; + sprintf(useloc, "%s\\%s", ResPaths.DataDir.GetCStr(), name); + + bool useSound = true; + if (flags >= 10) { + flags -= 10; + useSound = false; + } + else { + // for some reason DirectSound can't be shared, so uninstall + // allegro sound before playing the video + shutdown_sound(); + } + + bool isError = false; + if (Common::File::TestReadFile(useloc)) + { + isError = (gfxDriver->PlayVideo(useloc, useSound, (VideoSkipType)skip, (flags > 0)) == 0); + } + else + { + isError = true; + sprintf(lastError, "File not found: %s", useloc); + } + + if (isError) { + // turn "Always display as speech" off, to make sure error + // gets displayed correctly + int oldalways = game.options[OPT_ALWAYSSPCH]; + game.options[OPT_ALWAYSSPCH] = 0; + Display("Video playing error: %s", lastError); + game.options[OPT_ALWAYSSPCH] = oldalways; + } + + if (useSound) + { + // Restore sound system + install_sound(usetup.digicard,usetup.midicard,NULL); + if (usetup.mod_player) + init_mod_player(NUM_MOD_DIGI_VOICES); + } + + set_palette_range(palette, 0, 255, 0); +} + +#endif + +void AGSWin32::AboutToQuitGame() +{ +#ifndef AGS_NO_VIDEO_PLAYER + dxmedia_abort_video(); +#endif +} + +void AGSWin32::PostAllegroExit() { + // Release the timer setting + timeEndPeriod(win32TimerPeriod); +} + +SetupReturnValue AGSWin32::RunSetup(const ConfigTree &cfg_in, ConfigTree &cfg_out) +{ + String version_str = String::FromFormat("Adventure Game Studio v%s setup", get_engine_version()); + return AGS::Engine::WinSetup(cfg_in, cfg_out, usetup.data_files_dir, version_str); +} + +void AGSWin32::SetGameWindowIcon() { + SetWinIcon(); +} + +void AGSWin32::WriteStdOut(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + if (_isDebuggerPresent) + { + // Add "AGS:" prefix when outputting to debugger, to make it clear that this + // is a text from the program log + char buf[STD_BUFFER_SIZE] = "AGS: "; + vsnprintf(buf + 5, STD_BUFFER_SIZE - 5, fmt, ap); + OutputDebugString(buf); + OutputDebugString("\n"); + } + else + { + vprintf(fmt, ap); + printf("\n"); + } + va_end(ap); +} + +void AGSWin32::WriteStdErr(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + if (_isDebuggerPresent) + { + // Add "AGS:" prefix when outputting to debugger, to make it clear that this + // is a text from the program log + char buf[STD_BUFFER_SIZE] = "AGS ERR: "; + vsnprintf(buf + 9, STD_BUFFER_SIZE - 9, fmt, ap); + OutputDebugString(buf); + OutputDebugString("\n"); + } + else + { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } + va_end(ap); +} + +void AGSWin32::ShutdownCDPlayer() { + cd_exit(); +} + +extern "C" const unsigned char hw_to_mycode[256]; + +int AGSWin32::ConvertKeycodeToScanCode(int keycode) +{ + // ** HIDEOUS HACK TO WORK AROUND ALLEGRO BUG + // the key[] array is hardcoded to qwerty keyboards, so we + // have to re-map it to make it work on other keyboard layouts + keycode += ('a' - 'A'); + int vkey = VkKeyScan(keycode); + int scancode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC); + if ((scancode >= 0) && (scancode < 256)) + keycode = hw_to_mycode[scancode]; + return keycode; +} + +void AGSWin32::ValidateWindowSize(int &x, int &y, bool borderless) const +{ + RECT wa_rc, nc_rc; + // This is the size of the available workspace on user's desktop + SystemParametersInfo(SPI_GETWORKAREA, 0, &wa_rc, 0); + // This is the maximal size that OS can reliably resize the window to (including any frame) + const Size max_win(GetSystemMetrics(SM_CXMAXTRACK), GetSystemMetrics(SM_CYMAXTRACK)); + // This is the size of window's non-client area (frame, caption, etc) + HWND allegro_wnd = win_get_window(); + LONG winstyle = borderless ? WS_POPUP : WS_SYSMENU | WS_CAPTION | WS_MINIMIZEBOX; + LONG winstyle_al = GetWindowLong(allegro_wnd, GWL_STYLE); + SetRectEmpty(&nc_rc); + AdjustWindowRect(&nc_rc, winstyle, FALSE); + // Limit the window's full size to the system's window size limit, + // and limit window's client size to the work space (visible area) + x = Math::Min(x, (int)(max_win.Width - (nc_rc.right - nc_rc.left))); + y = Math::Min(y, (int)(max_win.Height - (nc_rc.bottom - nc_rc.top))); + x = Math::Clamp(x, 1, (int)(wa_rc.right - wa_rc.left)); + y = Math::Clamp(y, 1, (int)(wa_rc.bottom - wa_rc.top)); +} + +bool AGSWin32::LockMouseToWindow() +{ + RECT rc; + HWND allegro_wnd = win_get_window(); + GetClientRect(allegro_wnd, &rc); + ClientToScreen(allegro_wnd, (POINT*)&rc); + ClientToScreen(allegro_wnd, (POINT*)&rc.right); + --rc.right; + --rc.bottom; + return ::ClipCursor(&rc) != 0; +} + +void AGSWin32::UnlockMouse() +{ + ::ClipCursor(NULL); +} + +AGSPlatformDriver* AGSPlatformDriver::GetDriver() { + if (instance == NULL) + instance = new AGSWin32(); + return instance; +} + + +// *********** WINDOWS-SPECIFIC PLUGIN API FUNCTIONS ************* + +HWND IAGSEngine::GetWindowHandle () { + return win_get_window(); +} +LPDIRECTDRAW2 IAGSEngine::GetDirectDraw2 () { + if (directdraw == NULL) + quit("!This plugin requires DirectDraw based graphics driver (software driver)."); + + return directdraw; +} +LPDIRECTDRAWSURFACE2 IAGSEngine::GetBitmapSurface (BITMAP *bmp) +{ + if (directdraw == NULL) + quit("!This plugin requires DirectDraw based graphics driver (software driver)."); + + BMP_EXTRA_INFO *bei = (BMP_EXTRA_INFO*)bmp->extra; + + if (bmp == gfxDriver->GetMemoryBackBuffer()->GetAllegroBitmap()) + invalidate_screen(); + + return bei->surf; +} + +LPDIRECTSOUND IAGSEngine::GetDirectSound() { + return directsound; +} + +LPDIRECTINPUTDEVICE IAGSEngine::GetDirectInputKeyboard() { + return key_dinput_device; +} + +LPDIRECTINPUTDEVICE IAGSEngine::GetDirectInputMouse() { + return mouse_dinput_device; +} + +#endif diff --git a/engines/ags/engine/platform/windows/gfx/ali3dd3d.cpp b/engines/ags/engine/platform/windows/gfx/ali3dd3d.cpp new file mode 100644 index 00000000000..b96a10c798b --- /dev/null +++ b/engines/ags/engine/platform/windows/gfx/ali3dd3d.cpp @@ -0,0 +1,2090 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Allegro Interface for 3D; Direct 3D 9 driver +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS + +#include "platform/windows/gfx/ali3dd3d.h" + +#include +#include +#include "ac/timer.h" +#include "debug/assert.h" +#include "debug/out.h" +#include "gfx/ali3dexception.h" +#include "gfx/gfxfilter_d3d.h" +#include "gfx/gfxfilter_aad3d.h" +#include "gfx/gfx_util.h" +#include "main/main_allegro.h" +#include "platform/base/agsplatformdriver.h" +#include "util/library.h" + +#ifndef AGS_NO_VIDEO_PLAYER +extern int dxmedia_play_video_3d(const char*filename, IDirect3DDevice9 *device, bool useAVISound, int canskip, int stretch); +extern void dxmedia_shutdown_3d(); +#endif + +using namespace AGS::Common; + +// Necessary to update textures from 8-bit bitmaps +extern RGB palette[256]; + +// +// Following functions implement various matrix operations. Normally they are found in the auxiliary d3d9x.dll, +// but we do not want AGS to be dependent on it. +// +// Setup identity matrix +void MatrixIdentity(D3DMATRIX &m) +{ + m = { + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + }; +} +// Setup translation matrix +void MatrixTranslate(D3DMATRIX &m, float x, float y, float z) +{ + MatrixIdentity(m); + m.m[3][0] = x; + m.m[3][1] = y; + m.m[3][2] = z; +} +// Setup scaling matrix +void MatrixScale(D3DMATRIX &m, float sx, float sy, float sz) +{ + MatrixIdentity(m); + m.m[0][0] = sx; + m.m[1][1] = sy; + m.m[2][2] = sz; +} +// Setup rotation around Z axis; angle is in radians +void MatrixRotateZ(D3DMATRIX &m, float angle) +{ + MatrixIdentity(m); + m.m[0][0] = cos(angle); + m.m[1][1] = cos(angle); + m.m[0][1] = sin(angle); + m.m[1][0] = -sin(angle); +} +// Matrix multiplication +void MatrixMultiply(D3DMATRIX &mr, const D3DMATRIX &m1, const D3DMATRIX &m2) +{ + for (int i = 0; i < 4; ++i) + for (int j = 0; j < 4; ++j) + mr.m[i][j] = m1.m[i][0] * m2.m[0][j] + m1.m[i][1] * m2.m[1][j] + m1.m[i][2] * m2.m[2][j] + m1.m[i][3] * m2.m[3][j]; +} +// Setup full 2D transformation matrix +void MatrixTransform2D(D3DMATRIX &m, float x, float y, float sx, float sy, float anglez) +{ + D3DMATRIX translate; + D3DMATRIX rotate; + D3DMATRIX scale; + MatrixTranslate(translate, x, y, 0.f); + MatrixRotateZ(rotate, anglez); + MatrixScale(scale, sx, sy, 1.f); + + D3DMATRIX tr1; + MatrixMultiply(tr1, scale, rotate); + MatrixMultiply(m, tr1, translate); +} +// Setup inverse 2D transformation matrix +void MatrixTransformInverse2D(D3DMATRIX &m, float x, float y, float sx, float sy, float anglez) +{ + D3DMATRIX translate; + D3DMATRIX rotate; + D3DMATRIX scale; + MatrixTranslate(translate, x, y, 0.f); + MatrixRotateZ(rotate, anglez); + MatrixScale(scale, sx, sy, 1.f); + + D3DMATRIX tr1; + MatrixMultiply(tr1, translate, rotate); + MatrixMultiply(m, tr1, scale); +} + + +namespace AGS +{ +namespace Engine +{ +namespace D3D +{ + +using namespace Common; + +void D3DBitmap::Dispose() +{ + if (_tiles != NULL) + { + for (int i = 0; i < _numTiles; i++) + _tiles[i].texture->Release(); + + free(_tiles); + _tiles = NULL; + _numTiles = 0; + } + if (_vertex != NULL) + { + _vertex->Release(); + _vertex = NULL; + } +} + +static D3DFORMAT color_depth_to_d3d_format(int color_depth, bool wantAlpha); +static int d3d_format_to_color_depth(D3DFORMAT format, bool secondary); + +bool D3DGfxModeList::GetMode(int index, DisplayMode &mode) const +{ + if (_direct3d && index >= 0 && index < _modeCount) + { + D3DDISPLAYMODE d3d_mode; + if (SUCCEEDED(_direct3d->EnumAdapterModes(D3DADAPTER_DEFAULT, _pixelFormat, index, &d3d_mode))) + { + mode.Width = d3d_mode.Width; + mode.Height = d3d_mode.Height; + mode.ColorDepth = d3d_format_to_color_depth(d3d_mode.Format, false); + mode.RefreshRate = d3d_mode.RefreshRate; + return true; + } + } + return false; +} + + +void dummy_vsync() { } + +#define GFX_DIRECT3D_WIN AL_ID('D','X','3','W') +#define GFX_DIRECT3D_FULL AL_ID('D','X','3','D') + +GFX_DRIVER gfx_direct3d_win = +{ + GFX_DIRECT3D_WIN, + empty_string, + empty_string, + "Direct3D windowed", + NULL, // init + NULL, // exit + NULL, // AL_METHOD(int, scroll, (int x, int y)); + dummy_vsync, // vsync + NULL, // setpalette + NULL, // AL_METHOD(int, request_scroll, (int x, int y)); + NULL, // AL_METHOD(int, poll_scroll, (void)); + NULL, // AL_METHOD(void, enable_triple_buffer, (void)); + NULL, //create_video_bitmap + NULL, //destroy_video_bitmap + NULL, //show_video_bitmap + NULL, + NULL, //gfx_directx_create_system_bitmap, + NULL, //gfx_directx_destroy_system_bitmap, + NULL, //gfx_directx_set_mouse_sprite, + NULL, //gfx_directx_show_mouse, + NULL, //gfx_directx_hide_mouse, + NULL, //gfx_directx_move_mouse, + NULL, // AL_METHOD(void, drawing_mode, (void)); + NULL, // AL_METHOD(void, save_video_state, (void*)); + NULL, // AL_METHOD(void, restore_video_state, (void*)); + NULL, // AL_METHOD(void, set_blender_mode, (int mode, int r, int g, int b, int a)); + NULL, // AL_METHOD(int, fetch_mode_list, (void)); + 0, 0, // int w, h; + FALSE, // int linear; + 0, // long bank_size; + 0, // long bank_gran; + 0, // long vid_mem; + 0, // long vid_phys_base; + TRUE // int windowed; +}; + +GFX_DRIVER gfx_direct3d_full = +{ + GFX_DIRECT3D_FULL, + empty_string, + empty_string, + "Direct3D fullscreen", + NULL, // init + NULL, // exit + NULL, // AL_METHOD(int, scroll, (int x, int y)); + dummy_vsync, // sync + NULL, // setpalette + NULL, // AL_METHOD(int, request_scroll, (int x, int y)); + NULL, // AL_METHOD(int, poll_scroll, (void)); + NULL, // AL_METHOD(void, enable_triple_buffer, (void)); + NULL, //create_video_bitmap + NULL, //destroy_video_bitmap + NULL, //show_video_bitmap + NULL, + NULL, //gfx_directx_create_system_bitmap, + NULL, //gfx_directx_destroy_system_bitmap, + NULL, //gfx_directx_set_mouse_sprite, + NULL, //gfx_directx_show_mouse, + NULL, //gfx_directx_hide_mouse, + NULL, //gfx_directx_move_mouse, + NULL, // AL_METHOD(void, drawing_mode, (void)); + NULL, // AL_METHOD(void, save_video_state, (void*)); + NULL, // AL_METHOD(void, restore_video_state, (void*)); + NULL, // AL_METHOD(void, set_blender_mode, (int mode, int r, int g, int b, int a)); + NULL, // AL_METHOD(int, fetch_mode_list, (void)); + 0, 0, // int w, h; + FALSE, // int linear; + 0, // long bank_size; + 0, // long bank_gran; + 0, // long vid_mem; + 0, // long vid_phys_base; + FALSE // int windowed; +}; + +// The custom FVF, which describes the custom vertex structure. +#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1) + +static int wnd_create_device(); +// TODO: is there better way to not have this mode as a global static variable? +// wnd_create_device() is being called using wnd_call_proc, which does not take +// user parameters. +static DisplayMode d3d_mode_to_init; + +D3DGraphicsDriver::D3DGraphicsDriver(IDirect3D9 *d3d) +{ + direct3d = d3d; + direct3ddevice = NULL; + vertexbuffer = NULL; + pixelShader = NULL; + _legacyPixelShader = false; + set_up_default_vertices(); + pNativeSurface = NULL; + pNativeTexture = NULL; + availableVideoMemory = 0; + _smoothScaling = false; + _pixelRenderXOffset = 0; + _pixelRenderYOffset = 0; + _renderSprAtScreenRes = false; + + // Shifts comply to D3DFMT_A8R8G8B8 + _vmem_a_shift_32 = 24; + _vmem_r_shift_32 = 16; + _vmem_g_shift_32 = 8; + _vmem_b_shift_32 = 0; + + // Initialize default sprite batch, it will be used when no other batch was activated + D3DGraphicsDriver::InitSpriteBatch(0, _spriteBatchDesc[0]); +} + +void D3DGraphicsDriver::set_up_default_vertices() +{ + defaultVertices[0].position.x = 0.0f; + defaultVertices[0].position.y = 0.0f; + defaultVertices[0].position.z = 0.0f; + defaultVertices[0].normal.x = 0.0f; + defaultVertices[0].normal.y = 0.0f; + defaultVertices[0].normal.z = -1.0f; + //defaultVertices[0].color=0xffffffff; + defaultVertices[0].tu=0.0; + defaultVertices[0].tv=0.0; + defaultVertices[1].position.x = 1.0f; + defaultVertices[1].position.y = 0.0f; + defaultVertices[1].position.z = 0.0f; + defaultVertices[1].normal.x = 0.0f; + defaultVertices[1].normal.y = 0.0f; + defaultVertices[1].normal.z = -1.0f; + //defaultVertices[1].color=0xffffffff; + defaultVertices[1].tu=1.0; + defaultVertices[1].tv=0.0; + defaultVertices[2].position.x = 0.0f; + defaultVertices[2].position.y = -1.0f; + defaultVertices[2].position.z = 0.0f; + defaultVertices[2].normal.x = 0.0f; + defaultVertices[2].normal.y = 0.0f; + defaultVertices[2].normal.z = -1.0f; + //defaultVertices[2].color=0xffffffff; + defaultVertices[2].tu=0.0; + defaultVertices[2].tv=1.0; + defaultVertices[3].position.x = 1.0f; + defaultVertices[3].position.y = -1.0f; + defaultVertices[3].position.z = 0.0f; + defaultVertices[3].normal.x = 0.0f; + defaultVertices[3].normal.y = 0.0f; + defaultVertices[3].normal.z = -1.0f; + //defaultVertices[3].color=0xffffffff; + defaultVertices[3].tu=1.0; + defaultVertices[3].tv=1.0; +} + +void D3DGraphicsDriver::Vsync() +{ + // do nothing on D3D +} + +void D3DGraphicsDriver::OnModeSet(const DisplayMode &mode) +{ + GraphicsDriverBase::OnModeSet(mode); + + // The display mode has been set up successfully, save the + // final refresh rate that we are using + D3DDISPLAYMODE final_display_mode; + if (direct3ddevice->GetDisplayMode(0, &final_display_mode) == D3D_OK) { + _mode.RefreshRate = final_display_mode.RefreshRate; + } + else { + _mode.RefreshRate = 0; + } +} + +void D3DGraphicsDriver::ReleaseDisplayMode() +{ + if (!IsModeSet()) + return; + + OnModeReleased(); + ClearDrawLists(); + ClearDrawBackups(); + DestroyFxPool(); + DestroyAllStageScreens(); + + gfx_driver = NULL; +} + +int D3DGraphicsDriver::FirstTimeInit() +{ + HRESULT hr; + + direct3ddevice->GetDeviceCaps(&direct3ddevicecaps); + + // the PixelShader.fx uses ps_1_4 + // the PixelShaderLegacy.fx needs ps_2_0 + int requiredPSMajorVersion = 1; + int requiredPSMinorVersion = 4; + if (_legacyPixelShader) { + requiredPSMajorVersion = 2; + requiredPSMinorVersion = 0; + } + + if (direct3ddevicecaps.PixelShaderVersion < D3DPS_VERSION(requiredPSMajorVersion, requiredPSMinorVersion)) + { + direct3ddevice->Release(); + direct3ddevice = NULL; + previousError = + set_allegro_error("Graphics card does not support Pixel Shader %d.%d", requiredPSMajorVersion, requiredPSMinorVersion); + return -1; + } + + // Load the pixel shader!! + HMODULE exeHandle = GetModuleHandle(NULL); + HRSRC hRes = FindResource(exeHandle, (_legacyPixelShader) ? "PIXEL_SHADER_LEGACY" : "PIXEL_SHADER", "DATA"); + if (hRes) + { + HGLOBAL hGlobal = LoadResource(exeHandle, hRes); + if (hGlobal) + { + DWORD resourceSize = SizeofResource(exeHandle, hRes); + DWORD *dataPtr = (DWORD*)LockResource(hGlobal); + hr = direct3ddevice->CreatePixelShader(dataPtr, &pixelShader); + if (hr != D3D_OK) + { + direct3ddevice->Release(); + direct3ddevice = NULL; + previousError = set_allegro_error("Failed to create pixel shader: 0x%08X", hr); + return -1; + } + UnlockResource(hGlobal); + } + } + + if (pixelShader == NULL) + { + direct3ddevice->Release(); + direct3ddevice = NULL; + previousError = set_allegro_error("Failed to load pixel shader resource"); + return -1; + } + + if (direct3ddevice->CreateVertexBuffer(4*sizeof(CUSTOMVERTEX), D3DUSAGE_WRITEONLY, + D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &vertexbuffer, NULL) != D3D_OK) + { + direct3ddevice->Release(); + direct3ddevice = NULL; + previousError = set_allegro_error("Failed to create vertex buffer"); + return -1; + } + + // This line crashes because my card doesn't support 8-bit textures + //direct3ddevice->SetCurrentTexturePalette(0); + + CUSTOMVERTEX *vertices; + vertexbuffer->Lock(0, 0, (void**)&vertices, D3DLOCK_DISCARD); + + for (int i = 0; i < 4; i++) + { + vertices[i] = defaultVertices[i]; + } + + vertexbuffer->Unlock(); + + direct3ddevice->GetGammaRamp(0, &defaultgammaramp); + + if (defaultgammaramp.red[255] < 256) + { + // correct bug in some gfx drivers that returns gamma ramp + // values from 0-255 instead of 0-65535 + for (int i = 0; i < 256; i++) + { + defaultgammaramp.red[i] *= 256; + defaultgammaramp.green[i] *= 256; + defaultgammaramp.blue[i] *= 256; + } + } + currentgammaramp = defaultgammaramp; + + return 0; +} + +void D3DGraphicsDriver::initD3DDLL(const DisplayMode &mode) +{ + if (!IsModeSupported(mode)) + { + throw Ali3DException(get_allegro_error()); + } + + _enter_critical(); + + d3d_mode_to_init = mode; + // Set the display mode in the window's thread + if (wnd_call_proc(wnd_create_device)) { + _exit_critical(); + throw Ali3DException(get_allegro_error()); + } + + availableVideoMemory = direct3ddevice->GetAvailableTextureMem(); + + _exit_critical(); + + // Set up a fake allegro gfx driver so that things like + // the allegro mouse handler still work + if (mode.Windowed) + gfx_driver = &gfx_direct3d_win; + else + gfx_driver = &gfx_direct3d_full; + + return; +} + +/* color_depth_to_d3d_format: + * Convert a colour depth into the appropriate D3D tag + */ +static D3DFORMAT color_depth_to_d3d_format(int color_depth, bool wantAlpha) +{ + if (wantAlpha) + { + switch (color_depth) + { + case 8: + return D3DFMT_P8; + case 15: + case 16: + return D3DFMT_A1R5G5B5; + case 24: + case 32: + return D3DFMT_A8R8G8B8; + } + } + else + { + switch (color_depth) + { + case 8: + return D3DFMT_P8; + case 15: // don't use X1R5G5B5 because some cards don't support it + return D3DFMT_A1R5G5B5; + case 16: + return D3DFMT_R5G6B5; + case 24: + return D3DFMT_R8G8B8; + case 32: + return D3DFMT_X8R8G8B8; + } + } + return D3DFMT_UNKNOWN; +} + +/* d3d_format_to_color_depth: + * Convert a D3D tag to colour depth + * + * TODO: this is currently an inversion of color_depth_to_d3d_format; + * check later if more formats should be handled + */ +static int d3d_format_to_color_depth(D3DFORMAT format, bool secondary) +{ + switch (format) + { + case D3DFMT_P8: + return 8; + case D3DFMT_A1R5G5B5: + return secondary ? 15 : 16; + case D3DFMT_X1R5G5B5: + return secondary ? 15 : 16; + case D3DFMT_R5G6B5: + return 16; + case D3DFMT_R8G8B8: + return secondary ? 24 : 32; + case D3DFMT_A8R8G8B8: + case D3DFMT_X8R8G8B8: + return 32; + } + return 0; +} + +bool D3DGraphicsDriver::IsModeSupported(const DisplayMode &mode) +{ + if (mode.Width <= 0 || mode.Height <= 0 || mode.ColorDepth <= 0) + { + set_allegro_error("Invalid resolution parameters: %d x %d x %d", mode.Width, mode.Height, mode.ColorDepth); + return false; + } + + if (mode.Windowed) + { + return true; + } + + D3DFORMAT pixelFormat = color_depth_to_d3d_format(mode.ColorDepth, false); + D3DDISPLAYMODE d3d_mode; + + int mode_count = direct3d->GetAdapterModeCount(D3DADAPTER_DEFAULT, pixelFormat); + for (int i = 0; i < mode_count; i++) + { + if (FAILED(direct3d->EnumAdapterModes(D3DADAPTER_DEFAULT, pixelFormat, i, &d3d_mode))) + { + set_allegro_error("IDirect3D9::EnumAdapterModes failed"); + return false; + } + + if ((d3d_mode.Width == mode.Width) && (d3d_mode.Height == mode.Height)) + { + return true; + } + } + + set_allegro_error("The requested adapter mode is not supported"); + return false; +} + +bool D3DGraphicsDriver::SupportsGammaControl() +{ + if ((direct3ddevicecaps.Caps2 & D3DCAPS2_FULLSCREENGAMMA) == 0) + return false; + + if (_mode.Windowed) + return false; + + return true; +} + +void D3DGraphicsDriver::SetGamma(int newGamma) +{ + for (int i = 0; i < 256; i++) + { + int newValue = ((int)defaultgammaramp.red[i] * newGamma) / 100; + if (newValue >= 65535) + newValue = 65535; + currentgammaramp.red[i] = newValue; + currentgammaramp.green[i] = newValue; + currentgammaramp.blue[i] = newValue; + } + + direct3ddevice->SetGammaRamp(0, D3DSGR_NO_CALIBRATION, ¤tgammaramp); +} + +/* wnd_set_video_mode: + * Called by window thread to set a gfx mode; this is needed because DirectDraw can only + * change the mode in the thread that handles the window. + */ +static int wnd_create_device() +{ + return D3DGraphicsFactory::GetD3DDriver()->_initDLLCallback(d3d_mode_to_init); +} + +static int wnd_reset_device() +{ + return D3DGraphicsFactory::GetD3DDriver()->_resetDeviceIfNecessary(); +} + +int D3DGraphicsDriver::_resetDeviceIfNecessary() +{ + HRESULT hr = direct3ddevice->TestCooperativeLevel(); + + if (hr == D3DERR_DEVICELOST) + { + Debug::Printf("D3DGraphicsDriver: D3D Device Lost"); + // user has alt+tabbed away from the game + return 1; + } + + if (hr == D3DERR_DEVICENOTRESET) + { + Debug::Printf("D3DGraphicsDriver: D3D Device Not Reset"); + hr = ResetD3DDevice(); + if (hr != D3D_OK) + { + Debug::Printf("D3DGraphicsDriver: Failed to reset D3D device"); + // can't throw exception because we're in the wrong thread, + // so just return a value instead + return 2; + } + + InitializeD3DState(); + CreateVirtualScreen(); + direct3ddevice->SetGammaRamp(0, D3DSGR_NO_CALIBRATION, ¤tgammaramp); + } + + return 0; +} + +int D3DGraphicsDriver::_initDLLCallback(const DisplayMode &mode) +{ + HWND allegro_wnd = win_get_window(); + + if (mode.Windowed) + platform->AdjustWindowStyleForWindowed(); + else + platform->AdjustWindowStyleForFullscreen(); + + memset( &d3dpp, 0, sizeof(d3dpp) ); + d3dpp.BackBufferWidth = mode.Width; + d3dpp.BackBufferHeight = mode.Height; + d3dpp.BackBufferFormat = color_depth_to_d3d_format(mode.ColorDepth, false); + d3dpp.BackBufferCount = 1; + d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; + // THIS MUST BE SWAPEFFECT_COPY FOR PlayVideo TO WORK + d3dpp.SwapEffect = D3DSWAPEFFECT_COPY; //D3DSWAPEFFECT_DISCARD; + d3dpp.hDeviceWindow = allegro_wnd; + d3dpp.Windowed = mode.Windowed; + d3dpp.EnableAutoDepthStencil = FALSE; + d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; // we need this flag to access the backbuffer with lockrect + d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; + if(mode.Vsync) + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + else + d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; + + /* If full screen, specify the refresh rate */ + // TODO find a way to avoid the wrong refreshrate to be set on mode.RefreshRate + // for now it's best to let it set automatically, so we prevent alt tab delays due to mismatching refreshrates + //if ((d3dpp.Windowed == FALSE) && (mode.RefreshRate > 0)) + // d3dpp.FullScreen_RefreshRateInHz = mode.RefreshRate; + + if (_initGfxCallback != NULL) + _initGfxCallback(&d3dpp); + + bool first_time_init = direct3ddevice == NULL; + HRESULT hr = 0; + if (direct3ddevice) + { + hr = ResetD3DDevice(); + } + else + hr = direct3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, allegro_wnd, + D3DCREATE_MIXED_VERTEXPROCESSING | D3DCREATE_MULTITHREADED, // multithreaded required for AVI player + &d3dpp, &direct3ddevice); + if (hr != D3D_OK) + { + if (!previousError.IsEmpty()) + set_allegro_error(previousError); + else + set_allegro_error("Failed to create Direct3D Device: 0x%08X", hr); + return -1; + } + + if (mode.Windowed) + { + if (adjust_window(mode.Width, mode.Height) != 0) + { + direct3ddevice->Release(); + direct3ddevice = NULL; + set_allegro_error("Window size not supported"); + return -1; + } + } + + win_grab_input(); + + if (first_time_init) + { + int ft_res = FirstTimeInit(); + if (ft_res != 0) + return ft_res; + } + return 0; +} + +void D3DGraphicsDriver::InitializeD3DState() +{ + direct3ddevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0, 0, 0, 255), 0.5f, 0); + + // set the render flags. + direct3ddevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + + direct3ddevice->SetRenderState(D3DRS_LIGHTING, true); + direct3ddevice->SetRenderState(D3DRS_ZENABLE, FALSE); + + direct3ddevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); + direct3ddevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); + direct3ddevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); + + direct3ddevice->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); + direct3ddevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER); + direct3ddevice->SetRenderState(D3DRS_ALPHAREF, (DWORD)0); + + direct3ddevice->SetFVF(D3DFVF_CUSTOMVERTEX); + + D3DMATERIAL9 material; + ZeroMemory(&material, sizeof(material)); //zero memory ( NEW ) + material.Diffuse.r = 1.0f; //diffuse color ( NEW ) + material.Diffuse.g = 1.0f; + material.Diffuse.b = 1.0f; + direct3ddevice->SetMaterial(&material); + + D3DLIGHT9 d3dLight; + ZeroMemory(&d3dLight, sizeof(D3DLIGHT9)); + + // Set up a white point light. + d3dLight.Type = D3DLIGHT_DIRECTIONAL; + d3dLight.Diffuse.r = 1.0f; + d3dLight.Diffuse.g = 1.0f; + d3dLight.Diffuse.b = 1.0f; + d3dLight.Diffuse.a = 1.0f; + d3dLight.Ambient.r = 1.0f; + d3dLight.Ambient.g = 1.0f; + d3dLight.Ambient.b = 1.0f; + d3dLight.Specular.r = 1.0f; + d3dLight.Specular.g = 1.0f; + d3dLight.Specular.b = 1.0f; + + // Position it high in the scene and behind the user. + // Remember, these coordinates are in world space, so + // the user could be anywhere in world space, too. + // For the purposes of this example, assume the user + // is at the origin of world space. + d3dLight.Direction.x = 0.0f; + d3dLight.Direction.y = 0.0f; + d3dLight.Direction.z = 1.0f; + + // Don't attenuate. + d3dLight.Attenuation0 = 1.0f; + d3dLight.Range = 1000.0f; + + // Set the property information for the first light. + direct3ddevice->SetLight(0, &d3dLight); + direct3ddevice->LightEnable(0, TRUE); + + // If we already have a render frame configured, then setup viewport immediately + SetupViewport(); +} + +void D3DGraphicsDriver::SetupViewport() +{ + if (!IsModeSet() || !IsRenderFrameValid() || !IsNativeSizeValid()) + return; + + const float src_width = _srcRect.GetWidth(); + const float src_height = _srcRect.GetHeight(); + const float disp_width = _mode.Width; + const float disp_height = _mode.Height; + + // Setup orthographic projection matrix + D3DMATRIX matOrtho = { + (2.0f / src_width), 0.0, 0.0, 0.0, + 0.0, (2.0f / src_height), 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + }; + + D3DMATRIX matIdentity = { + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + }; + + direct3ddevice->SetTransform(D3DTS_PROJECTION, &matOrtho); + direct3ddevice->SetTransform(D3DTS_WORLD, &matIdentity); + direct3ddevice->SetTransform(D3DTS_VIEW, &matIdentity); + + // See "Directly Mapping Texels to Pixels" MSDN article for why this is necessary + // http://msdn.microsoft.com/en-us/library/windows/desktop/bb219690.aspx + _pixelRenderXOffset = (src_width / disp_width) / 2.0f; + _pixelRenderYOffset = (src_height / disp_height) / 2.0f; + + // Clear the screen before setting a viewport. + ClearScreenRect(RectWH(0, 0, _mode.Width, _mode.Height), nullptr); + + // Set Viewport. + ZeroMemory(&_d3dViewport, sizeof(D3DVIEWPORT9)); + _d3dViewport.X = _dstRect.Left; + _d3dViewport.Y = _dstRect.Top; + _d3dViewport.Width = _dstRect.GetWidth(); + _d3dViewport.Height = _dstRect.GetHeight(); + _d3dViewport.MinZ = 0.0f; + _d3dViewport.MaxZ = 1.0f; + direct3ddevice->SetViewport(&_d3dViewport); + + viewport_rect.left = _dstRect.Left; + viewport_rect.right = _dstRect.Right + 1; + viewport_rect.top = _dstRect.Top; + viewport_rect.bottom = _dstRect.Bottom + 1; +} + +void D3DGraphicsDriver::SetGraphicsFilter(PD3DFilter filter) +{ + _filter = filter; + OnSetFilter(); +} + +void D3DGraphicsDriver::SetTintMethod(TintMethod method) +{ + _legacyPixelShader = (method == TintReColourise); +} + +bool D3DGraphicsDriver::SetDisplayMode(const DisplayMode &mode, volatile int *loopTimer) +{ + ReleaseDisplayMode(); + + if (mode.ColorDepth < 15) + { + set_allegro_error("Direct3D driver does not support 256-color display mode"); + return false; + } + + try + { + initD3DDLL(mode); + } + catch (Ali3DException exception) + { + if (exception._message != get_allegro_error()) + set_allegro_error(exception._message); + return false; + } + OnInit(loopTimer); + OnModeSet(mode); + InitializeD3DState(); + CreateVirtualScreen(); + return true; +} + +void D3DGraphicsDriver::CreateVirtualScreen() +{ + if (!IsModeSet() || !IsNativeSizeValid()) + return; + + // set up native surface + if (pNativeSurface != NULL) + { + pNativeSurface->Release(); + pNativeSurface = NULL; + } + if (pNativeTexture != NULL) + { + pNativeTexture->Release(); + pNativeTexture = NULL; + } + if (direct3ddevice->CreateTexture( + _srcRect.GetWidth(), + _srcRect.GetHeight(), + 1, + D3DUSAGE_RENDERTARGET, + color_depth_to_d3d_format(_mode.ColorDepth, false), + D3DPOOL_DEFAULT, + &pNativeTexture, + NULL) != D3D_OK) + { + throw Ali3DException("CreateTexture failed"); + } + if (pNativeTexture->GetSurfaceLevel(0, &pNativeSurface) != D3D_OK) + { + throw Ali3DException("GetSurfaceLevel failed"); + } + + direct3ddevice->ColorFill(pNativeSurface, NULL, 0); + + // create initial stage screen for plugin raw drawing + _stageVirtualScreen = CreateStageScreen(0, _srcRect.GetSize()); + // we must set Allegro's screen pointer to **something** + screen = (BITMAP*)_stageVirtualScreen->GetAllegroBitmap(); +} + +HRESULT D3DGraphicsDriver::ResetD3DDevice() +{ + // Direct3D documentation: + // Before calling the IDirect3DDevice9::Reset method for a device, + // an application should release any explicit render targets, depth stencil + // surfaces, additional swap chains, state blocks, and D3DPOOL_DEFAULT + // resources associated with the device. + if (pNativeSurface != NULL) + { + pNativeSurface->Release(); + pNativeSurface = NULL; + } + if (pNativeTexture != NULL) + { + pNativeTexture->Release(); + pNativeTexture = NULL; + } + return direct3ddevice->Reset(&d3dpp); +} + +bool D3DGraphicsDriver::SetNativeSize(const Size &src_size) +{ + OnSetNativeSize(src_size); + // Also make sure viewport is updated using new native & destination rectangles + SetupViewport(); + CreateVirtualScreen(); + return !_srcRect.IsEmpty(); +} + +bool D3DGraphicsDriver::SetRenderFrame(const Rect &dst_rect) +{ + OnSetRenderFrame(dst_rect); + // Also make sure viewport is updated using new native & destination rectangles + SetupViewport(); + return !_dstRect.IsEmpty(); +} + +int D3DGraphicsDriver::GetDisplayDepthForNativeDepth(int native_color_depth) const +{ + // TODO: check for device caps to know which depth is supported? + return 32; +} + +IGfxModeList *D3DGraphicsDriver::GetSupportedModeList(int color_depth) +{ + direct3d->AddRef(); + return new D3DGfxModeList(direct3d, color_depth_to_d3d_format(color_depth, false)); +} + +PGfxFilter D3DGraphicsDriver::GetGraphicsFilter() const +{ + return _filter; +} + +void D3DGraphicsDriver::UnInit() +{ +#ifndef AGS_NO_VIDEO_PLAYER + // TODO: this should not be done inside the graphics driver! + dxmedia_shutdown_3d(); +#endif + + OnUnInit(); + ReleaseDisplayMode(); + + if (pNativeSurface) + { + pNativeSurface->Release(); + pNativeSurface = NULL; + } + if (pNativeTexture) + { + pNativeTexture->Release(); + pNativeTexture = NULL; + } + + if (vertexbuffer) + { + vertexbuffer->Release(); + vertexbuffer = NULL; + } + + if (pixelShader) + { + pixelShader->Release(); + pixelShader = NULL; + } + + if (direct3ddevice) + { + direct3ddevice->Release(); + direct3ddevice = NULL; + } +} + +D3DGraphicsDriver::~D3DGraphicsDriver() +{ + D3DGraphicsDriver::UnInit(); + + if (direct3d) + direct3d->Release(); +} + +void D3DGraphicsDriver::ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse) +{ + // NOTE: this function is practically useless at the moment, because D3D redraws whole game frame each time + if (!direct3ddevice) return; + Rect r(x1, y1, x2, y2); + r = _scaling.ScaleRange(r); + ClearScreenRect(r, colorToUse); +} + +void D3DGraphicsDriver::ClearScreenRect(const Rect &r, RGB *colorToUse) +{ + D3DRECT rectToClear; + rectToClear.x1 = r.Left; + rectToClear.y1 = r.Top; + rectToClear.x2 = r.Right + 1; + rectToClear.y2 = r.Bottom + 1; + DWORD colorDword = 0; + if (colorToUse != NULL) + colorDword = D3DCOLOR_XRGB(colorToUse->r, colorToUse->g, colorToUse->b); + direct3ddevice->Clear(1, &rectToClear, D3DCLEAR_TARGET, colorDword, 0.5f, 0); +} + +bool D3DGraphicsDriver::GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt) +{ + // Currently don't support copying in screen resolution when we are rendering in native + if (!_renderSprAtScreenRes) + at_native_res = true; + Size need_size = at_native_res ? _srcRect.GetSize() : _dstRect.GetSize(); + if (destination->GetColorDepth() != _mode.ColorDepth || destination->GetSize() != need_size) + { + if (want_fmt) + *want_fmt = GraphicResolution(need_size.Width, need_size.Height, _mode.ColorDepth); + return false; + } + // If we are rendering sprites at the screen resolution, and requested native res, + // re-render last frame to the native surface + if (at_native_res && _renderSprAtScreenRes) + { + _renderSprAtScreenRes = false; + _reDrawLastFrame(); + _render(true); + _renderSprAtScreenRes = true; + } + + IDirect3DSurface9* surface = NULL; + { + if (_pollingCallback) + _pollingCallback(); + + if (at_native_res) + { + // with render to texture the texture mipmap surface can't be locked directly + // we have to create a surface with D3DPOOL_SYSTEMMEM for GetRenderTargetData + if (direct3ddevice->CreateOffscreenPlainSurface( + _srcRect.GetWidth(), + _srcRect.GetHeight(), + color_depth_to_d3d_format(_mode.ColorDepth, false), + D3DPOOL_SYSTEMMEM, + &surface, + NULL) != D3D_OK) + { + throw Ali3DException("CreateOffscreenPlainSurface failed"); + } + if (direct3ddevice->GetRenderTargetData(pNativeSurface, surface) != D3D_OK) + { + throw Ali3DException("GetRenderTargetData failed"); + } + + } + // Get the back buffer surface + else if (direct3ddevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &surface) != D3D_OK) + { + throw Ali3DException("IDirect3DDevice9::GetBackBuffer failed"); + } + + if (_pollingCallback) + _pollingCallback(); + + D3DLOCKED_RECT lockedRect; + if (surface->LockRect(&lockedRect, (at_native_res ? NULL : &viewport_rect), D3DLOCK_READONLY ) != D3D_OK) + { + throw Ali3DException("IDirect3DSurface9::LockRect failed"); + } + + BitmapHelper::ReadPixelsFromMemory(destination, (uint8_t*)lockedRect.pBits, lockedRect.Pitch); + + surface->UnlockRect(); + surface->Release(); + + if (_pollingCallback) + _pollingCallback(); + } + return true; +} + +void D3DGraphicsDriver::RenderToBackBuffer() +{ + throw Ali3DException("D3D driver does not have a back buffer"); +} + +void D3DGraphicsDriver::Render() +{ + Render(0, 0, kFlip_None); +} + +void D3DGraphicsDriver::Render(int /*xoff*/, int /*yoff*/, GlobalFlipType /*flip*/) +{ + if (wnd_call_proc(wnd_reset_device)) + { + throw Ali3DFullscreenLostException(); + } + + _renderAndPresent(true); +} + +void D3DGraphicsDriver::_reDrawLastFrame() +{ + RestoreDrawLists(); +} + +void D3DGraphicsDriver::_renderSprite(const D3DDrawListEntry *drawListEntry, const D3DMATRIX &matGlobal) +{ + HRESULT hr; + D3DBitmap *bmpToDraw = drawListEntry->bitmap; + D3DMATRIX matSelfTransform; + D3DMATRIX matTransform; + + if (bmpToDraw->_transparency >= 255) + return; + + if (bmpToDraw->_tintSaturation > 0) + { + // Use custom pixel shader + float vector[8]; + if (_legacyPixelShader) + { + rgb_to_hsv(bmpToDraw->_red, bmpToDraw->_green, bmpToDraw->_blue, &vector[0], &vector[1], &vector[2]); + vector[0] /= 360.0; // In HSV, Hue is 0-360 + } + else + { + vector[0] = (float)bmpToDraw->_red / 256.0; + vector[1] = (float)bmpToDraw->_green / 256.0; + vector[2] = (float)bmpToDraw->_blue / 256.0; + } + + vector[3] = (float)bmpToDraw->_tintSaturation / 256.0; + + if (bmpToDraw->_transparency > 0) + vector[4] = (float)bmpToDraw->_transparency / 256.0; + else + vector[4] = 1.0f; + + if (bmpToDraw->_lightLevel > 0) + vector[5] = (float)bmpToDraw->_lightLevel / 256.0; + else + vector[5] = 1.0f; + + direct3ddevice->SetPixelShaderConstantF(0, &vector[0], 2); + direct3ddevice->SetPixelShader(pixelShader); + } + else + { + // Not using custom pixel shader; set up the default one + direct3ddevice->SetPixelShader(NULL); + int useTintRed = 255; + int useTintGreen = 255; + int useTintBlue = 255; + int useTransparency = 0xff; + int textureColorOp = D3DTOP_MODULATE; + + if ((bmpToDraw->_lightLevel > 0) && (bmpToDraw->_lightLevel < 256)) + { + // darkening the sprite... this stupid calculation is for + // consistency with the allegro software-mode code that does + // a trans blend with a (8,8,8) sprite + useTintRed = (bmpToDraw->_lightLevel * 192) / 256 + 64; + useTintGreen = useTintRed; + useTintBlue = useTintRed; + } + else if (bmpToDraw->_lightLevel > 256) + { + // ideally we would use a multi-stage operation here + // because we need to do TEXTURE + (TEXTURE x LIGHT) + // but is it worth having to set the device to 2-stage? + textureColorOp = D3DTOP_ADD; + useTintRed = (bmpToDraw->_lightLevel - 256) / 2; + useTintGreen = useTintRed; + useTintBlue = useTintRed; + } + + if (bmpToDraw->_transparency > 0) + { + useTransparency = bmpToDraw->_transparency; + } + + direct3ddevice->SetRenderState(D3DRS_TEXTUREFACTOR, D3DCOLOR_RGBA(useTintRed, useTintGreen, useTintBlue, useTransparency)); + direct3ddevice->SetTextureStageState(0, D3DTSS_COLOROP, textureColorOp); + direct3ddevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); + direct3ddevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR); + + if (bmpToDraw->_transparency == 0) + { + // No transparency, use texture alpha component + direct3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + direct3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + } + else + { + // Fixed transparency, use (TextureAlpha x FixedTranslucency) + direct3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); + direct3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + direct3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TFACTOR); + } + } + + if (bmpToDraw->_vertex == NULL) + { + hr = direct3ddevice->SetStreamSource(0, vertexbuffer, 0, sizeof(CUSTOMVERTEX)); + } + else + { + hr = direct3ddevice->SetStreamSource(0, bmpToDraw->_vertex, 0, sizeof(CUSTOMVERTEX)); + } + if (hr != D3D_OK) + { + throw Ali3DException("IDirect3DDevice9::SetStreamSource failed"); + } + + float width = bmpToDraw->GetWidthToRender(); + float height = bmpToDraw->GetHeightToRender(); + float xProportion = width / (float)bmpToDraw->_width; + float yProportion = height / (float)bmpToDraw->_height; + float drawAtX = drawListEntry->x; + float drawAtY = drawListEntry->y; + + for (int ti = 0; ti < bmpToDraw->_numTiles; ti++) + { + width = bmpToDraw->_tiles[ti].width * xProportion; + height = bmpToDraw->_tiles[ti].height * yProportion; + float xOffs; + float yOffs = bmpToDraw->_tiles[ti].y * yProportion; + if (bmpToDraw->_flipped) + xOffs = (bmpToDraw->_width - (bmpToDraw->_tiles[ti].x + bmpToDraw->_tiles[ti].width)) * xProportion; + else + xOffs = bmpToDraw->_tiles[ti].x * xProportion; + float thisX = drawAtX + xOffs; + float thisY = drawAtY + yOffs; + thisX = (-(_srcRect.GetWidth() / 2)) + thisX; + thisY = (_srcRect.GetHeight() / 2) - thisY; + + //Setup translation and scaling matrices + float widthToScale = (float)width; + float heightToScale = (float)height; + if (bmpToDraw->_flipped) + { + // The usual transform changes 0..1 into 0..width + // So first negate it (which changes 0..w into -w..0) + widthToScale = -widthToScale; + // and now shift it over to make it 0..w again + thisX += width; + } + + // Multiply object's own and global matrixes + MatrixTransform2D(matSelfTransform, (float)thisX - _pixelRenderXOffset, (float)thisY + _pixelRenderYOffset, widthToScale, heightToScale, 0.f); + MatrixMultiply(matTransform, matSelfTransform, matGlobal); + + if ((_smoothScaling) && bmpToDraw->_useResampler && (bmpToDraw->_stretchToHeight > 0) && + ((bmpToDraw->_stretchToHeight != bmpToDraw->_height) || + (bmpToDraw->_stretchToWidth != bmpToDraw->_width))) + { + direct3ddevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + direct3ddevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + } + else if (!_renderSprAtScreenRes) + { + direct3ddevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT); + direct3ddevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT); + } + else + { + _filter->SetSamplerStateForStandardSprite(direct3ddevice); + } + + direct3ddevice->SetTransform(D3DTS_WORLD, &matTransform); + direct3ddevice->SetTexture(0, bmpToDraw->_tiles[ti].texture); + + hr = direct3ddevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, ti * 4, 2); + if (hr != D3D_OK) + { + throw Ali3DException("IDirect3DDevice9::DrawPrimitive failed"); + } + + } +} + +void D3DGraphicsDriver::_renderFromTexture() +{ + if (direct3ddevice->SetStreamSource(0, vertexbuffer, 0, sizeof(CUSTOMVERTEX)) != D3D_OK) + { + throw Ali3DException("IDirect3DDevice9::SetStreamSource failed"); + } + + float width = _srcRect.GetWidth(); + float height = _srcRect.GetHeight(); + float drawAtX = -(_srcRect.GetWidth() / 2); + float drawAtY = _srcRect.GetHeight() / 2; + + D3DMATRIX matrix; + MatrixTransform2D(matrix, (float)drawAtX - _pixelRenderXOffset, (float)drawAtY + _pixelRenderYOffset, width, height, 0.f); + + direct3ddevice->SetTransform(D3DTS_WORLD, &matrix); + + direct3ddevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1); + direct3ddevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); + + _filter->SetSamplerStateForStandardSprite(direct3ddevice); + + direct3ddevice->SetTexture(0, pNativeTexture); + + if (direct3ddevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2) != D3D_OK) + { + throw Ali3DException("IDirect3DDevice9::DrawPrimitive failed"); + } +} + +void D3DGraphicsDriver::_renderAndPresent(bool clearDrawListAfterwards) +{ + _render(clearDrawListAfterwards); + direct3ddevice->Present(NULL, NULL, NULL, NULL); +} + +void D3DGraphicsDriver::_render(bool clearDrawListAfterwards) +{ + IDirect3DSurface9 *pBackBuffer = NULL; + + if (direct3ddevice->GetRenderTarget(0, &pBackBuffer) != D3D_OK) { + throw Ali3DException("IDirect3DSurface9::GetRenderTarget failed"); + } + direct3ddevice->ColorFill(pBackBuffer, nullptr, D3DCOLOR_RGBA(0, 0, 0, 255)); + + if (!_renderSprAtScreenRes) { + if (direct3ddevice->SetRenderTarget(0, pNativeSurface) != D3D_OK) + { + throw Ali3DException("IDirect3DSurface9::SetRenderTarget failed"); + } + } + + direct3ddevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0, 0, 0, 255), 0.5f, 0); + if (direct3ddevice->BeginScene() != D3D_OK) + throw Ali3DException("IDirect3DDevice9::BeginScene failed"); + + // if showing at 2x size, the sprite can get distorted otherwise + direct3ddevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); + direct3ddevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP); + direct3ddevice->SetSamplerState(0, D3DSAMP_ADDRESSW, D3DTADDRESS_CLAMP); + + RenderSpriteBatches(); + + if (!_renderSprAtScreenRes) { + if (direct3ddevice->SetRenderTarget(0, pBackBuffer)!= D3D_OK) + { + throw Ali3DException("IDirect3DSurface9::SetRenderTarget failed"); + } + direct3ddevice->SetViewport(&_d3dViewport); + _renderFromTexture(); + } + + direct3ddevice->EndScene(); + + pBackBuffer->Release(); + + if (clearDrawListAfterwards) + { + BackupDrawLists(); + ClearDrawLists(); + } + ResetFxPool(); +} + +void D3DGraphicsDriver::RenderSpriteBatches() +{ + // Render all the sprite batches with necessary transformations + for (size_t i = 0; i <= _actSpriteBatch; ++i) + { + const Rect &viewport = _spriteBatches[i].Viewport; + const D3DSpriteBatch &batch = _spriteBatches[i]; + if (!viewport.IsEmpty()) + { + direct3ddevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE); + RECT scissor; + if (_renderSprAtScreenRes) + { + scissor.left = _scaling.X.ScalePt(viewport.Left); + scissor.top = _scaling.Y.ScalePt(viewport.Top); + scissor.right = _scaling.X.ScalePt(viewport.Right + 1); + scissor.bottom = _scaling.Y.ScalePt(viewport.Bottom + 1); + } + else + { + scissor.left = viewport.Left; + scissor.top = viewport.Top; + scissor.right = viewport.Right + 1; + scissor.bottom = viewport.Bottom + 1; + } + direct3ddevice->SetScissorRect(&scissor); + } + else + { + direct3ddevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE); + } + _stageVirtualScreen = GetStageScreen(i); + RenderSpriteBatch(batch); + } + + _stageVirtualScreen = GetStageScreen(0); + direct3ddevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE); +} + +void D3DGraphicsDriver::RenderSpriteBatch(const D3DSpriteBatch &batch) +{ + D3DDrawListEntry stageEntry; // raw-draw plugin support + + const std::vector &listToDraw = batch.List; + for (size_t i = 0; i < listToDraw.size(); ++i) + { + if (listToDraw[i].skip) + continue; + + const D3DDrawListEntry *sprite = &listToDraw[i]; + if (listToDraw[i].bitmap == NULL) + { + if (DoNullSpriteCallback(listToDraw[i].x, (int)direct3ddevice)) + stageEntry = D3DDrawListEntry((D3DBitmap*)_stageVirtualScreenDDB); + else + continue; + sprite = &stageEntry; + } + + this->_renderSprite(sprite, batch.Matrix); + } +} + +void D3DGraphicsDriver::InitSpriteBatch(size_t index, const SpriteBatchDesc &desc) +{ + if (_spriteBatches.size() <= index) + _spriteBatches.resize(index + 1); + _spriteBatches[index].List.clear(); + + Rect viewport = desc.Viewport; + // Combine both world transform and viewport transform into one matrix for faster perfomance + D3DMATRIX matRoomToViewport, matViewport, matViewportFinal; + // IMPORTANT: while the sprites are usually transformed in the order of Scale-Rotate-Translate, + // the camera's transformation is essentially reverse world transformation. And the operations + // are inverse: Translate-Rotate-Scale + MatrixTransformInverse2D(matRoomToViewport, + desc.Transform.X, -(desc.Transform.Y), + desc.Transform.ScaleX, desc.Transform.ScaleY, desc.Transform.Rotate); + // Next step is translate to viewport position; remove this if this is + // changed to a separate operation at some point + // TODO: find out if this is an optimal way to translate scaled room into Top-Left screen coordinates + float scaled_offx = (_srcRect.GetWidth() - desc.Transform.ScaleX * (float)_srcRect.GetWidth()) / 2.f; + float scaled_offy = (_srcRect.GetHeight() - desc.Transform.ScaleY * (float)_srcRect.GetHeight()) / 2.f; + MatrixTranslate(matViewport, viewport.Left - scaled_offx, -(viewport.Top - scaled_offy), 0.f); + MatrixMultiply(matViewportFinal, matRoomToViewport, matViewport); + + // Then apply global node transformation (flip and offset) + int node_tx = desc.Offset.X, node_ty = desc.Offset.Y; + float node_sx = 1.f, node_sy = 1.f; + if ((desc.Flip == kFlip_Vertical) || (desc.Flip == kFlip_Both)) + { + int left = _srcRect.GetWidth() - (viewport.Right + 1); + viewport.MoveToX(left); + node_sx = -1.f; + } + if ((desc.Flip == kFlip_Horizontal) || (desc.Flip == kFlip_Both)) + { + int top = _srcRect.GetHeight() - (viewport.Bottom + 1); + viewport.MoveToY(top); + node_sy = -1.f; + } + viewport = Rect::MoveBy(viewport, node_tx, node_ty); + D3DMATRIX matFlip; + MatrixTransform2D(matFlip, node_tx, -(node_ty), node_sx, node_sy, 0.f); + MatrixMultiply(_spriteBatches[index].Matrix, matViewportFinal, matFlip); + _spriteBatches[index].Viewport = viewport; + + // create stage screen for plugin raw drawing + int src_w = viewport.GetWidth() / desc.Transform.ScaleX; + int src_h = viewport.GetHeight() / desc.Transform.ScaleY; + CreateStageScreen(index, Size(src_w, src_h)); +} + +void D3DGraphicsDriver::ResetAllBatches() +{ + for (size_t i = 0; i < _spriteBatches.size(); ++i) + _spriteBatches[i].List.clear(); +} + +void D3DGraphicsDriver::ClearDrawBackups() +{ + _backupBatchDescs.clear(); + _backupBatches.clear(); +} + +void D3DGraphicsDriver::BackupDrawLists() +{ + ClearDrawBackups(); + for (size_t i = 0; i <= _actSpriteBatch; ++i) + { + _backupBatchDescs.push_back(_spriteBatchDesc[i]); + _backupBatches.push_back(_spriteBatches[i]); + } +} + +void D3DGraphicsDriver::RestoreDrawLists() +{ + if (_backupBatchDescs.size() == 0) + { + ClearDrawLists(); + return; + } + _spriteBatchDesc = _backupBatchDescs; + _spriteBatches = _backupBatches; + _actSpriteBatch = _backupBatchDescs.size() - 1; +} + +void D3DGraphicsDriver::DrawSprite(int x, int y, IDriverDependantBitmap* bitmap) +{ + _spriteBatches[_actSpriteBatch].List.push_back(D3DDrawListEntry((D3DBitmap*)bitmap, x, y)); +} + +void D3DGraphicsDriver::DestroyDDB(IDriverDependantBitmap* bitmap) +{ + // Remove deleted DDB from backups + for (D3DSpriteBatches::iterator it = _backupBatches.begin(); it != _backupBatches.end(); ++it) + { + std::vector &drawlist = it->List; + for (size_t i = 0; i < drawlist.size(); i++) + { + if (drawlist[i].bitmap == bitmap) + drawlist[i].skip = true; + } + } + delete bitmap; +} + +void D3DGraphicsDriver::UpdateTextureRegion(D3DTextureTile *tile, Bitmap *bitmap, D3DBitmap *target, bool hasAlpha) +{ + IDirect3DTexture9* newTexture = tile->texture; + + D3DLOCKED_RECT lockedRegion; + HRESULT hr = newTexture->LockRect(0, &lockedRegion, NULL, D3DLOCK_NOSYSLOCK | D3DLOCK_DISCARD); + if (hr != D3D_OK) + { + throw Ali3DException("Unable to lock texture"); + } + + bool usingLinearFiltering = _filter->NeedToColourEdgeLines(); + char *memPtr = (char*)lockedRegion.pBits; + + if (target->_opaque) + BitmapToVideoMemOpaque(bitmap, hasAlpha, tile, target, memPtr, lockedRegion.Pitch); + else + BitmapToVideoMem(bitmap, hasAlpha, tile, target, memPtr, lockedRegion.Pitch, usingLinearFiltering); + + newTexture->UnlockRect(0); +} + +void D3DGraphicsDriver::UpdateDDBFromBitmap(IDriverDependantBitmap* bitmapToUpdate, Bitmap *bitmap, bool hasAlpha) +{ + D3DBitmap *target = (D3DBitmap*)bitmapToUpdate; + if (target->_width != bitmap->GetWidth() || target->_height != bitmap->GetHeight()) + throw Ali3DException("UpdateDDBFromBitmap: mismatched bitmap size"); + const int color_depth = bitmap->GetColorDepth(); + if (color_depth != target->_colDepth) + throw Ali3DException("UpdateDDBFromBitmap: mismatched colour depths"); + + target->_hasAlpha = hasAlpha; + if (color_depth == 8) + select_palette(palette); + + for (int i = 0; i < target->_numTiles; i++) + { + UpdateTextureRegion(&target->_tiles[i], bitmap, target, hasAlpha); + } + + if (color_depth == 8) + unselect_palette(); +} + +int D3DGraphicsDriver::GetCompatibleBitmapFormat(int color_depth) +{ + if (color_depth == 8) + return 8; + if (color_depth > 8 && color_depth <= 16) + return 16; + return 32; +} + +void D3DGraphicsDriver::AdjustSizeToNearestSupportedByCard(int *width, int *height) +{ + int allocatedWidth = *width, allocatedHeight = *height; + + if (direct3ddevicecaps.TextureCaps & D3DPTEXTURECAPS_POW2) + { + bool foundWidth = false, foundHeight = false; + int tryThis = 2; + while ((!foundWidth) || (!foundHeight)) + { + if ((tryThis >= allocatedWidth) && (!foundWidth)) + { + allocatedWidth = tryThis; + foundWidth = true; + } + + if ((tryThis >= allocatedHeight) && (!foundHeight)) + { + allocatedHeight = tryThis; + foundHeight = true; + } + + tryThis = tryThis << 1; + } + } + + if (direct3ddevicecaps.TextureCaps & D3DPTEXTURECAPS_SQUAREONLY) + { + if (allocatedWidth > allocatedHeight) + { + allocatedHeight = allocatedWidth; + } + else + { + allocatedWidth = allocatedHeight; + } + } + + *width = allocatedWidth; + *height = allocatedHeight; +} + +bool D3DGraphicsDriver::IsTextureFormatOk( D3DFORMAT TextureFormat, D3DFORMAT AdapterFormat ) +{ + HRESULT hr = direct3d->CheckDeviceFormat( D3DADAPTER_DEFAULT, + D3DDEVTYPE_HAL, + AdapterFormat, + 0, + D3DRTYPE_TEXTURE, + TextureFormat); + + return SUCCEEDED( hr ); +} + +IDriverDependantBitmap* D3DGraphicsDriver::CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) +{ + int allocatedWidth = bitmap->GetWidth(); + int allocatedHeight = bitmap->GetHeight(); + if (bitmap->GetColorDepth() != GetCompatibleBitmapFormat(bitmap->GetColorDepth())) + throw Ali3DException("CreateDDBFromBitmap: bitmap colour depth not supported"); + int colourDepth = bitmap->GetColorDepth(); + + D3DBitmap *ddb = new D3DBitmap(bitmap->GetWidth(), bitmap->GetHeight(), colourDepth, opaque); + + AdjustSizeToNearestSupportedByCard(&allocatedWidth, &allocatedHeight); + int tilesAcross = 1, tilesDown = 1; + + // *************** REMOVE THESE LINES ************* + //direct3ddevicecaps.MaxTextureWidth = 64; + //direct3ddevicecaps.MaxTextureHeight = 256; + // *************** END REMOVE THESE LINES ************* + + // Calculate how many textures will be necessary to + // store this image + tilesAcross = (allocatedWidth + direct3ddevicecaps.MaxTextureWidth - 1) / direct3ddevicecaps.MaxTextureWidth; + tilesDown = (allocatedHeight + direct3ddevicecaps.MaxTextureHeight - 1) / direct3ddevicecaps.MaxTextureHeight; + int tileWidth = bitmap->GetWidth() / tilesAcross; + int lastTileExtraWidth = bitmap->GetWidth() % tilesAcross; + int tileHeight = bitmap->GetHeight() / tilesDown; + int lastTileExtraHeight = bitmap->GetHeight() % tilesDown; + int tileAllocatedWidth = tileWidth; + int tileAllocatedHeight = tileHeight; + + AdjustSizeToNearestSupportedByCard(&tileAllocatedWidth, &tileAllocatedHeight); + + int numTiles = tilesAcross * tilesDown; + D3DTextureTile *tiles = (D3DTextureTile*)malloc(sizeof(D3DTextureTile) * numTiles); + memset(tiles, 0, sizeof(D3DTextureTile) * numTiles); + + CUSTOMVERTEX *vertices = NULL; + + if ((numTiles == 1) && + (allocatedWidth == bitmap->GetWidth()) && + (allocatedHeight == bitmap->GetHeight())) + { + // use default whole-image vertices + } + else + { + // The texture is not the same as the bitmap, so create some custom vertices + // so that only the relevant portion of the texture is rendered + int vertexBufferSize = numTiles * 4 * sizeof(CUSTOMVERTEX); + HRESULT hr = direct3ddevice->CreateVertexBuffer(vertexBufferSize, 0, + D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &ddb->_vertex, NULL); + + if (hr != D3D_OK) + { + free(tiles); + char errorMessage[200]; + sprintf(errorMessage, "Direct3DDevice9::CreateVertexBuffer(Length=%d) for texture failed: error code %08X", vertexBufferSize, hr); + throw Ali3DException(errorMessage); + } + + if (ddb->_vertex->Lock(0, 0, (void**)&vertices, D3DLOCK_DISCARD) != D3D_OK) + { + free(tiles); + throw Ali3DException("Failed to lock vertex buffer"); + } + } + + for (int x = 0; x < tilesAcross; x++) + { + for (int y = 0; y < tilesDown; y++) + { + D3DTextureTile *thisTile = &tiles[y * tilesAcross + x]; + int thisAllocatedWidth = tileAllocatedWidth; + int thisAllocatedHeight = tileAllocatedHeight; + thisTile->x = x * tileWidth; + thisTile->y = y * tileHeight; + thisTile->width = tileWidth; + thisTile->height = tileHeight; + if (x == tilesAcross - 1) + { + thisTile->width += lastTileExtraWidth; + thisAllocatedWidth = thisTile->width; + AdjustSizeToNearestSupportedByCard(&thisAllocatedWidth, &thisAllocatedHeight); + } + if (y == tilesDown - 1) + { + thisTile->height += lastTileExtraHeight; + thisAllocatedHeight = thisTile->height; + AdjustSizeToNearestSupportedByCard(&thisAllocatedWidth, &thisAllocatedHeight); + } + + if (vertices != NULL) + { + for (int vidx = 0; vidx < 4; vidx++) + { + int i = (y * tilesAcross + x) * 4 + vidx; + vertices[i] = defaultVertices[vidx]; + if (vertices[i].tu > 0.0) + { + vertices[i].tu = (float)thisTile->width / (float)thisAllocatedWidth; + } + if (vertices[i].tv > 0.0) + { + vertices[i].tv = (float)thisTile->height / (float)thisAllocatedHeight; + } + } + } + + // NOTE: pay attention that the texture format depends on the **display mode**'s color format, + // rather than source bitmap's color depth! + D3DFORMAT textureFormat = color_depth_to_d3d_format(_mode.ColorDepth, !opaque); + HRESULT hr = direct3ddevice->CreateTexture(thisAllocatedWidth, thisAllocatedHeight, 1, 0, + textureFormat, + D3DPOOL_MANAGED, &thisTile->texture, NULL); + if (hr != D3D_OK) + { + char errorMessage[200]; + sprintf(errorMessage, "Direct3DDevice9::CreateTexture(X=%d, Y=%d, FMT=%d) failed: error code %08X", thisAllocatedWidth, thisAllocatedHeight, textureFormat, hr); + throw Ali3DException(errorMessage); + } + + } + } + + if (vertices != NULL) + { + ddb->_vertex->Unlock(); + } + + ddb->_numTiles = numTiles; + ddb->_tiles = tiles; + + UpdateDDBFromBitmap(ddb, bitmap, hasAlpha); + + return ddb; +} + +void D3DGraphicsDriver::do_fade(bool fadingOut, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) +{ + // Construct scene in order: game screen, fade fx, post game overlay + // NOTE: please keep in mind: redrawing last saved frame here instead of constructing new one + // is done because of backwards-compatibility issue: originally AGS faded out using frame + // drawn before the script that triggers blocking fade (e.g. instigated by ChangeRoom). + // Unfortunately some existing games were changing looks of the screen during same function, + // but these were not supposed to get on screen until before fade-in. + if (fadingOut) + this->_reDrawLastFrame(); + else if (_drawScreenCallback != NULL) + _drawScreenCallback(); + Bitmap *blackSquare = BitmapHelper::CreateBitmap(16, 16, 32); + blackSquare->Clear(makecol32(targetColourRed, targetColourGreen, targetColourBlue)); + IDriverDependantBitmap *d3db = this->CreateDDBFromBitmap(blackSquare, false, true); + delete blackSquare; + BeginSpriteBatch(_srcRect, SpriteTransform()); + d3db->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false); + this->DrawSprite(0, 0, d3db); + if (_drawPostScreenCallback != NULL) + _drawPostScreenCallback(); + + if (speed <= 0) speed = 16; + speed *= 2; // harmonise speeds with software driver which is faster + for (int a = 1; a < 255; a += speed) + { + d3db->SetTransparency(fadingOut ? a : (255 - a)); + this->_renderAndPresent(false); + + if (_pollingCallback) + _pollingCallback(); + WaitForNextFrame(); + } + + if (fadingOut) + { + d3db->SetTransparency(0); + this->_renderAndPresent(false); + } + + this->DestroyDDB(d3db); + this->ClearDrawLists(); + ResetFxPool(); +} + +void D3DGraphicsDriver::FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) +{ + do_fade(true, speed, targetColourRed, targetColourGreen, targetColourBlue); +} + +void D3DGraphicsDriver::FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue) +{ + do_fade(false, speed, targetColourRed, targetColourGreen, targetColourBlue); +} + +void D3DGraphicsDriver::BoxOutEffect(bool blackingOut, int speed, int delay) +{ + // Construct scene in order: game screen, fade fx, post game overlay + if (blackingOut) + this->_reDrawLastFrame(); + else if (_drawScreenCallback != NULL) + _drawScreenCallback(); + Bitmap *blackSquare = BitmapHelper::CreateBitmap(16, 16, 32); + blackSquare->Clear(); + IDriverDependantBitmap *d3db = this->CreateDDBFromBitmap(blackSquare, false, true); + delete blackSquare; + BeginSpriteBatch(_srcRect, SpriteTransform()); + size_t fx_batch = _actSpriteBatch; + d3db->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false); + this->DrawSprite(0, 0, d3db); + if (!blackingOut) + { + // when fading in, draw four black boxes, one + // across each side of the screen + this->DrawSprite(0, 0, d3db); + this->DrawSprite(0, 0, d3db); + this->DrawSprite(0, 0, d3db); + } + if (_drawPostScreenCallback != NULL) + _drawPostScreenCallback(); + + int yspeed = _srcRect.GetHeight() / (_srcRect.GetWidth() / speed); + int boxWidth = speed; + int boxHeight = yspeed; + + while (boxWidth < _srcRect.GetWidth()) + { + boxWidth += speed; + boxHeight += yspeed; + D3DSpriteBatch &batch = _spriteBatches[fx_batch]; + std::vector &drawList = batch.List; + const size_t last = drawList.size() - 1; + if (blackingOut) + { + drawList[last].x = _srcRect.GetWidth() / 2- boxWidth / 2; + drawList[last].y = _srcRect.GetHeight() / 2 - boxHeight / 2; + d3db->SetStretch(boxWidth, boxHeight, false); + } + else + { + drawList[last - 3].x = _srcRect.GetWidth() / 2 - boxWidth / 2 - _srcRect.GetWidth(); + drawList[last - 2].y = _srcRect.GetHeight() / 2 - boxHeight / 2 - _srcRect.GetHeight(); + drawList[last - 1].x = _srcRect.GetWidth() / 2 + boxWidth / 2; + drawList[last ].y = _srcRect.GetHeight() / 2 + boxHeight / 2; + d3db->SetStretch(_srcRect.GetWidth(), _srcRect.GetHeight(), false); + } + + this->_renderAndPresent(false); + + if (_pollingCallback) + _pollingCallback(); + platform->Delay(delay); + } + + this->DestroyDDB(d3db); + this->ClearDrawLists(); + ResetFxPool(); +} + +#ifndef AGS_NO_VIDEO_PLAYER + +bool D3DGraphicsDriver::PlayVideo(const char *filename, bool useAVISound, VideoSkipType skipType, bool stretchToFullScreen) +{ + direct3ddevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_RGBA(0, 0, 0, 255), 0.5f, 0); + + int result = dxmedia_play_video_3d(filename, direct3ddevice, useAVISound, skipType, stretchToFullScreen ? 1 : 0); + return (result == 0); +} + +#endif + +void D3DGraphicsDriver::SetScreenFade(int red, int green, int blue) +{ + D3DBitmap *ddb = static_cast(MakeFx(red, green, blue)); + ddb->SetStretch(_spriteBatches[_actSpriteBatch].Viewport.GetWidth(), + _spriteBatches[_actSpriteBatch].Viewport.GetHeight(), false); + ddb->SetTransparency(0); + _spriteBatches[_actSpriteBatch].List.push_back(D3DDrawListEntry(ddb)); +} + +void D3DGraphicsDriver::SetScreenTint(int red, int green, int blue) +{ + if (red == 0 && green == 0 && blue == 0) return; + D3DBitmap *ddb = static_cast(MakeFx(red, green, blue)); + ddb->SetStretch(_spriteBatches[_actSpriteBatch].Viewport.GetWidth(), + _spriteBatches[_actSpriteBatch].Viewport.GetHeight(), false); + ddb->SetTransparency(128); + _spriteBatches[_actSpriteBatch].List.push_back(D3DDrawListEntry(ddb)); +} + + +D3DGraphicsFactory *D3DGraphicsFactory::_factory = NULL; +Library D3DGraphicsFactory::_library; + +D3DGraphicsFactory::~D3DGraphicsFactory() +{ + DestroyDriver(); // driver must be destroyed before d3d library is disposed + ULONG ref_cnt = _direct3d->Release(); + if (ref_cnt > 0) + Debug::Printf(kDbgMsg_Warn, "WARNING: Not all of the Direct3D resources have been disposed; ID3D ref count: %d", ref_cnt); + _factory = NULL; +} + +size_t D3DGraphicsFactory::GetFilterCount() const +{ + return 2; +} + +const GfxFilterInfo *D3DGraphicsFactory::GetFilterInfo(size_t index) const +{ + switch (index) + { + case 0: + return &D3DGfxFilter::FilterInfo; + case 1: + return &AAD3DGfxFilter::FilterInfo; + default: + return NULL; + } +} + +String D3DGraphicsFactory::GetDefaultFilterID() const +{ + return D3DGfxFilter::FilterInfo.Id; +} + +/* static */ D3DGraphicsFactory *D3DGraphicsFactory::GetFactory() +{ + if (!_factory) + { + _factory = new D3DGraphicsFactory(); + if (!_factory->Init()) + { + delete _factory; + _factory = NULL; + } + } + return _factory; +} + +/* static */ D3DGraphicsDriver *D3DGraphicsFactory::GetD3DDriver() +{ + if (!_factory) + _factory = GetFactory(); + if (_factory) + return _factory->EnsureDriverCreated(); + return NULL; +} + + +D3DGraphicsFactory::D3DGraphicsFactory() + : _direct3d(NULL) +{ +} + +D3DGraphicsDriver *D3DGraphicsFactory::EnsureDriverCreated() +{ + if (!_driver) + { + _factory->_direct3d->AddRef(); + _driver = new D3DGraphicsDriver(_factory->_direct3d); + } + return _driver; +} + +D3DGfxFilter *D3DGraphicsFactory::CreateFilter(const String &id) +{ + if (D3DGfxFilter::FilterInfo.Id.CompareNoCase(id) == 0) + return new D3DGfxFilter(); + else if (AAD3DGfxFilter::FilterInfo.Id.CompareNoCase(id) == 0) + return new AAD3DGfxFilter(); + return NULL; +} + +bool D3DGraphicsFactory::Init() +{ + assert(_direct3d == NULL); + if (_direct3d) + return true; + + if (!_library.Load("d3d9")) + { + set_allegro_error("Direct3D 9 is not installed"); + return false; + } + + typedef IDirect3D9 * WINAPI D3D9CreateFn(UINT); + D3D9CreateFn *lpDirect3DCreate9 = (D3D9CreateFn*)_library.GetFunctionAddress("Direct3DCreate9"); + if (!lpDirect3DCreate9) + { + _library.Unload(); + set_allegro_error("Entry point not found in d3d9.dll"); + return false; + } + _direct3d = lpDirect3DCreate9(D3D_SDK_VERSION); + if (!_direct3d) + { + _library.Unload(); + set_allegro_error("Direct3DCreate failed!"); + return false; + } + return true; +} + +} // namespace D3D +} // namespace Engine +} // namespace AGS + +#endif // AGS_PLATFORM_OS_WINDOWS \ No newline at end of file diff --git a/engines/ags/engine/platform/windows/gfx/ali3dd3d.h b/engines/ags/engine/platform/windows/gfx/ali3dd3d.h new file mode 100644 index 00000000000..a8cc442f0a6 --- /dev/null +++ b/engines/ags/engine/platform/windows/gfx/ali3dd3d.h @@ -0,0 +1,336 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Direct3D graphics factory +// +//============================================================================= + +#ifndef __AGS_EE_GFX__ALI3DD3D_H +#define __AGS_EE_GFX__ALI3DD3D_H + +#include "core/platform.h" + +#if ! AGS_PLATFORM_OS_WINDOWS +#error This file should only be included on the Windows build +#endif + +#include +#include +#include +#include +#include "gfx/bitmap.h" +#include "gfx/ddb.h" +#include "gfx/gfxdriverfactorybase.h" +#include "gfx/gfxdriverbase.h" +#include "util/library.h" +#include "util/string.h" + +namespace AGS +{ +namespace Engine +{ +namespace D3D +{ + +using AGS::Common::Bitmap; +using AGS::Common::String; +class D3DGfxFilter; + +struct D3DTextureTile : public TextureTile +{ + IDirect3DTexture9* texture; +}; + +class D3DBitmap : public VideoMemDDB +{ +public: + // Transparency is a bit counter-intuitive + // 0=not transparent, 255=invisible, 1..254 barely visible .. mostly visible + void SetTransparency(int transparency) override { _transparency = transparency; } + void SetFlippedLeftRight(bool isFlipped) override { _flipped = isFlipped; } + void SetStretch(int width, int height, bool useResampler = true) override + { + _stretchToWidth = width; + _stretchToHeight = height; + _useResampler = useResampler; + } + void SetLightLevel(int lightLevel) override { _lightLevel = lightLevel; } + void SetTint(int red, int green, int blue, int tintSaturation) override + { + _red = red; + _green = green; + _blue = blue; + _tintSaturation = tintSaturation; + } + + bool _flipped; + int _stretchToWidth, _stretchToHeight; + bool _useResampler; + int _red, _green, _blue; + int _tintSaturation; + int _lightLevel; + bool _hasAlpha; + int _transparency; + IDirect3DVertexBuffer9* _vertex; + D3DTextureTile *_tiles; + int _numTiles; + + D3DBitmap(int width, int height, int colDepth, bool opaque) + { + _width = width; + _height = height; + _colDepth = colDepth; + _flipped = false; + _hasAlpha = false; + _stretchToWidth = 0; + _stretchToHeight = 0; + _useResampler = false; + _red = _green = _blue = 0; + _tintSaturation = 0; + _lightLevel = 0; + _transparency = 0; + _opaque = opaque; + _vertex = NULL; + _tiles = NULL; + _numTiles = 0; + } + + int GetWidthToRender() { return (_stretchToWidth > 0) ? _stretchToWidth : _width; } + int GetHeightToRender() { return (_stretchToHeight > 0) ? _stretchToHeight : _height; } + + void Dispose(); + + ~D3DBitmap() override + { + Dispose(); + } +}; + +class D3DGfxModeList : public IGfxModeList +{ +public: + D3DGfxModeList(IDirect3D9 *direct3d, D3DFORMAT d3dformat) + : _direct3d(direct3d) + , _pixelFormat(d3dformat) + { + _modeCount = _direct3d ? _direct3d->GetAdapterModeCount(D3DADAPTER_DEFAULT, _pixelFormat) : 0; + } + + ~D3DGfxModeList() override + { + if (_direct3d) + _direct3d->Release(); + } + + int GetModeCount() const override + { + return _modeCount; + } + + bool GetMode(int index, DisplayMode &mode) const override; + +private: + IDirect3D9 *_direct3d; + D3DFORMAT _pixelFormat; + int _modeCount; +}; + +struct CUSTOMVERTEX +{ + D3DVECTOR position; // The position. + D3DVECTOR normal; + FLOAT tu, tv; // The texture coordinates. +}; + +typedef SpriteDrawListEntry D3DDrawListEntry; +// D3D renderer's sprite batch +struct D3DSpriteBatch +{ + // List of sprites to render + std::vector List; + // Clipping viewport + Rect Viewport; + // Transformation matrix, built from the batch description + D3DMATRIX Matrix; +}; +typedef std::vector D3DSpriteBatches; + + +class D3DGraphicsDriver : public VideoMemoryGraphicsDriver +{ +public: + const char*GetDriverName() override { return "Direct3D 9"; } + const char*GetDriverID() override { return "D3D9"; } + void SetTintMethod(TintMethod method) override; + bool SetDisplayMode(const DisplayMode &mode, volatile int *loopTimer) override; + bool SetNativeSize(const Size &src_size) override; + bool SetRenderFrame(const Rect &dst_rect) override; + int GetDisplayDepthForNativeDepth(int native_color_depth) const override; + IGfxModeList *GetSupportedModeList(int color_depth) override; + bool IsModeSupported(const DisplayMode &mode) override; + PGfxFilter GetGraphicsFilter() const override; + // Clears the screen rectangle. The coordinates are expected in the **native game resolution**. + void ClearRectangle(int x1, int y1, int x2, int y2, RGB *colorToUse) override; + int GetCompatibleBitmapFormat(int color_depth) override; + IDriverDependantBitmap* CreateDDBFromBitmap(Bitmap *bitmap, bool hasAlpha, bool opaque) override; + void UpdateDDBFromBitmap(IDriverDependantBitmap* bitmapToUpdate, Bitmap *bitmap, bool hasAlpha) override; + void DestroyDDB(IDriverDependantBitmap* bitmap) override; + void DrawSprite(int x, int y, IDriverDependantBitmap* bitmap) override; + void SetScreenFade(int red, int green, int blue) override; + void SetScreenTint(int red, int green, int blue) override; + void RenderToBackBuffer() override; + void Render() override; + void Render(int xoff, int yoff, GlobalFlipType flip) override; + bool GetCopyOfScreenIntoBitmap(Bitmap *destination, bool at_native_res, GraphicResolution *want_fmt) override; + void EnableVsyncBeforeRender(bool enabled) override { } + void Vsync() override; + void RenderSpritesAtScreenResolution(bool enabled, int supersampling) override { _renderSprAtScreenRes = enabled; }; + void FadeOut(int speed, int targetColourRed, int targetColourGreen, int targetColourBlue) override; + void FadeIn(int speed, PALETTE p, int targetColourRed, int targetColourGreen, int targetColourBlue) override; + void BoxOutEffect(bool blackingOut, int speed, int delay) override; +#ifndef AGS_NO_VIDEO_PLAYER + bool PlayVideo(const char *filename, bool useSound, VideoSkipType skipType, bool stretchToFullScreen) override; +#endif + bool SupportsGammaControl() override; + void SetGamma(int newGamma) override; + void UseSmoothScaling(bool enabled) override { _smoothScaling = enabled; } + bool RequiresFullRedrawEachFrame() override { return true; } + bool HasAcceleratedTransform() override { return true; } + + typedef std::shared_ptr PD3DFilter; + + // Clears screen rect, coordinates are expected in display resolution + void ClearScreenRect(const Rect &r, RGB *colorToUse); + void UnInit(); + void SetGraphicsFilter(PD3DFilter filter); + + // Internal; TODO: find a way to hide these + int _initDLLCallback(const DisplayMode &mode); + int _resetDeviceIfNecessary(); + + D3DGraphicsDriver(IDirect3D9 *d3d); + ~D3DGraphicsDriver() override; + +private: + PD3DFilter _filter; + + IDirect3D9 *direct3d; + D3DPRESENT_PARAMETERS d3dpp; + IDirect3DDevice9* direct3ddevice; + D3DGAMMARAMP defaultgammaramp; + D3DGAMMARAMP currentgammaramp; + D3DCAPS9 direct3ddevicecaps; + IDirect3DVertexBuffer9* vertexbuffer; + IDirect3DSurface9 *pNativeSurface; + IDirect3DTexture9 *pNativeTexture; + RECT viewport_rect; + UINT availableVideoMemory; + CUSTOMVERTEX defaultVertices[4]; + String previousError; + IDirect3DPixelShader9* pixelShader; + bool _smoothScaling; + bool _legacyPixelShader; + float _pixelRenderXOffset; + float _pixelRenderYOffset; + bool _renderSprAtScreenRes; + + D3DSpriteBatches _spriteBatches; + // TODO: these draw list backups are needed only for the fade-in/out effects + // find out if it's possible to reimplement these effects in main drawing routine. + SpriteBatchDescs _backupBatchDescs; + D3DSpriteBatches _backupBatches; + + D3DVIEWPORT9 _d3dViewport; + + // Called after new mode was successfully initialized + void OnModeSet(const DisplayMode &mode) override; + void InitSpriteBatch(size_t index, const SpriteBatchDesc &desc) override; + void ResetAllBatches() override; + // Called when the direct3d device is created for the first time + int FirstTimeInit(); + void initD3DDLL(const DisplayMode &mode); + void InitializeD3DState(); + void SetupViewport(); + HRESULT ResetD3DDevice(); + // Unset parameters and release resources related to the display mode + void ReleaseDisplayMode(); + void set_up_default_vertices(); + void AdjustSizeToNearestSupportedByCard(int *width, int *height); + void UpdateTextureRegion(D3DTextureTile *tile, Bitmap *bitmap, D3DBitmap *target, bool hasAlpha); + void CreateVirtualScreen(); + void do_fade(bool fadingOut, int speed, int targetColourRed, int targetColourGreen, int targetColourBlue); + bool IsTextureFormatOk( D3DFORMAT TextureFormat, D3DFORMAT AdapterFormat ); + // Backup all draw lists in the temp storage + void BackupDrawLists(); + // Restore draw lists from the temp storage + void RestoreDrawLists(); + // Deletes draw list backups + void ClearDrawBackups(); + void _renderAndPresent(bool clearDrawListAfterwards); + void _render(bool clearDrawListAfterwards); + void _reDrawLastFrame(); + void RenderSpriteBatches(); + void RenderSpriteBatch(const D3DSpriteBatch &batch); + void _renderSprite(const D3DDrawListEntry *entry, const D3DMATRIX &matGlobal); + void _renderFromTexture(); +}; + + +class D3DGraphicsFactory : public GfxDriverFactoryBase +{ +public: + ~D3DGraphicsFactory() override; + + size_t GetFilterCount() const override; + const GfxFilterInfo *GetFilterInfo(size_t index) const override; + String GetDefaultFilterID() const override; + + static D3DGraphicsFactory *GetFactory(); + static D3DGraphicsDriver *GetD3DDriver(); + +private: + D3DGraphicsFactory(); + + D3DGraphicsDriver *EnsureDriverCreated() override; + D3DGfxFilter *CreateFilter(const String &id) override; + + bool Init(); + + static D3DGraphicsFactory *_factory; + // + // IMPORTANT NOTE: since the Direct3d9 device is created with + // D3DCREATE_MULTITHREADED behavior flag, once it is created the d3d9.dll may + // only be unloaded after window is destroyed, as noted in the MSDN's article + // on "D3DCREATE" + // (http://msdn.microsoft.com/en-us/library/windows/desktop/bb172527.aspx). + // Otherwise window becomes either destroyed prematurely or broken (details + // are unclear), which causes errors during Allegro deinitialization. + // + // Curiously, this problem was only confirmed under WinXP so far. + // + // For the purpose of avoiding this problem, we have a static library wrapper + // that unloads library only at the very program exit (except cases of device + // creation failure). + // + // TODO: find out if there is better solution. + // + static Library _library; + IDirect3D9 *_direct3d; +}; + +} // namespace D3D +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_GFX__ALI3DD3D_H diff --git a/engines/ags/engine/platform/windows/media/video/acwavi.cpp b/engines/ags/engine/platform/windows/media/video/acwavi.cpp new file mode 100644 index 00000000000..de682a80400 --- /dev/null +++ b/engines/ags/engine/platform/windows/media/video/acwavi.cpp @@ -0,0 +1,434 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AVI/MPG player for AGS +// Adapted from MS DirectX Media example program to work with allegro +// 2002 Chris Jones +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS && ! defined (AGS_NO_VIDEO_PLAYER) + +//#define ALLEGRO_STATICLINK // already defined in project settings +#include +#include +#include +#include +#include +#include // Multimedia stream interfaces +#include // DirectDraw multimedia stream interfaces +#include // Defines DEFINE_GUID macro and enables GUID initialization +#include "ac/draw.h" +#include "gfx/bitmap.h" +#include "gfx/graphicsdriver.h" +#include "main/game_run.h" +#include "platform/base/agsplatformdriver.h" + +using namespace AGS::Common; +using namespace AGS::Engine; + +//link with the following libraries under project/settings/link... +//amstrmid.lib quartz.lib strmbase.lib ddraw.lib + +extern void update_audio_system_on_game_loop(); +extern void update_polled_stuff_if_runtime(); +extern int ags_mgetbutton(); +extern volatile char want_exit; +extern IGraphicsDriver *gfxDriver; +//int errno; +char lastError[300]; + +//Global variables +HWND ghWnd; +BOOL g_bAppactive = FALSE; // The window is active +bool useSound = true; +volatile bool currentlyPlaying = false; +volatile bool currentlyPaused = false; + +//DirectDrawEx Global interfaces +extern "C" extern LPDIRECTDRAW2 directdraw; +//extern "C" extern IUnknown* directsound; +extern "C" extern BITMAP *gfx_directx_create_system_bitmap(int width, int height); + +//Global MultiMedia streaming interfaces +IMultiMediaStream *g_pMMStream=NULL; +IMediaStream *g_pPrimaryVidStream=NULL; +IDirectDrawMediaStream *g_pDDStream=NULL; +IDirectDrawStreamSample *g_pSample=NULL; + +Bitmap *vscreen = NULL; +Bitmap *vsMemory = NULL; + +//Function prototypes +HRESULT RenderFileToMMStream(LPCTSTR szFilename); +HRESULT InitRenderToSurface(); +void RenderToSurface(); + +void ExitCode() { + //Release MultiMedia streaming Objects + if (g_pMMStream != NULL) { + g_pMMStream->Release(); + g_pMMStream= NULL; + } + if (g_pSample != NULL) { + g_pSample->Release(); + g_pSample = NULL; + } + if (g_pDDStream != NULL) { + g_pDDStream->Release(); + g_pDDStream= NULL; + } + if (g_pPrimaryVidStream != NULL) { + g_pPrimaryVidStream->Release(); + g_pPrimaryVidStream= NULL; + } +} + +typedef struct BMP_EXTRA_INFO { + LPDIRECTDRAWSURFACE2 surf; + struct BMP_EXTRA_INFO *next; + struct BMP_EXTRA_INFO *prev; + int flags; + int lock_nesting; +} BMP_EXTRA_INFO; + +LPDIRECTDRAWSURFACE get_bitmap_surface (Bitmap *bmp) { + BMP_EXTRA_INFO *bei = (BMP_EXTRA_INFO*)((BITMAP*)bmp->GetAllegroBitmap())->extra; + + // convert the DDSurface2 back to a standard DDSurface + return (LPDIRECTDRAWSURFACE)bei->surf; +} +LPDIRECTDRAWSURFACE2 get_bitmap_surface2 (Bitmap *bmp) { + BMP_EXTRA_INFO *bei = (BMP_EXTRA_INFO*)((BITMAP*)bmp->GetAllegroBitmap())->extra; + + return bei->surf; +} + +//Create the stream sample which will be used to call updates on the video +HRESULT InitRenderToSurface() { + + HRESULT hr; + DDSURFACEDESC ddsd; + + //Use the multimedia stream to get the primary video media stream + hr = g_pMMStream->GetMediaStream(MSPID_PrimaryVideo, &g_pPrimaryVidStream); + if (FAILED(hr)) { + strcpy (lastError, "MMStream::GetMediaStream failed to create the primary video stream."); + return E_FAIL; + } + + //Use the media stream to get the IDirectDrawMediaStream + hr = g_pPrimaryVidStream->QueryInterface(IID_IDirectDrawMediaStream, (void **)&g_pDDStream); + if (FAILED(hr)) { + strcpy(lastError, "The video stream does not support the IDirectDrawMediaStream interface; ensure you have the latest DirectX version installed."); + return E_FAIL; + } + + //Must set dwSize before calling GetFormat + ddsd.dwSize = sizeof(ddsd); + hr = g_pDDStream->GetFormat(&ddsd, NULL, NULL, NULL); + if (FAILED(hr)) { + strcpy(lastError, "IDirectDrawMediaStream::GetFormat failed"); + return E_FAIL; + } + + RECT rect; + rect.top = rect.left = 0; + // these are the width and height of the video + rect.bottom = ddsd.dwHeight; + rect.right = ddsd.dwWidth; + + if (vscreen == NULL) + vscreen = BitmapHelper::CreateRawBitmapOwner(gfx_directx_create_system_bitmap(ddsd.dwWidth, ddsd.dwHeight)); + + if (vscreen == NULL) { + strcpy(lastError, "Unable to create the DX Video System Bitmap"); + return E_FAIL; + } + + vsMemory = BitmapHelper::CreateBitmap(vscreen->GetWidth(), vscreen->GetHeight(), vscreen->GetColorDepth()); + + IDirectDrawSurface *g_pDDSOffscreen; + g_pDDSOffscreen = get_bitmap_surface (vscreen); + + //Create the stream sample + hr = g_pDDStream->CreateSample(g_pDDSOffscreen, &rect, 0, &g_pSample); + if (FAILED(hr)) { + strcpy (lastError, "VideoStream::CreateSample failed"); + return E_FAIL; + } + + return NOERROR; +} + +//Renders a file to a multimedia stream +HRESULT RenderFileToMMStream(LPCTSTR szFilename) { + HRESULT hr; + IAMMultiMediaStream *pAMStream=NULL; + + //Convert filename to Unicode + WCHAR wFile[MAX_PATH]; + MultiByteToWideChar(CP_ACP, 0, szFilename, -1, wFile, + sizeof(wFile)/sizeof(wFile[0])); + + //Create the AMMultiMediaStream object + hr = CoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER, + IID_IAMMultiMediaStream, (void **)&pAMStream); + + if (FAILED(hr)) { + strcpy(lastError, "Could not create a CLSID_MultiMediaStream object. " + "Make sure you have the latest version of DirectX installed."); + return E_FAIL; + } + + //Initialize stream + hr = pAMStream->Initialize(STREAMTYPE_READ, 0, NULL); + if (FAILED(hr)) { + strcpy(lastError, "AMStream::Initialize failed."); + return E_FAIL; + } + //Add primary video stream + hr = pAMStream->AddMediaStream(directdraw, &MSPID_PrimaryVideo, 0, NULL); + if (FAILED(hr)) { + strcpy(lastError, "AddMediaStream failed."); + return E_FAIL; + } + //Add primary audio stream + if (useSound) { + //hr = pAMStream->AddMediaStream(directsound, &MSPID_PrimaryAudio, 0, NULL); + hr = pAMStream->AddMediaStream(NULL, &MSPID_PrimaryAudio, AMMSF_ADDDEFAULTRENDERER, NULL); + if (FAILED(hr)) { + strcpy(lastError, "AddMediaStream failed."); + return E_FAIL; + } + } + //Opens and automatically creates a filter graph for the specified media file + hr = pAMStream->OpenFile(wFile, 0); + if (FAILED(hr)) { + pAMStream->Release(); + sprintf(lastError, "File not found or format not supported: %s", szFilename); + return E_FAIL; + } + + //save the local stream to the global variable + g_pMMStream = pAMStream; + // Add a reference to the file + //pAMStream->AddRef(); + + return NOERROR; +} + +int newWidth, newHeight; + +//Perform frame by frame updates and blits. Set the stream +//state to STOP if there are no more frames to update. +void RenderToSurface(Bitmap *vscreen) { + //update each frame + if (g_pSample->Update(0, NULL, NULL, 0) != S_OK) { + g_bAppactive = FALSE; + g_pMMStream->SetState(STREAMSTATE_STOP); + } + else { + g_bAppactive = TRUE; + Bitmap *screen_bmp = gfxDriver->GetMemoryBackBuffer(); + // TODO: don't render on screen bitmap, use gfxDriver->DrawSprite instead! + screen_bmp->Acquire(); + // Because vscreen is a DX Video Bitmap, it can be stretched + // onto the screen (also a Video Bmp) but not onto a memory + // bitmap (which is what "screen" is when using gfx filters) + if (screen_bmp->IsVideoBitmap()) + { + screen_bmp->StretchBlt(vscreen, + RectWH(0, 0, vscreen->GetWidth(), vscreen->GetHeight()), + RectWH(screen_bmp->GetWidth() / 2 - newWidth / 2, + screen_bmp->GetHeight() / 2 - newHeight / 2, + newWidth, newHeight)); + } + else + { + vsMemory->Blit(vscreen, 0, 0, 0, 0, vscreen->GetWidth(), vscreen->GetHeight()); + screen_bmp->StretchBlt(vsMemory, + RectWH(0, 0, vscreen->GetWidth(), vscreen->GetHeight()), + RectWH(screen_bmp->GetWidth() / 2 - newWidth / 2, + screen_bmp->GetHeight() / 2 - newHeight / 2, + newWidth, newHeight)); + } + screen_bmp->Release(); + + // if we're not playing AVI sound, poll the audio system + if (!useSound) + update_audio_system_on_game_loop(); + + render_to_screen(); + } +} + +void dxmedia_pause_video() { + + if (currentlyPlaying) { + currentlyPaused = true; + g_pMMStream->SetState(STREAMSTATE_STOP); + } + +} + +void dxmedia_resume_video() { + + if (currentlyPlaying) { + currentlyPaused = false; + g_pMMStream->SetState(STREAMSTATE_RUN); + } + +} + +void dxmedia_abort_video() { + + if (currentlyPlaying) { + + currentlyPlaying = false; + g_pMMStream->SetState(STREAMSTATE_STOP); + + ExitCode(); + CoUninitialize(); + delete vscreen; + vscreen = NULL; + if (vsMemory != NULL) + { + delete vsMemory; + vsMemory = NULL; + } + strcpy (lastError, "Played successfully."); + } + +} + +int dxmedia_play_video(const char* filename, bool pUseSound, int canskip, int stretch) { + HRESULT hr; + + useSound = pUseSound; + ghWnd = win_get_window(); + + CoInitialize(NULL); + + if (!useSound) + update_polled_stuff_if_runtime(); + + hr = RenderFileToMMStream(filename); + if (FAILED(hr)) { + ExitCode(); + CoUninitialize(); + return -1; + } + + if (!useSound) + update_polled_stuff_if_runtime(); + + hr = InitRenderToSurface(); + if (FAILED(hr)) { + ExitCode(); + CoUninitialize(); + return -1; + } + + newWidth = vscreen->GetWidth(); + newHeight = vscreen->GetHeight(); + + Bitmap *screen_bmp = gfxDriver->GetMemoryBackBuffer(); + + if ((stretch == 1) || + (vscreen->GetWidth() > screen_bmp->GetWidth()) || + (vscreen->GetHeight() > screen_bmp->GetHeight())) { + // If they want to stretch, or if it's bigger than the screen, then stretch + float widthRatio = (float)vscreen->GetWidth() / (float)screen_bmp->GetWidth(); + float heightRatio = (float)vscreen->GetHeight() / (float)screen_bmp->GetHeight(); + + if (widthRatio > heightRatio) { + newWidth = vscreen->GetWidth() / widthRatio; + newHeight = vscreen->GetHeight() / widthRatio; + } + else { + newWidth = vscreen->GetWidth() / heightRatio; + newHeight = vscreen->GetHeight() / heightRatio; + } + } + + //Now set the multimedia stream to RUN + hr = g_pMMStream->SetState(STREAMSTATE_RUN); + g_bAppactive = TRUE; + + if (FAILED(hr)) { + sprintf(lastError, "Unable to play stream: 0x%08X", hr); + ExitCode(); + CoUninitialize(); + delete vscreen; + return -1; + } + // in case we're not full screen, clear the background + screen_bmp->Clear(); + + currentlyPlaying = true; + + gfxDriver->ClearDrawLists(); + + while ((g_bAppactive) && (!want_exit)) { + + while (currentlyPaused) { + platform->YieldCPU(); + } + + RenderToSurface(vscreen); + //Sleep(0); + int key, mbut, mwheelz; + if (run_service_key_controls(key)) { + if ((canskip == 1) && (key == 27)) + break; + if (canskip >= 2) + break; + } + if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0 && (canskip == 3)) + break; + } + + dxmedia_abort_video(); + + return 0; +} + +#if 0 + +int WINAPI WinMain( + HINSTANCE hInstance, // handle to current instance + HINSTANCE hPrevInstance, // handle to previous instance + LPSTR lpCmdLine, // pointer to command line + int nCmdShow) { + + install_allegro(SYSTEM_AUTODETECT, &errno, atexit); + + install_keyboard(); + + set_color_depth(16); + set_gfx_mode (GFX_DIRECTX_WIN, 640, 480, 0,0); + + set_display_switch_mode(SWITCH_BACKGROUND); + + dxmedia_play_video ("f:\\download\\Seinfeld S05E04 - The Sniffing Accountant.mpg", 1, 1); + dxmedia_play_video ("f:\\download\\Family Guy S02E16 - There's Something About Paulie.mpg", 2, 1); + + return 0; +} +#endif + +#endif // AGS_PLATFORM_OS_WINDOWS \ No newline at end of file diff --git a/engines/ags/engine/platform/windows/media/video/acwavi3d.cpp b/engines/ags/engine/platform/windows/media/video/acwavi3d.cpp new file mode 100644 index 00000000000..79adf85b463 --- /dev/null +++ b/engines/ags/engine/platform/windows/media/video/acwavi3d.cpp @@ -0,0 +1,962 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AVI/MPG player for AGS +// VMR9-based player to work with D3D +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS && ! defined (AGS_NO_VIDEO_PLAYER) + +//#define ALLEGRO_STATICLINK // already defined in project settings +#include +#include +#include +#include +#include +#define DWORD_PTR DWORD* +#define LONG_PTR LONG* +#define LPD3DVECTOR D3DVECTOR* +#define _D3DTYPES_H_ +#define _STRSAFE_H_INCLUDED_ +typedef float D3DVALUE, *LPD3DVALUE; +#include "ac/common.h" +#include "main/game_run.h" +#include "media/video/VMR9Graph.h" +#include "platform/base/agsplatformdriver.h" +//#include +#include "media/audio/audio_system.h" + +#define USES_CONVERSION int _convert = 0; _convert; UINT _acp = CP_ACP; _acp; LPCWSTR _lpw = NULL; _lpw; LPCSTR _lpa = NULL; _lpa + +inline LPWSTR WINAPI AtlA2WHelper(LPWSTR lpw, LPCSTR lpa, int nChars, UINT acp) +{ + // verify that no illegal character present + // since lpw was allocated based on the size of lpa + // don't worry about the number of chars + lpw[0] = '\0'; + MultiByteToWideChar(acp, 0, lpa, -1, lpw, nChars); + return lpw; +} +inline LPWSTR WINAPI AtlA2WHelper(LPWSTR lpw, LPCSTR lpa, int nChars) +{ + return AtlA2WHelper(lpw, lpa, nChars, CP_ACP); +} +#define ATLA2WHELPER AtlA2WHelper + + #define A2W(lpa) (\ + ((_lpa = lpa) == NULL) ? NULL : (\ + _convert = (lstrlenA(_lpa)+1),\ + ATLA2WHELPER((LPWSTR) alloca(_convert*2), _lpa, _convert))) + + +// Interface from main game + +extern int ags_mgetbutton(); +extern void update_audio_system_on_game_loop(); +extern volatile char want_exit; +extern char lastError[300]; +CVMR9Graph *graph = NULL; + +void dxmedia_shutdown_3d() +{ + if (graph != NULL) + { + delete graph; + graph = NULL; + } +} + +int dxmedia_play_video_3d(const char* filename, IDirect3DDevice9 *device, bool useAVISound, int canskip, int stretch) +{ + HWND gameWindow = win_get_window(); + + if (graph == NULL) + { + graph = new CVMR9Graph(gameWindow, device); + } + + if (!useAVISound) + update_audio_system_on_game_loop(); + + if (!graph->SetMediaFile(filename, useAVISound)) + { + dxmedia_shutdown_3d(); + return -1; + } + graph->SetLayerZOrder(0, 0); + + if (!useAVISound) + update_audio_system_on_game_loop(); + + if (!graph->PlayGraph()) + { + dxmedia_shutdown_3d(); + return -1; + } + + + OAFilterState filterState = State_Running; + while ((filterState != State_Stopped) && (!want_exit)) + { + WaitForNextFrame(); + + if (!useAVISound) + update_audio_system_on_game_loop(); + + filterState = graph->GetState(); + + int key, mbut, mwheelz; + if (run_service_key_controls(key)) { + if ((canskip == 1) && (key == 27)) + break; + if (canskip >= 2) + break; + } + if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0 && (canskip == 3)) + break; + + //device->Present(NULL, NULL, 0, NULL); + } + + graph->StopGraph(); + + dxmedia_shutdown_3d(); + return 0; +} + + +// VMR9Graph.cpp: implementation of the CVMR9Graph class. +// +////////////////////////////////////////////////////////////////////// + + +//#include + +#if AGS_PLATFORM_DEBUG +#undef THIS_FILE +static char THIS_FILE[]=__FILE__; +#define new DEBUG_NEW +#endif + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +// Function name : CVMR9Graph::CVMR9Graph +// Description : constructor +// Return type : +CVMR9Graph::CVMR9Graph() +{ + InitDefaultValues(); +} + + +// Function name : CVMR9Graph +// Description : constructor +// Return type : +// Argument : HWND MediaWindow +// Argument : int NumberOfStream +CVMR9Graph::CVMR9Graph(HWND MediaWindow, IDirect3DDevice9 *device, int NumberOfStream) +{ + InitDefaultValues(); + + if (MediaWindow != NULL) + { + m_hMediaWindow = MediaWindow; + } + + if (NumberOfStream > 0 && NumberOfStream < 11) { + m_nNumberOfStream = NumberOfStream; + } + + //m_pD3DDevice = device; + m_oldWndProc = GetWindowLong(m_hMediaWindow, GWL_WNDPROC); +} + +// Function name : CVMR9Graph::~CVMR9Graph +// Description : destructor +// Return type : +CVMR9Graph::~CVMR9Graph() +{ + ReleaseAllInterfaces(); + long newProc = GetWindowLong(m_hMediaWindow, GWL_WNDPROC); +} + + +// Function name : CVMR9Graph::InitDefaultValues +// Description : initialize all default values +// Return type : void +void CVMR9Graph::InitDefaultValues() +{ + ZeroMemory(m_pszErrorDescription, 1024); + m_dwRotId = -1; + m_nNumberOfStream = 4; // default VMR9 config + m_hMediaWindow = NULL; + // SRC interfaces + for (int i=0; i<10; i++) { + m_srcFilterArray[i] = NULL; + } + // SOUND interface + m_pDirectSoundFilter = NULL; + // GRAPH interfaces + m_pGraphUnknown = NULL; + m_pGraphBuilder = NULL; + m_pFilterGraph = NULL; + m_pFilterGraph2 = NULL; + m_pMediaControl = NULL; + m_pMediaSeeking = NULL; + //m_pMediaEvent = NULL; + m_pMediaEventEx = NULL; + // VMR9 interfaces + m_pVMRBaseFilter = NULL; + m_pVMRFilterConfig = NULL; + m_pVMRMixerBitmap = NULL; + m_pVMRMixerControl = NULL; + m_pVMRMonitorConfig = NULL; + m_pVMRWindowlessControl = NULL; + // DIRECT3D interfaces + m_pD3DSurface = NULL; +} + +// Function name : CVMR9Graph::ReleaseAllInterfaces +// Description : release all of the graph interfaces +// Return type : void +void CVMR9Graph::ReleaseAllInterfaces() +{ + // CALLBACK handle + /*if (m_pMediaEventEx != NULL) { + m_pMediaEventEx->SetNotifyWindow(NULL, WM_MEDIA_NOTIF, NULL); + }*/ + // SRC interfaces + for (int i=0; i<10; i++) { + IBaseFilter* pSrcFilter = m_srcFilterArray[i]; + if (pSrcFilter != NULL) { + pSrcFilter->Release(); + m_srcFilterArray[i] = NULL; + } + } + // SOUND interfaces + if (m_pDirectSoundFilter != NULL) { + m_pDirectSoundFilter->Release(); + m_pDirectSoundFilter = NULL; + } + // VMR9 interfaces + if (m_pVMRFilterConfig != NULL) { + m_pVMRFilterConfig->Release(); + m_pVMRFilterConfig = NULL; + } + if (m_pVMRMixerBitmap != NULL) { + m_pVMRMixerBitmap->Release(); + m_pVMRMixerBitmap = NULL; + } + if (m_pVMRMixerControl != NULL) { + m_pVMRMixerControl->Release(); + m_pVMRMixerControl = NULL; + } + if (m_pVMRMonitorConfig != NULL) { + m_pVMRMonitorConfig->Release(); + m_pVMRMonitorConfig = NULL; + } + if (m_pVMRWindowlessControl != NULL) { + m_pVMRWindowlessControl->Release(); + m_pVMRWindowlessControl = NULL; + } + if (m_pVMRBaseFilter != NULL) { + m_pVMRBaseFilter->Release(); + m_pVMRBaseFilter = NULL; + } + // GRAPH interfaces + if (m_pGraphBuilder != NULL) { + m_pGraphBuilder->Release(); + m_pGraphBuilder = NULL; + } + if (m_pFilterGraph != NULL) { + m_pFilterGraph->Release(); + m_pFilterGraph = NULL; + } + if (m_pFilterGraph2 != NULL) { + m_pFilterGraph2->Release(); + m_pFilterGraph2 = NULL; + } + if (m_pMediaControl != NULL) { + m_pMediaControl->Release(); + m_pMediaControl = NULL; + } + if (m_pMediaSeeking!= NULL) { + m_pMediaSeeking->Release(); + m_pMediaSeeking = NULL; + } + /*if (m_pMediaEvent != NULL) { + m_pMediaEvent->Release(); + m_pMediaEvent = NULL; + }*/ + /*if (m_pMediaEventEx != NULL) { + m_pMediaEventEx->Release(); + m_pMediaEventEx = NULL; + }*/ + if (m_pGraphUnknown != NULL) { + m_pGraphUnknown->Release(); + m_pGraphUnknown = NULL; + } + // DIRECT3D interfaces + if (m_pD3DSurface != NULL) { + m_pD3DSurface->Release(); + m_pD3DSurface = NULL; + } +} + +////////////////////////////////////////////////////////////////////// +// Helper Functions +////////////////////////////////////////////////////////////////////// + + +// Function name : CVMR9Graph::GetLastError +// Description : get the last error description +// Return type : LPCTSTR +LPCTSTR CVMR9Graph::GetLastError() +{ + return (const char*)m_pszErrorDescription; +} + + +// Function name : CVMR9Graph::AddToRot +// Description : let the graph instance be accessible from graphedit +// Return type : HRESULT +// Argument : IUnknown *pUnkGraph +// Argument : DWORD *pdwRegister +HRESULT CVMR9Graph::AddToRot(IUnknown *pUnkGraph) +{ + if (pUnkGraph == NULL) { + return E_INVALIDARG; + } + + IMoniker * pMoniker; + IRunningObjectTable *pROT; + if (FAILED(GetRunningObjectTable(0, &pROT))) { + return E_FAIL; + } + WCHAR wsz[256]; + wsprintfW(wsz, L"FilterGraph %08x pid %08x", (DWORD_PTR)pUnkGraph, GetCurrentProcessId()); + HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker); + if (SUCCEEDED(hr)) { + hr = pROT->Register(0, pUnkGraph, pMoniker, &m_dwRotId); + pMoniker->Release(); + } + pROT->Release(); + + return hr; +} + + +// Function name : CVMR9Graph::RemoveFromRot +// Description : remove the graph instance accessibility from graphedit +// Return type : void +void CVMR9Graph::RemoveFromRot() +{ + if (m_dwRotId != -1) { + IRunningObjectTable *pROT; + if (SUCCEEDED(GetRunningObjectTable(0, &pROT))) { + pROT->Revoke(m_dwRotId); + m_dwRotId = -1; + pROT->Release(); + } + } +} + + +// Function name : CVMR9Graph::GetPin +// Description : return the desired pin +// Return type : IPin* +// Argument : IBaseFilter *pFilter +// Argument : PIN_DIRECTION PinDir +IPin* CVMR9Graph::GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir) +{ + BOOL bFound = FALSE; + IEnumPins *pEnum; + IPin *pPin; + + pFilter->EnumPins(&pEnum); + while(pEnum->Next(1, &pPin, 0) == S_OK) { + PIN_DIRECTION PinDirThis; + pPin->QueryDirection(&PinDirThis); + if (PinDir == PinDirThis) + { + IPin *pTmp = 0; + if (SUCCEEDED(pPin->ConnectedTo(&pTmp))) // Already connected, not the pin we want. + { + pTmp->Release(); + } + else // Unconnected, this is the pin we want. + { + bFound = true; + break; + } + } + pPin->Release(); + } + pEnum->Release(); + + return (bFound ? pPin : 0); +} + + +// Function name : CVMR9Graph::ReportError +// Description : report an error in the dump device +// Return type : void +// Argument : const char* pszError +// Argument : HRESULT hrCode +void CVMR9Graph::ReportError(const char* pszError, HRESULT hrCode) +{ + TCHAR szErr[MAX_ERROR_TEXT_LEN]; + DWORD res = AMGetErrorText(hrCode, szErr, MAX_ERROR_TEXT_LEN); + if (res != 0) { + sprintf(m_pszErrorDescription, "[ERROR in %s, line %d] %s : COM Error 0x%x, %s", __FILE__, __LINE__, pszError, hrCode, szErr); + } else { + sprintf(m_pszErrorDescription, "[ERROR in %s, line %d] %s : COM Error 0x%x", __FILE__, __LINE__, pszError, hrCode); + } + strcpy(lastError, m_pszErrorDescription); + //TRACE("%s \r\n", m_pszErrorDescription); +} + + +// Function name : CVMR9Graph::GetNextFilter +// Description : +// Return type : HRESULT +// Argument : IBaseFilter *pFilter +// Argument : PIN_DIRECTION Dir +// Argument : IBaseFilter **ppNext +HRESULT CVMR9Graph::GetNextFilter(IBaseFilter *pFilter, PIN_DIRECTION Dir, IBaseFilter **ppNext) +{ + if (!pFilter || !ppNext) return E_POINTER; + + IEnumPins *pEnum = 0; + IPin *pPin = 0; + HRESULT hr = pFilter->EnumPins(&pEnum); + if (FAILED(hr)) return hr; + while (S_OK == pEnum->Next(1, &pPin, 0)) { + // See if this pin matches the specified direction. + PIN_DIRECTION ThisPinDir; + hr = pPin->QueryDirection(&ThisPinDir); + if (FAILED(hr)) { + // Something strange happened. + hr = E_UNEXPECTED; + pPin->Release(); + break; + } + if (ThisPinDir == Dir) { + // Check if the pin is connected to another pin. + IPin *pPinNext = 0; + hr = pPin->ConnectedTo(&pPinNext); + if (SUCCEEDED(hr)) + { + // Get the filter that owns that pin. + PIN_INFO PinInfo; + hr = pPinNext->QueryPinInfo(&PinInfo); + pPinNext->Release(); + pPin->Release(); + pEnum->Release(); + if (FAILED(hr) || (PinInfo.pFilter == NULL)) + { + // Something strange happened. + return E_UNEXPECTED; + } + // This is the filter we're looking for. + *ppNext = PinInfo.pFilter; // Client must release. + return S_OK; + } + } + pPin->Release(); + } + pEnum->Release(); + // Did not find a matching filter. + return E_FAIL; +} + + +// Function name : CVMR9Graph::RemoveFilterChain +// Description : remove a chain of filter, stopping at pStopFilter +// Return type : BOOL +// Argument : IBaseFilter* pFilter +// Argument : IBaseFilter* pStopFilter +BOOL CVMR9Graph::RemoveFilterChain(IBaseFilter* pFilter, IBaseFilter* pStopFilter) +{ + HRESULT hr; + + IBaseFilter* pFilterFound = NULL; + + hr = GetNextFilter(pFilter, PINDIR_OUTPUT, &pFilterFound); + if (SUCCEEDED(hr) && pFilterFound != pStopFilter) { + RemoveFilterChain(pFilterFound, pStopFilter); + pFilterFound->Release(); + } + + m_pFilterGraph->RemoveFilter(pFilter); + + return TRUE; +} + + +// Function name : CVMR9Graph::AddFilterByClsid +// Description : add a filter in the chain +// Return type : HRESULT +// Argument : IGraphBuilder *pGraph +// Argument : LPCWSTR wszName +// Argument : const GUID& clsid +// Argument : IBaseFilter **ppF +HRESULT CVMR9Graph::AddFilterByClsid(IGraphBuilder *pGraph, LPCWSTR wszName, const GUID& clsid, IBaseFilter **ppF) +{ + *ppF = NULL; + HRESULT hr = CoCreateInstance(clsid, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)ppF); + if (SUCCEEDED(hr)) + { + hr = pGraph->AddFilter((*ppF), wszName); + } + return hr; +} + + +////////////////////////////////////////////////////////////////////// +// Layer helper methods +////////////////////////////////////////////////////////////////////// + + +// Function name : CVMR9Graph::IsValidLayer +// Description : check for valid layer +// Return type : BOOL +// Argument : int nLayer +BOOL CVMR9Graph::IsValidLayer(int nLayer) +{ + if (nLayer > 9 || nLayer < 0) return FALSE; + + IBaseFilter* pBaseFilter = m_srcFilterArray[nLayer]; + if (pBaseFilter == NULL) + return FALSE; + else + return TRUE; +} + + + +////////////////////////////////////////////////////////////////////// +// Graph Construction / Render +////////////////////////////////////////////////////////////////////// + + +// We need this because the filter graph must be built +// on the D3D thread +static int wndproc_build_filter_graph() +{ + return graph->BuildAndRenderGraph(graph->UseAVISound); +} + +BOOL CVMR9Graph::BuildAndRenderGraph(bool withSound) +{ + USES_CONVERSION; + + int nLayer = 0; + HRESULT hr; + + // ENSURE that a valid graph builder is available + if (m_pGraphBuilder == NULL) { + BOOL bRet = BuildFilterGraph(withSound); + if (!bRet) return bRet; + } + + // ENSURE that the filter graph is in a stop state + OAFilterState filterState; + m_pMediaControl->GetState(500, &filterState); + if (filterState != State_Stopped) { + m_pMediaControl->Stop(); + } + + // CHECK a source filter availaibility for the layer + if (m_srcFilterArray[nLayer] == NULL) { + char pszFilterName[10]; + sprintf(pszFilterName, "SRC%02d", nLayer); + IBaseFilter* pBaseFilter = NULL; + hr = m_pGraphBuilder->AddSourceFilter(A2W(m_pszFileName), A2W(pszFilterName), &pBaseFilter); + if (FAILED(hr)) { + ReportError("Could not find a source filter for this file", hr); + return FALSE; + } + m_srcFilterArray[nLayer] = pBaseFilter; + } else { + // suppress the old src filter + IBaseFilter* pBaseFilter = m_srcFilterArray[nLayer]; + RemoveFilterChain(pBaseFilter, m_pVMRBaseFilter); + pBaseFilter->Release(); + m_srcFilterArray[nLayer] = NULL; + // create a new src filter + char pszFilterName[10]; + sprintf(pszFilterName, "SRC%02d", nLayer); + hr = m_pGraphBuilder->AddSourceFilter(A2W(m_pszFileName), A2W(pszFilterName), &pBaseFilter); + m_srcFilterArray[nLayer] = pBaseFilter; + if (FAILED(hr)) { + m_srcFilterArray[nLayer] = NULL; + ReportError("Could not load the file", hr); + return FALSE; + } + } + + // RENDER the graph + BOOL bRet = RenderGraph(); + if (!bRet) return bRet; + + return TRUE; +} + +// Function name : CVMR9Graph::SetMediaFile +// Description : set a media source +// Return type : BOOL +// Argument : const char* pszFileName +// Argument : int nLayer = 0 +BOOL CVMR9Graph::SetMediaFile(const char* pszFileName, bool withSound, int nLayer) +{ + + if (pszFileName == NULL) { + ReportError("Could not load a file with an empty file name", E_INVALIDARG); + return FALSE; + } + + UseAVISound = withSound; + m_pszFileName = pszFileName; + + if (!wnd_call_proc(wndproc_build_filter_graph)) + return FALSE; + + + return TRUE; +} + +// Function name : CVMR9Graph::BuildFilterGraph +// Description : construct the filter graph +// Return type : BOOL +BOOL CVMR9Graph::BuildFilterGraph(bool withSound) +{ + HRESULT hr; + + ReleaseAllInterfaces(); + RemoveFromRot(); + + // BUILD the filter graph + hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IUnknown, (void**) &m_pGraphUnknown); + if (FAILED(hr)) { + ReportError("Could not build the graph", hr); + return FALSE; + } + // QUERY the filter graph interfaces + hr = m_pGraphUnknown->QueryInterface(IID_IGraphBuilder, (void**) &m_pGraphBuilder); + hr = m_pGraphUnknown->QueryInterface(IID_IFilterGraph, (void**) &m_pFilterGraph); + hr = m_pGraphUnknown->QueryInterface(IID_IFilterGraph2, (void**) &m_pFilterGraph2); + hr = m_pGraphUnknown->QueryInterface(IID_IMediaControl, (void**) & m_pMediaControl); + hr = m_pGraphUnknown->QueryInterface(IID_IMediaSeeking, (void**) & m_pMediaSeeking); + //hr = m_pGraphUnknown->QueryInterface(IID_IMediaEvent, (void**) &m_pMediaEvent); + //hr = m_pGraphUnknown->QueryInterface(IID_IMediaEventEx, (void**) &m_pMediaEventEx); + +/* // SET the graph state window callback + if (m_pMediaEventEx != NULL) { + m_pMediaEventEx->SetNotifyWindow((OAHWND)m_hMediaWindow, WM_MEDIA_NOTIF, NULL); + //m_pMediaEventEx->SetNotifyWindow(NULL, NULL, NULL); + }*/ + + if (withSound) + BuildSoundRenderer(); + +// Don't known what's wrong... but RenderEx crash when playing whith graphedit build 021204 ... + //do we need this?? + //AddToRot(m_pGraphUnknown); + + return BuildVMR(); +} + + +// Function name : CVMR9Graph::BuildVMR +// Description : construct and add the VMR9 renderer to the graph +// Return type : BOOL +BOOL CVMR9Graph::BuildVMR() +{ + HRESULT hr; + + if (m_hMediaWindow == NULL) { + ReportError("Could not operate without a Window", E_FAIL); + return FALSE; + } + + if (m_pGraphBuilder == NULL) { + ReportError("Could not build the VMR, the graph isn't valid", E_FAIL); + return FALSE; + } + + // BUILD the VMR9 + IBaseFilter *pVmr = NULL; + hr = CoCreateInstance(CLSID_VideoMixingRenderer9, 0, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**) &m_pVMRBaseFilter); + if (FAILED(hr)) { + ReportError("Could not create an instance ofthe VMR9", hr); + return FALSE; + } + + // ADD the VMR9 to the graph + hr = m_pGraphBuilder->AddFilter(m_pVMRBaseFilter, L"VMR9"); + if (FAILED(hr)) { + ReportError("Could not add the VMR9 to the Graph", hr); + return FALSE; + } + + // DIRECT3D + //BOOL bD3D = BuildDirect3d(); + + // QUERY the VMR9 interfaces + hr = m_pVMRBaseFilter->QueryInterface(IID_IVMRFilterConfig9, (void**) &m_pVMRFilterConfig); + if (SUCCEEDED(hr)) { + // CONFIGURE the VMR9 + m_pVMRFilterConfig->SetRenderingMode(VMR9Mode_Windowless); + m_pVMRFilterConfig->SetNumberOfStreams(m_nNumberOfStream); + } + + hr = m_pVMRBaseFilter->QueryInterface(IID_IVMRWindowlessControl9, (void**) &m_pVMRWindowlessControl); + if (SUCCEEDED(hr)) { + // CONFIGURE the VMR9 + m_pVMRWindowlessControl->SetVideoClippingWindow(m_hMediaWindow); + m_pVMRWindowlessControl->SetAspectRatioMode(VMR9ARMode_LetterBox); + } + + hr = m_pVMRBaseFilter->QueryInterface(IID_IVMRMixerBitmap9, (void**) &m_pVMRMixerBitmap); + hr = m_pVMRBaseFilter->QueryInterface(IID_IVMRMixerControl9, (void**) &m_pVMRMixerControl); + hr = m_pVMRBaseFilter->QueryInterface(IID_IVMRMonitorConfig9, (void**) &m_pVMRMonitorConfig); + + return TRUE; +} + + +// Function name : CVMR9Graph::BuildSoundRendered +// Description : build the DirectSound renderer +// Return type : BOOL +BOOL CVMR9Graph::BuildSoundRenderer() +{ + HRESULT hr; + + hr = AddFilterByClsid(m_pGraphBuilder, L"DirectSound", CLSID_DSoundRender, &m_pDirectSoundFilter); + if (FAILED(hr)) { + ReportError("Could not add the DirectSoundRenderer", hr); + return FALSE; + } + return TRUE; +} + +// Function name : CVMR9Graph::RenderGraph +// Description : render the graph +// Return type : BOOL +BOOL CVMR9Graph::RenderGraph() +{ + HRESULT hr; + + if (m_pFilterGraph2 == NULL) { + ReportError("Could not render the graph because it is not fully constructed", E_FAIL); + return FALSE; + } + + for (int i=0; i<10; i++) { + IBaseFilter* pBaseFilter = m_srcFilterArray[i]; + if (pBaseFilter != NULL) { + IPin* pPin; + while ((pPin = GetPin(pBaseFilter, PINDIR_OUTPUT)) != NULL) + { + hr = m_pFilterGraph2->RenderEx(pPin, AM_RENDEREX_RENDERTOEXISTINGRENDERERS, NULL); + if (FAILED(hr)) + { + ReportError("Unable to render the pin", hr); + return FALSE; + } + } + } + } + return TRUE; +} + + +// Function name : CVMR9Graph::PreserveAspectRatio +// Description : set aspect ratio mode +// Return type : BOOL +// Argument : BOOL bPreserve +BOOL CVMR9Graph::PreserveAspectRatio(BOOL bPreserve) +{ + if (m_pVMRWindowlessControl == NULL) { + ReportError("Can't set aspect ratio, no VMR", E_FAIL); + return FALSE; + } + + if (bPreserve) + m_pVMRWindowlessControl->SetAspectRatioMode(VMR9ARMode_LetterBox); + else + m_pVMRWindowlessControl->SetAspectRatioMode(VMR9ARMode_None); + + return TRUE; +} + + +// Function name : CVMR9Graph::AddFilter +// Description : manually add a filter in the graph +// Return type : IBaseFilter* : caller responsible of release +// Argument : const char* pszName +// Argument : const GUID& clsid +IBaseFilter* CVMR9Graph::AddFilter(const char* pszName, const GUID& clsid) +{ + USES_CONVERSION; + + HRESULT hr; + + IBaseFilter* pBaseFilter = NULL; + + if (pszName == NULL) { + ReportError("Can't add filter, no valid name", E_INVALIDARG); + return NULL; + } + + hr = AddFilterByClsid(m_pGraphBuilder, A2W(pszName), clsid, &pBaseFilter); + if (FAILED(hr)) { + ReportError("Can't add filter", hr); + return NULL; + } + + return pBaseFilter; +} + +// Function name : CVMR9Graph::PlayGraph +// Description : run the graph +// Return type : BOOL +BOOL CVMR9Graph::PlayGraph() +{ + if (m_pMediaControl == NULL) { + ReportError("Can't play, no graph", E_FAIL); + return FALSE; + } + if (m_pVMRWindowlessControl == NULL) { + ReportError("Can't play, no VMR", E_FAIL); + return FALSE; + } + + // MEDIA SIZE + LONG Width; + LONG Height; + LONG ARWidth; + LONG ARHeight; + m_pVMRWindowlessControl->GetNativeVideoSize(&Width, &Height, &ARWidth, &ARHeight); + + RECT mediaRect; + mediaRect.left = 0; + mediaRect.top = 0; + mediaRect.right = Width; + mediaRect.bottom = Height; + + RECT wndRect; + GetClientRect(m_hMediaWindow, &wndRect); + + m_pVMRWindowlessControl->SetVideoPosition(&mediaRect, &wndRect); + + // RUN + m_pMediaControl->Run(); + + return TRUE; +} + + +// Function name : CVMR9Graph::StopGraph +// Description : stop the graph +// Return type : BOOL +BOOL CVMR9Graph::StopGraph() +{ + if (m_pMediaControl == NULL) { + ReportError("Can't stop, no graph", E_FAIL); + return FALSE; + } + + m_pMediaControl->Stop(); + + return TRUE; +} + +OAFilterState CVMR9Graph::GetState() +{ + OAFilterState filterState; + m_pMediaControl->GetState(500, &filterState); + if (filterState == State_Running) + { + LONGLONG curPos; + m_pMediaSeeking->GetCurrentPosition(&curPos); + LONGLONG length; + m_pMediaSeeking->GetDuration(&length); + + if (curPos >= length) + { + filterState = State_Stopped; + } + } + + return filterState; +} + + +// Function name : CVMR9Graph::ResetGraph +// Description : reset the graph - clean interfaces +// Return type : BOOL +BOOL CVMR9Graph::ResetGraph() +{ + // STOP the graph + if (m_pMediaControl != NULL) { + m_pMediaControl->Stop(); + } + + try { + ReleaseAllInterfaces(); + } catch(...) { + ReportError("Can't reset graph, we have serious bugs...", E_FAIL); + return FALSE; + } + + return TRUE; +} + + +// Function name : SetLayerZOrder +// Description : set z order of the layer +// Return type : BOOL +// Argument : int nLayer +// Argument : DWORD dwZOrder : bigger is away +BOOL CVMR9Graph::SetLayerZOrder(int nLayer, DWORD dwZOrder) +{ + HRESULT hr; + + if (!IsValidLayer(nLayer)) { + ReportError("Can't set order, incorect layer", E_INVALIDARG); + return FALSE; + } + + if (m_pVMRMixerControl == NULL) { + ReportError("Can't set order, no VMR", E_FAIL); + return FALSE; + } + + hr = m_pVMRMixerControl->SetZOrder(nLayer, dwZOrder); + if (FAILED(hr)) { + ReportError("Can't set ZOrder", hr); + return FALSE; + } + + return TRUE; +} + +#endif // AGS_PLATFORM_OS_WINDOWS diff --git a/engines/ags/engine/platform/windows/minidump.cpp b/engines/ags/engine/platform/windows/minidump.cpp new file mode 100644 index 00000000000..b71dde460d8 --- /dev/null +++ b/engines/ags/engine/platform/windows/minidump.cpp @@ -0,0 +1,111 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS && !AGS_PLATFORM_DEBUG +#define UNICODE +#include // sprintf +#include "windows.h" +#include +#include "main/main.h" + +CONTEXT cpustate; +EXCEPTION_RECORD excinfo; +int miniDumpResultCode = 0; + +typedef enum _MINIDUMP_TYPE { + MiniDumpNormal = 0x0000, + MiniDumpWithDataSegs = 0x0001, + MiniDumpWithFullMemory = 0x0002, + MiniDumpWithHandleData = 0x0004, + MiniDumpFilterMemory = 0x0008, + MiniDumpScanMemory = 0x0010, + MiniDumpWithUnloadedModules = 0x0020, + MiniDumpWithIndirectlyReferencedMemory = 0x0040, + MiniDumpFilterModulePaths = 0x0080, + MiniDumpWithProcessThreadData = 0x0100, + MiniDumpWithPrivateReadWriteMemory = 0x0200, + MiniDumpWithoutOptionalData = 0x0400, +} MINIDUMP_TYPE; + +typedef struct _MINIDUMP_EXCEPTION_INFORMATION { + DWORD ThreadId; + PEXCEPTION_POINTERS ExceptionPointers; + BOOL ClientPointers; +} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; + +typedef BOOL (WINAPI * MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD ProcessId, + HANDLE hFile, MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST void* UserStreamParam, + CONST void* CallbackParam); + +MINIDUMPWRITEDUMP _MiniDumpWriteDump; + + +void CreateMiniDump( EXCEPTION_POINTERS* pep ) +{ + HMODULE dllHandle = LoadLibrary(L"dbghelp.dll"); + if (dllHandle == NULL) + { + miniDumpResultCode = 1; + return; + } + + _MiniDumpWriteDump = (MINIDUMPWRITEDUMP)GetProcAddress(dllHandle, "MiniDumpWriteDump"); + if (_MiniDumpWriteDump == NULL) + { + FreeLibrary(dllHandle); + miniDumpResultCode = 2; + return; + } + + char fileName[80]; + sprintf(fileName, "CrashInfo.%s.dmp", EngineVersion.LongString.GetCStr()); + HANDLE hFile = CreateFileA(fileName, GENERIC_READ | GENERIC_WRITE, + 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if((hFile != NULL) && (hFile != INVALID_HANDLE_VALUE)) + { + MINIDUMP_EXCEPTION_INFORMATION mdei; + + mdei.ThreadId = GetCurrentThreadId(); + mdei.ExceptionPointers = pep; + mdei.ClientPointers = FALSE; + + MINIDUMP_TYPE mdt = MiniDumpNormal; //MiniDumpWithPrivateReadWriteMemory; + + BOOL rv = _MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), + hFile, mdt, (pep != 0) ? &mdei : 0, NULL, NULL); + + if (!rv) + miniDumpResultCode = 4; + + CloseHandle(hFile); + } + else + miniDumpResultCode = 3; + + FreeLibrary(dllHandle); +} + +int CustomExceptionHandler (LPEXCEPTION_POINTERS exinfo) { + cpustate = exinfo->ContextRecord[0]; + excinfo = exinfo->ExceptionRecord[0]; + CreateMiniDump(exinfo); + + return EXCEPTION_EXECUTE_HANDLER; +} + +#endif // AGS_PLATFORM_OS_WINDOWS && !AGS_PLATFORM_DEBUG diff --git a/engines/ags/engine/platform/windows/setup/winsetup.cpp b/engines/ags/engine/platform/windows/setup/winsetup.cpp new file mode 100644 index 00000000000..ea4f77fc7ec --- /dev/null +++ b/engines/ags/engine/platform/windows/setup/winsetup.cpp @@ -0,0 +1,1255 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ac/gamestructdefines.h" +#undef RGB +#undef PALETTE +#define RGB void* +#define PALETTE void* +#include "gfx/gfxdriverfactory.h" +#include "gfx/gfxfilter.h" +#include "gfx/graphicsdriver.h" +#include "main/config.h" +#include "main/graphics_mode.h" +#include "platform/base/agsplatformdriver.h" +#include "resource/resource.h" +#include "util/file.h" +#include "util/string_utils.h" + +#define AL_ID(a,b,c,d) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d)) + +#define DIGI_DIRECTAMX(n) AL_ID('A','X','A'+(n),' ') +// This DirectX hardware mixer is crap, it crashes the program +// when two sound effects are played at once +#define DIGI_DIRECTX(n) AL_ID('D','X','A'+(n),' ') +#define DIGI_WAVOUTID(n) AL_ID('W','O','A'+(n),' ') +#define DIGI_NONE 0 +#define MIDI_AUTODETECT -1 +#define MIDI_NONE 0 +#define MIDI_WIN32MAPPER AL_ID('W','3','2','M') + +extern "C" +{ + HWND win_get_window(); +} + +namespace AGS +{ +namespace Engine +{ + +using namespace AGS::Common; + +//============================================================================= +// +// WinConfig struct, keeps all configurable data. +// +//============================================================================= +struct WinConfig +{ + String Title; + String VersionString; + + String DataDirectory; + String UserSaveDir; + GameResolutionType GameResType; + Size GameResolution; + int GameColourDepth; + bool LetterboxByDesign; + + String GfxDriverId; + String GfxFilterId; + Size ScreenSize; + GameFrameSetup FsGameFrame; + GameFrameSetup WinGameFrame; + int RefreshRate; + bool Windowed; + bool VSync; + bool RenderAtScreenRes; + bool AntialiasSprites; + + int DigiID; + int MidiID; + bool ThreadedAudio; + bool UseVoicePack; + + bool MouseAutoLock; + float MouseSpeed; + + int SpriteCacheSize; + String DefaultLanguageName; + String Language; + + WinConfig(); + void SetDefaults(); + void Load(const ConfigTree &cfg); + void Save(ConfigTree &cfg); +}; + +WinConfig::WinConfig() +{ + SetDefaults(); +} + +void WinConfig::SetDefaults() +{ + DataDirectory = "."; + GameResType = kGameResolution_Undefined; + GameColourDepth = 0; + LetterboxByDesign = false; + + GfxFilterId = "StdScale"; + GfxDriverId = "D3D9"; + ScreenSize = get_desktop_size(); + FsGameFrame.ScaleDef = kFrame_MaxProportional; + WinGameFrame.ScaleDef = kFrame_MaxRound; + RefreshRate = 0; + Windowed = false; + VSync = false; + RenderAtScreenRes = false; + AntialiasSprites = false; + + MouseAutoLock = false; + MouseSpeed = 1.f; + + DigiID = -1; // autodetect + MidiID = -1; + ThreadedAudio = false; + UseVoicePack = true; + + SpriteCacheSize = 1024 * 128; + DefaultLanguageName = "Game Default"; + + Title = "Game Setup"; +} + +void WinConfig::Load(const ConfigTree &cfg) +{ + DataDirectory = INIreadstring(cfg, "misc", "datadir", DataDirectory); + UserSaveDir = INIreadstring(cfg, "misc", "user_data_dir"); + // Backward-compatible resolution type + GameResType = (GameResolutionType)INIreadint(cfg, "misc", "defaultres", GameResType); + if (GameResType < kGameResolution_Undefined || GameResType >= kNumGameResolutions) + GameResType = kGameResolution_Undefined; + GameResolution.Width = INIreadint(cfg, "misc", "game_width", GameResolution.Width); + GameResolution.Height = INIreadint(cfg, "misc", "game_height", GameResolution.Height); + GameColourDepth = INIreadint(cfg, "misc", "gamecolordepth", GameColourDepth); + LetterboxByDesign = INIreadint(cfg, "misc", "letterbox", 0) != 0; + + GfxDriverId = INIreadstring(cfg, "graphics", "driver", GfxDriverId); + GfxFilterId = INIreadstring(cfg, "graphics", "filter", GfxFilterId); + ScreenSize.Width = INIreadint(cfg, "graphics", "screen_width", ScreenSize.Width); + ScreenSize.Height = INIreadint(cfg, "graphics", "screen_height", ScreenSize.Height); + + parse_scaling_option(INIreadstring(cfg, "graphics", "game_scale_fs", make_scaling_option(FsGameFrame)), FsGameFrame); + parse_scaling_option(INIreadstring(cfg, "graphics", "game_scale_win", make_scaling_option(WinGameFrame)), WinGameFrame); + + RefreshRate = INIreadint(cfg, "graphics", "refresh", RefreshRate); + Windowed = INIreadint(cfg, "graphics", "windowed", Windowed ? 1 : 0) != 0; + VSync = INIreadint(cfg, "graphics", "vsync", VSync ? 1 : 0) != 0; + RenderAtScreenRes = INIreadint(cfg, "graphics", "render_at_screenres", RenderAtScreenRes ? 1 : 0) != 0; + + AntialiasSprites = INIreadint(cfg, "misc", "antialias", AntialiasSprites ? 1 : 0) != 0; + + DigiID = read_driverid(cfg, "sound", "digiid", DigiID); + MidiID = read_driverid(cfg, "sound", "midiid", MidiID); + ThreadedAudio = INIreadint(cfg, "sound", "threaded", ThreadedAudio ? 1 : 0) != 0; + UseVoicePack = INIreadint(cfg, "sound", "usespeech", UseVoicePack ? 1 : 0) != 0; + + MouseAutoLock = INIreadint(cfg, "mouse", "auto_lock", MouseAutoLock ? 1 : 0) != 0; + MouseSpeed = INIreadfloat(cfg, "mouse", "speed", 1.f); + if (MouseSpeed <= 0.f) + MouseSpeed = 1.f; + + SpriteCacheSize = INIreadint(cfg, "misc", "cachemax", SpriteCacheSize); + Language = INIreadstring(cfg, "language", "translation", Language); + DefaultLanguageName = INIreadstring(cfg, "language", "default_translation_name", DefaultLanguageName); + + Title = INIreadstring(cfg, "misc", "titletext", Title); +} + +void WinConfig::Save(ConfigTree &cfg) +{ + INIwritestring(cfg, "misc", "user_data_dir", UserSaveDir); + + INIwritestring(cfg, "graphics", "driver", GfxDriverId); + INIwritestring(cfg, "graphics", "filter", GfxFilterId); + INIwritestring(cfg, "graphics", "screen_def", Windowed ? "scaling" : "explicit"); + INIwriteint(cfg, "graphics", "screen_width", ScreenSize.Width); + INIwriteint(cfg, "graphics", "screen_height", ScreenSize.Height); + INIwritestring(cfg, "graphics", "game_scale_fs", make_scaling_option(FsGameFrame)); + INIwritestring(cfg, "graphics", "game_scale_win", make_scaling_option(WinGameFrame)); + INIwriteint(cfg, "graphics", "refresh", RefreshRate); + INIwriteint(cfg, "graphics", "windowed", Windowed ? 1 : 0); + INIwriteint(cfg, "graphics", "vsync", VSync ? 1 : 0); + INIwriteint(cfg, "graphics", "render_at_screenres", RenderAtScreenRes ? 1 : 0); + + INIwriteint(cfg, "misc", "antialias", AntialiasSprites ? 1 : 0); + + write_driverid(cfg, "sound", "digiid", DigiID); + write_driverid(cfg, "sound", "midiid", MidiID); + INIwriteint(cfg, "sound", "threaded", ThreadedAudio ? 1 : 0); + INIwriteint(cfg, "sound", "usespeech", UseVoicePack ? 1 : 0); + + INIwriteint(cfg, "mouse", "auto_lock", MouseAutoLock ? 1 : 0); + INIwritestring(cfg, "mouse", "speed", String::FromFormat("%0.1f", MouseSpeed)); + + INIwriteint(cfg, "misc", "cachemax", SpriteCacheSize); + INIwritestring(cfg, "language", "translation", Language); +} + + +//============================================================================= +// +// WinAPI interaction helpers +// +//============================================================================= + +int AddString(HWND hwnd, LPCTSTR text, DWORD_PTR data = 0L) +{ + int index = SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)text); + if (index >= 0) + SendMessage(hwnd, CB_SETITEMDATA, index, data); + return index; +} + +int InsertString(HWND hwnd, LPCTSTR text, int at_index, DWORD_PTR data = 0L) +{ + int index = SendMessage(hwnd, CB_INSERTSTRING, at_index, (LPARAM)text); + if (index >= 0) + SendMessage(hwnd, CB_SETITEMDATA, index, data); + return index; +} + +int GetItemCount(HWND hwnd) +{ + return SendMessage(hwnd, CB_GETCOUNT, 0, 0L); +} + +bool GetCheck(HWND hwnd) +{ + return SendMessage(hwnd, BM_GETCHECK, 0, 0) != FALSE; +} + +void SetCheck(HWND hwnd, bool check) +{ + SendMessage(hwnd, BM_SETCHECK, check ? BST_CHECKED : BST_UNCHECKED, 0); +} + +int GetCurSel(HWND hwnd) +{ + return SendMessage(hwnd, CB_GETCURSEL, 0, 0); +} + +void SetCurSel(HWND hwnd, int cur_sel) +{ + SendMessage(hwnd, CB_SETCURSEL, cur_sel, 0); +} + +typedef bool (*PfnCompareCBItemData)(DWORD_PTR data1, DWORD_PTR data2); + +bool CmpICBItemDataAsStr(DWORD_PTR data1, DWORD_PTR data2) +{ + LPCTSTR text_ptr1 = (LPCTSTR)data1; + LPCTSTR text_ptr2 = (LPCTSTR)data2; + return text_ptr1 && text_ptr2 && StrCmpI(text_ptr1, text_ptr2) == 0 || !text_ptr1 && !text_ptr2; +} + +int SetCurSelToItemData(HWND hwnd, DWORD_PTR data, PfnCompareCBItemData pfn_cmp = NULL, int def_sel = -1) +{ + int count = SendMessage(hwnd, CB_GETCOUNT, 0, 0); + for (int i = 0; i < count; ++i) + { + DWORD_PTR item_data = SendMessage(hwnd, CB_GETITEMDATA, i, 0); + if (pfn_cmp && pfn_cmp(item_data, data) || !pfn_cmp && item_data == data) + { + LRESULT res = SendMessage(hwnd, CB_SETCURSEL, i, 0); + if (res != CB_ERR) + return res; + break; + } + } + return SendMessage(hwnd, CB_SETCURSEL, def_sel, 0); +} + +int SetCurSelToItemDataStr(HWND hwnd, LPCTSTR text, int def_sel = -1) +{ + return SetCurSelToItemData(hwnd, (DWORD_PTR)text, CmpICBItemDataAsStr, def_sel); +} + +DWORD_PTR GetCurItemData(HWND hwnd, DWORD_PTR def_value = 0) +{ + int index = SendMessage(hwnd, CB_GETCURSEL, 0, 0); + if (index >= 0) + return SendMessage(hwnd, CB_GETITEMDATA, index, 0); + return def_value; +} + +String GetText(HWND hwnd) +{ + TCHAR short_buf[MAX_PATH + 1]; + int len = SendMessage(hwnd, WM_GETTEXTLENGTH, 0, 0); + if (len > 0) + { + TCHAR *buf = len >= sizeof(short_buf) ? new TCHAR[len + 1] : short_buf; + SendMessage(hwnd, WM_GETTEXT, len + 1, (LPARAM)buf); + String s = buf; + if (buf != short_buf) + delete [] buf; + return s; + } + return ""; +} + +void SetText(HWND hwnd, LPCTSTR text) +{ + SendMessage(hwnd, WM_SETTEXT, 0, (LPARAM)text); +} + +void ResetContent(HWND hwnd) +{ + SendMessage(hwnd, CB_RESETCONTENT, 0, 0); +} + +void SetSliderRange(HWND hwnd, int min, int max) +{ + SendMessage(hwnd, TBM_SETRANGE, (WPARAM)TRUE, (LPARAM)MAKELONG(min, max)); +} + +int GetSliderPos(HWND hwnd) +{ + return SendMessage(hwnd, TBM_GETPOS, 0, 0); +} + +void SetSliderPos(HWND hwnd, int pos) +{ + SendMessage(hwnd, TBM_SETPOS, (WPARAM)TRUE, (LPARAM)pos); +} + +void MakeFullLongPath(const char *path, char *out_buf, int buf_len) +{ + GetFullPathName(path, buf_len, out_buf, NULL); + GetLongPathName(out_buf, out_buf, buf_len); +} + + +//============================================================================= +// +// Browse-for-folder dialog +// +//============================================================================= + +int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +{ + if (uMsg == BFFM_INITIALIZED) + { + // Set initial selection + SendMessage(hwnd, BFFM_SETSELECTION, TRUE, (LPARAM)lpData); + } + return 0; +} + +bool BrowseForFolder(String &dir_buf) +{ + bool res = false; + CoInitialize(NULL); + + BROWSEINFO bi = { 0 }; + bi.lpszTitle = "Select location for game saves and custom data files"; + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_RETURNFSANCESTORS; + bi.lpfn = BrowseCallbackProc; + bi.lParam = (LPARAM)dir_buf.GetCStr(); + LPITEMIDLIST pidl = SHBrowseForFolder ( &bi ); + if (pidl) + { + char path[MAX_PATH]; + if (SHGetPathFromIDList(pidl, path) != FALSE) + { + dir_buf = path; + res = true; + } + CoTaskMemFree(pidl); + } + + CoUninitialize(); + return res; +} + + +//============================================================================= +// +// WinSetupDialog, handles the dialog UI. +// +//============================================================================= +class WinSetupDialog +{ +public: + enum GfxModeSpecial + { + kGfxMode_None = -1, + kGfxMode_Desktop = -2, + kGfxMode_GameRes = -3, + }; + + static const int MouseSpeedMin = 1; + static const int MouseSpeedMax = 100; + +public: + WinSetupDialog(const ConfigTree &cfg_in, ConfigTree &cfg_out, const String &data_dir, const String &version_str); + ~WinSetupDialog(); + static SetupReturnValue ShowModal(const ConfigTree &cfg_in, ConfigTree &cfg_out, + const String &data_dir, const String &version_str); + +private: + static INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); + + // Event handlers + INT_PTR OnInitDialog(HWND hwnd); + INT_PTR OnCommand(WORD id); + INT_PTR OnListSelection(WORD id); + void OnCustomSaveDirBtn(); + void OnCustomSaveDirCheck(); + void OnGfxDriverUpdate(); + void OnGfxFilterUpdate(); + void OnGfxModeUpdate(); + void OnScalingUpdate(HWND hlist, GameFrameSetup &frame_setup, bool windowed); + void OnWindowedUpdate(); + void ShowAdvancedOptions(); + + // Helper structs + typedef std::vector VDispModes; + // NOTE: we have to implement IGfxModeList for now because we are using + // few engine functions that take IGfxModeList as parameter + struct GfxModes : public IGfxModeList + { + VDispModes Modes; + + virtual int GetModeCount() const; + virtual bool GetMode(int index, DisplayMode &mode) const; + }; + + typedef std::vector VFilters; + struct DriverDesc + { + String Id; // internal id + String UserName; // human-friendly driver name + GfxModes GfxModeList; // list of supported modes + VFilters FilterList; // list of supported filters + int UseColorDepth; // recommended display depth + }; + + // Operations + void AddScalingString(HWND hlist, int scaling_factor); + void FillGfxFilterList(); + void FillGfxModeList(); + void FillLanguageList(); + void FillScalingList(HWND hlist, GameFrameSetup &frame_setup, bool windowed); + void InitGfxModes(); + void InitDriverDescFromFactory(const String &id); + void SaveSetup(); + void SelectNearestGfxMode(const Size screen_size); + void SetGfxModeText(); + void UpdateMouseSpeedText(); + + // Dialog singleton and properties + static WinSetupDialog *_dlg; + HWND _hwnd; + WinConfig _winCfg; + const ConfigTree &_cfgIn; + ConfigTree &_cfgOut; + // Window size + Size _winSize; + Size _baseSize; + // Driver descriptions + typedef std::shared_ptr PDriverDesc; + typedef std::map DriverDescMap; + DriverDescMap _drvDescMap; + PDriverDesc _drvDesc; + GfxFilterInfo _gfxFilterInfo; + // Resolution limits + Size _desktopSize; + Size _maxWindowSize; + Size _minGameSize; + int _maxGameScale = 0; + int _minGameScale = 0; + + // Dialog controls + HWND _hVersionText = NULL; + HWND _hCustomSaveDir = NULL; + HWND _hCustomSaveDirBtn = NULL; + HWND _hCustomSaveDirCheck = NULL; + HWND _hGfxDriverList = NULL; + HWND _hGfxModeList = NULL; + HWND _hGfxFilterList = NULL; + HWND _hFsScalingList = NULL; + HWND _hWinScalingList = NULL; + HWND _hDigiDriverList = NULL; + HWND _hMidiDriverList = NULL; + HWND _hLanguageList = NULL; + HWND _hSpriteCacheList = NULL; + HWND _hWindowed = NULL; + HWND _hVSync = NULL; + HWND _hRenderAtScreenRes = NULL; + HWND _hRefresh85Hz = NULL; + HWND _hAntialiasSprites = NULL; + HWND _hThreadedAudio = NULL; + HWND _hUseVoicePack = NULL; + HWND _hAdvanced = NULL; + HWND _hGameResolutionText = NULL; + HWND _hGfxModeText = NULL; + HWND _hMouseLock = NULL; + HWND _hMouseSpeed = NULL; + HWND _hMouseSpeedText = NULL; +}; + +WinSetupDialog *WinSetupDialog::_dlg = NULL; + +WinSetupDialog::WinSetupDialog(const ConfigTree &cfg_in, ConfigTree &cfg_out, const String &data_dir, const String &version_str) + : _hwnd(NULL) + , _cfgIn(cfg_in) + , _cfgOut(cfg_out) +{ + _winCfg.DataDirectory = data_dir; + _winCfg.VersionString = version_str; +} + +WinSetupDialog::~WinSetupDialog() +{ +} + +SetupReturnValue WinSetupDialog::ShowModal(const ConfigTree &cfg_in, ConfigTree &cfg_out, + const String &data_dir, const String &version_str) +{ + _dlg = new WinSetupDialog(cfg_in, cfg_out, data_dir, version_str); + INT_PTR dlg_res = DialogBoxParam(GetModuleHandle(NULL), (LPCTSTR)IDD_SETUP, win_get_window(), + (DLGPROC)WinSetupDialog::DialogProc, 0L); + delete _dlg; + _dlg = NULL; + + switch (dlg_res) + { + case IDOKRUN: return kSetup_RunGame; + case IDOK: return kSetup_Done; + default: return kSetup_Cancel; + } +} + +INT_PTR WinSetupDialog::OnInitDialog(HWND hwnd) +{ + _hwnd = hwnd; + _hVersionText = GetDlgItem(_hwnd, IDC_VERSION); + _hCustomSaveDir = GetDlgItem(_hwnd, IDC_CUSTOMSAVEDIR); + _hCustomSaveDirBtn = GetDlgItem(_hwnd, IDC_CUSTOMSAVEDIRBTN); + _hCustomSaveDirCheck = GetDlgItem(_hwnd, IDC_CUSTOMSAVEDIRCHECK); + _hGfxDriverList = GetDlgItem(_hwnd, IDC_GFXDRIVER); + _hGfxModeList = GetDlgItem(_hwnd, IDC_GFXMODE); + _hGfxFilterList = GetDlgItem(_hwnd, IDC_GFXFILTER); + _hFsScalingList = GetDlgItem(_hwnd, IDC_FSSCALING); + _hWinScalingList = GetDlgItem(_hwnd, IDC_WINDOWSCALING); + _hDigiDriverList = GetDlgItem(_hwnd, IDC_DIGISOUND); + _hMidiDriverList = GetDlgItem(_hwnd, IDC_MIDIMUSIC); + _hLanguageList = GetDlgItem(_hwnd, IDC_LANGUAGE); + _hSpriteCacheList = GetDlgItem(_hwnd, IDC_SPRITECACHE); + _hWindowed = GetDlgItem(_hwnd, IDC_WINDOWED); + _hVSync = GetDlgItem(_hwnd, IDC_VSYNC); + _hRenderAtScreenRes = GetDlgItem(_hwnd, IDC_RENDERATSCREENRES); + _hRefresh85Hz = GetDlgItem(_hwnd, IDC_REFRESH_85HZ); + _hAntialiasSprites = GetDlgItem(_hwnd, IDC_ANTIALIAS); + _hThreadedAudio = GetDlgItem(_hwnd, IDC_THREADEDAUDIO); + _hUseVoicePack = GetDlgItem(_hwnd, IDC_VOICEPACK); + _hAdvanced = GetDlgItem(_hwnd, IDC_ADVANCED); + _hGameResolutionText = GetDlgItem(_hwnd, IDC_RESOLUTION); + _hGfxModeText = GetDlgItem(_hwnd, IDC_GFXMODETEXT); + _hMouseLock = GetDlgItem(_hwnd, IDC_MOUSE_AUTOLOCK); + _hMouseSpeed = GetDlgItem(_hwnd, IDC_MOUSESPEED); + _hMouseSpeedText = GetDlgItem(_hwnd, IDC_MOUSESPEED_TEXT); + + _desktopSize = get_desktop_size(); + _maxWindowSize = _desktopSize; + AGSPlatformDriver::GetDriver()->ValidateWindowSize(_maxWindowSize.Width, _maxWindowSize.Height, false); + _minGameSize = Size(320, 200); + _maxGameScale = 1; + _minGameScale = 1; + + _winCfg.Load(_cfgIn); + + // Custom save dir controls + String custom_save_dir = _winCfg.UserSaveDir; + bool has_save_dir = !custom_save_dir.IsEmpty(); + if (!has_save_dir) + custom_save_dir = _winCfg.DataDirectory; + SetCheck(_hCustomSaveDirCheck, has_save_dir); + char full_save_dir[MAX_PATH] = {0}; + MakeFullLongPath(custom_save_dir, full_save_dir, MAX_PATH); + SetText(_hCustomSaveDir, full_save_dir); + EnableWindow(_hCustomSaveDir, has_save_dir ? TRUE : FALSE); + EnableWindow(_hCustomSaveDirBtn, has_save_dir ? TRUE : FALSE); + + // Resolution controls + if (_winCfg.GameResolution.IsNull() && + (_winCfg.GameResType == kGameResolution_Undefined || _winCfg.GameResType == kGameResolution_Custom) || + _winCfg.GameColourDepth == 0) + MessageBox(_hwnd, "Essential information about the game is missing in the configuration file. Setup program may be unable to deduce graphic modes properly.", "Initialization error", MB_OK | MB_ICONWARNING); + + if (_winCfg.GameResolution.IsNull()) + _winCfg.GameResolution = ResolutionTypeToSize(_winCfg.GameResType, _winCfg.LetterboxByDesign); + + SetText(_hwnd, _winCfg.Title); + SetText(win_get_window(), _winCfg.Title); + SetText(_hGameResolutionText, String::FromFormat("Native game resolution: %d x %d x %d", + _winCfg.GameResolution.Width, _winCfg.GameResolution.Height, _winCfg.GameColourDepth)); + + SetText(_hVersionText, _winCfg.VersionString); + + InitGfxModes(); + + for (DriverDescMap::const_iterator it = _drvDescMap.begin(); it != _drvDescMap.end(); ++it) + AddString(_hGfxDriverList, it->second->UserName, (DWORD_PTR)it->second->Id.GetCStr()); + SetCurSelToItemDataStr(_hGfxDriverList, _winCfg.GfxDriverId.GetCStr(), 0); + OnGfxDriverUpdate(); + + SetCheck(_hWindowed, _winCfg.Windowed); + OnWindowedUpdate(); + + FillScalingList(_hFsScalingList, _winCfg.FsGameFrame, false); + FillScalingList(_hWinScalingList, _winCfg.WinGameFrame, true); + + SetCheck(_hVSync, _winCfg.VSync); + + SetCheck(_hRenderAtScreenRes, _winCfg.RenderAtScreenRes); + + AddString(_hDigiDriverList, "No Digital Sound", DIGI_NONE); + AddString(_hDigiDriverList, "Default device (auto)", MIDI_AUTODETECT); + AddString(_hDigiDriverList, "Default DirectSound Device", DIGI_DIRECTAMX(0)); + AddString(_hDigiDriverList, "Default WaveOut Device", DIGI_WAVOUTID(0)); + AddString(_hDigiDriverList, "DirectSound (Hardware mixer)", DIGI_DIRECTX(0)); + SetCurSelToItemData(_hDigiDriverList, _winCfg.DigiID); + + AddString(_hMidiDriverList, "No MIDI music", MIDI_NONE); + AddString(_hMidiDriverList, "Default device (auto)", MIDI_AUTODETECT); + AddString(_hMidiDriverList, "Win32 MIDI Mapper", MIDI_WIN32MAPPER); + SetCurSelToItemData(_hMidiDriverList, _winCfg.MidiID); + + FillLanguageList(); + + SetCheck(_hMouseLock, _winCfg.MouseAutoLock); + + SetSliderRange(_hMouseSpeed, MouseSpeedMin, MouseSpeedMax); + int slider_pos = (int)(_winCfg.MouseSpeed * 10.f + .5f); + SetSliderPos(_hMouseSpeed, slider_pos); + UpdateMouseSpeedText(); + + AddString(_hSpriteCacheList, "16 MB", 16); + AddString(_hSpriteCacheList, "32 MB", 32); + AddString(_hSpriteCacheList, "64 MB", 64); + AddString(_hSpriteCacheList, "128 MB (default)", 128); + AddString(_hSpriteCacheList, "256 MB", 256); + AddString(_hSpriteCacheList, "384 MB", 384); + AddString(_hSpriteCacheList, "512 MB", 512); + SetCurSelToItemData(_hSpriteCacheList, _winCfg.SpriteCacheSize / 1024, NULL, 3); + + SetCheck(_hRefresh85Hz, _winCfg.RefreshRate == 85); + SetCheck(_hAntialiasSprites, _winCfg.AntialiasSprites); + SetCheck(_hThreadedAudio, _winCfg.ThreadedAudio); + SetCheck(_hUseVoicePack, _winCfg.UseVoicePack); + if (!File::TestReadFile("speech.vox")) + EnableWindow(_hUseVoicePack, FALSE); + + if (INIreadint(_cfgIn, "disabled", "threaded_audio", 0) != 0) + EnableWindow(_hThreadedAudio, FALSE); + if (INIreadint(_cfgIn, "disabled", "speechvox", 0) != 0) + EnableWindow(_hUseVoicePack, FALSE); + if (INIreadint(_cfgIn, "disabled", "filters", 0) != 0) + EnableWindow(_hGfxFilterList, FALSE); + if (INIreadint(_cfgIn, "disabled", "render_at_screenres", 0) != 0) + EnableWindow(_hRenderAtScreenRes, FALSE); + + RECT win_rect, gfx_rect, adv_rect, border; + GetWindowRect(_hwnd, &win_rect); + GetWindowRect(GetDlgItem(_hwnd, IDC_GFXOPTIONS), &gfx_rect); + _winSize.Width = win_rect.right - win_rect.left; + _winSize.Height = win_rect.bottom - win_rect.top; + GetWindowRect(_hAdvanced, &adv_rect); + border.left = border.top = border.right = border.bottom = 9; + MapDialogRect(_hwnd, &border); + _baseSize.Width = (adv_rect.right + (gfx_rect.left - win_rect.left)) - win_rect.left; + _baseSize.Height = adv_rect.bottom - win_rect.top + border.bottom; + + MoveWindow(_hwnd, max(0, win_rect.left + (_winSize.Width - _baseSize.Width) / 2), + max(0, win_rect.top + (_winSize.Height - _baseSize.Height) / 2), + _baseSize.Width, _baseSize.Height, TRUE); + SetFocus(GetDlgItem(_hwnd, IDOK)); + return FALSE; // notify WinAPI that we set focus ourselves +} + +INT_PTR WinSetupDialog::OnCommand(WORD id) +{ + switch (id) + { + case IDC_ADVANCED: ShowAdvancedOptions(); break; + case IDC_WINDOWED: OnWindowedUpdate(); break; + case IDC_CUSTOMSAVEDIRBTN: OnCustomSaveDirBtn(); break; + case IDC_CUSTOMSAVEDIRCHECK: OnCustomSaveDirCheck(); break; + case IDOK: + case IDOKRUN: + SaveSetup(); + // fall-through intended + case IDCANCEL: + EndDialog(_hwnd, id); + return TRUE; + default: + return FALSE; + } + return TRUE; +} + +INT_PTR WinSetupDialog::OnListSelection(WORD id) +{ + switch (id) + { + case IDC_GFXDRIVER: OnGfxDriverUpdate(); break; + case IDC_GFXFILTER: OnGfxFilterUpdate(); break; + case IDC_GFXMODE: OnGfxModeUpdate(); break; + case IDC_FSSCALING: OnScalingUpdate(_hFsScalingList, _winCfg.FsGameFrame, false); break; + case IDC_WINDOWSCALING: OnScalingUpdate(_hWinScalingList, _winCfg.WinGameFrame, true); break; + default: + return FALSE; + } + return TRUE; +} + +void WinSetupDialog::OnCustomSaveDirBtn() +{ + String save_dir = GetText(_hCustomSaveDir); + if (BrowseForFolder(save_dir)) + { + SetText(_hCustomSaveDir, save_dir); + } +} + +void WinSetupDialog::OnCustomSaveDirCheck() +{ + bool custom_save_dir = GetCheck(_hCustomSaveDirCheck); + EnableWindow(_hCustomSaveDir, custom_save_dir ? TRUE : FALSE); + EnableWindow(_hCustomSaveDirBtn, custom_save_dir ? TRUE : FALSE); +} + +void WinSetupDialog::OnGfxDriverUpdate() +{ + _winCfg.GfxDriverId = (LPCTSTR)GetCurItemData(_hGfxDriverList); + + DriverDescMap::const_iterator it = _drvDescMap.find(_winCfg.GfxDriverId); + if (it != _drvDescMap.end()) + _drvDesc = it->second; + else + _drvDesc.reset(); + + FillGfxModeList(); + FillGfxFilterList(); +} + +void WinSetupDialog::OnGfxFilterUpdate() +{ + _winCfg.GfxFilterId = (LPCTSTR)GetCurItemData(_hGfxFilterList); + + _gfxFilterInfo = GfxFilterInfo(); + for (size_t i = 0; i < _drvDesc->FilterList.size(); ++i) + { + if (_drvDesc->FilterList[i].Id.CompareNoCase(_winCfg.GfxFilterId) == 0) + { + _gfxFilterInfo = _drvDesc->FilterList[i]; + break; + } + } +} + +void WinSetupDialog::OnGfxModeUpdate() +{ + DWORD_PTR sel = GetCurItemData(_hGfxModeList); + if (sel == kGfxMode_Desktop) + _winCfg.ScreenSize = _desktopSize; + else if (sel == kGfxMode_GameRes) + _winCfg.ScreenSize = _winCfg.GameResolution; + else + { + const DisplayMode &mode = _drvDesc->GfxModeList.Modes[sel]; + _winCfg.ScreenSize = Size(mode.Width, mode.Height); + } +} + +void WinSetupDialog::OnScalingUpdate(HWND hlist, GameFrameSetup &frame_setup, bool windowed) +{ + int scale = GetCurItemData(hlist); + if (scale >= 0 && scale < kNumFrameScaleDef) + { + frame_setup.ScaleDef = (FrameScaleDefinition)scale; + frame_setup.ScaleFactor = 0; + } + else + { + frame_setup.ScaleDef = kFrame_IntScale; + frame_setup.ScaleFactor = scale >= 0 ? scale - kNumFrameScaleDef : scale; + } + + if (windowed) + SetGfxModeText(); +} + +void WinSetupDialog::OnWindowedUpdate() +{ + _winCfg.Windowed = GetCheck(_hWindowed); + + if (_winCfg.Windowed) + { + ShowWindow(_hGfxModeList, SW_HIDE); + ShowWindow(_hGfxModeText, SW_SHOW); + SetGfxModeText(); + } + else + { + ShowWindow(_hGfxModeList, SW_SHOW); + ShowWindow(_hGfxModeText, SW_HIDE); + } + + SelectNearestGfxMode(_winCfg.ScreenSize); +} + +void WinSetupDialog::ShowAdvancedOptions() +{ + // Reveal the advanced bit of the window + ShowWindow(_hAdvanced, SW_HIDE); + + RECT win_rect; + GetWindowRect(_hwnd, &win_rect); + MoveWindow(_hwnd, max(0, win_rect.left + (_baseSize.Width - _winSize.Width) / 2), + max(0, win_rect.top + (_baseSize.Height - _winSize.Height) / 2), + _winSize.Width, _winSize.Height, TRUE); + + int offset = _winSize.Height - _baseSize.Height; + RECT rc; + int ctrl_ids[] = { IDC_VERSION, IDOK, IDOKRUN, IDCANCEL, 0 }; + for (int i = 0; ctrl_ids[i]; ++i) + { + HWND hctrl = GetDlgItem(_hwnd, ctrl_ids[i]); + GetWindowRect(hctrl, &rc); + ScreenToClient(_hwnd, (POINT*)&rc); + ScreenToClient(_hwnd, (POINT*)&rc.right); + MoveWindow(hctrl, rc.left, rc.top + offset, rc.right - rc.left, rc.bottom - rc.top, TRUE); + } +} + +INT_PTR CALLBACK WinSetupDialog::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + _ASSERT(_dlg != NULL && _dlg->_hwnd == NULL); + return _dlg->OnInitDialog(hwndDlg); + case WM_COMMAND: + _ASSERT(_dlg != NULL && _dlg->_hwnd != NULL); + if (HIWORD(wParam) == CBN_SELCHANGE) + return _dlg->OnListSelection(LOWORD(wParam)); + return _dlg->OnCommand(LOWORD(wParam)); + case WM_HSCROLL: + _ASSERT(_dlg != NULL && _dlg->_hwnd != NULL); + _dlg->UpdateMouseSpeedText(); + return TRUE; + default: + return FALSE; + } +} + +int WinSetupDialog::GfxModes::GetModeCount() const +{ + return Modes.size(); +} + +bool WinSetupDialog::GfxModes::GetMode(int index, DisplayMode &mode) const +{ + if (index >= 0 && (size_t)index < Modes.size()) + { + mode = Modes[index]; + return true; + } + return false; +} + +void WinSetupDialog::AddScalingString(HWND hlist, int scaling_factor) +{ + String s; + if (scaling_factor >= 0) + s = String::FromFormat("x%d", scaling_factor); + else + s = String::FromFormat("1/%d", -scaling_factor); + AddString(hlist, s, (DWORD_PTR)(scaling_factor >= 0 ? scaling_factor + kNumFrameScaleDef : scaling_factor)); +} + +void WinSetupDialog::FillGfxFilterList() +{ + ResetContent(_hGfxFilterList); + + if (!_drvDesc) + { + _gfxFilterInfo = GfxFilterInfo(); + return; + } + + for (size_t i = 0; i < _drvDesc->FilterList.size(); ++i) + { + const GfxFilterInfo &info = _drvDesc->FilterList[i]; + if (INIreadint(_cfgIn, "disabled", info.Id, 0) == 0) + AddString(_hGfxFilterList, info.Name, (DWORD_PTR)info.Id.GetCStr()); + } + + SetCurSelToItemDataStr(_hGfxFilterList, _winCfg.GfxFilterId, 0); + OnGfxFilterUpdate(); +} + +void WinSetupDialog::FillGfxModeList() +{ + ResetContent(_hGfxModeList); + + if (!_drvDesc) + { + OnGfxModeUpdate(); + return; + } + + const VDispModes &modes = _drvDesc->GfxModeList.Modes; + bool has_desktop_mode = false; + bool has_native_mode = false; + String buf; + for (VDispModes::const_iterator mode = modes.begin(); mode != modes.end(); ++mode) + { + if (mode->Width == _desktopSize.Width && mode->Height == _desktopSize.Height) + { + has_desktop_mode = true; + continue; + } + else if (mode->Width == _winCfg.GameResolution.Width && mode->Height == _winCfg.GameResolution.Height) + { + has_native_mode = true; + continue; + } + buf.Format("%d x %d", mode->Width, mode->Height); + AddString(_hGfxModeList, buf, (DWORD_PTR)(mode - modes.begin())); + } + + int spec_mode_idx = 0; + if (has_desktop_mode) + InsertString(_hGfxModeList, String::FromFormat("Desktop resolution (%d x %d)", + _desktopSize.Width, _desktopSize.Height), spec_mode_idx++, (DWORD_PTR)kGfxMode_Desktop); + if (has_native_mode) + InsertString(_hGfxModeList, String::FromFormat("Native game resolution (%d x %d)", + _winCfg.GameResolution.Width, _winCfg.GameResolution.Height), spec_mode_idx++, (DWORD_PTR)kGfxMode_GameRes); + + SelectNearestGfxMode(_winCfg.ScreenSize); +} + +void WinSetupDialog::FillLanguageList() +{ + ResetContent(_hLanguageList); + AddString(_hLanguageList, _winCfg.DefaultLanguageName.GetCStr()); + SetCurSel(_hLanguageList, 0); + + String path_mask = String::FromFormat("%s\\*.tra", _winCfg.DataDirectory.GetCStr()); + WIN32_FIND_DATAA file_data; + HANDLE find_handle = FindFirstFile(path_mask, &file_data); + if (find_handle != INVALID_HANDLE_VALUE) + { + bool found_sel = false; + do + { + LPTSTR ext = PathFindExtension(file_data.cFileName); + if (ext && StrCmpI(ext, ".tra") == 0) + { + file_data.cFileName[0] = toupper(file_data.cFileName[0]); + *ext = 0; + int index = AddString(_hLanguageList, file_data.cFileName); + if (!found_sel && _winCfg.Language.CompareNoCase(file_data.cFileName) == 0) + { + SetCurSel(_hLanguageList, index); + found_sel = true; + } + } + } + while (FindNextFileA(find_handle, &file_data) != FALSE); + FindClose(find_handle); + } +} + +void WinSetupDialog::FillScalingList(HWND hlist, GameFrameSetup &frame_setup, bool windowed) +{ + ResetContent(hlist); + + const int min_scale = min(_winCfg.GameResolution.Width / _minGameSize.Width, _winCfg.GameResolution.Height / _minGameSize.Height); + const Size max_size = windowed ? _maxWindowSize : _winCfg.ScreenSize; + const int max_scale = _winCfg.GameResolution.IsNull() ? 1 : + min(max_size.Width / _winCfg.GameResolution.Width, max_size.Height / _winCfg.GameResolution.Height); + _maxGameScale = max(1, max_scale); + _minGameScale = -max(1, min_scale); + + if (windowed) + AddString(hlist, "None (original game size)", 1 + kNumFrameScaleDef); + + AddString(hlist, "Max round multiplier", kFrame_MaxRound); + AddString(hlist, "Fill whole screen", kFrame_MaxStretch); + AddString(hlist, "Stretch, preserving aspect ratio", kFrame_MaxProportional); + + if (windowed && !_winCfg.GameResolution.IsNull()) + { + // Add integer multipliers + for (int scale = 2; scale <= _maxGameScale; ++scale) + AddScalingString(hlist, scale); + } + + SetCurSelToItemData(hlist, + frame_setup.ScaleDef == kFrame_IntScale ? frame_setup.ScaleFactor + kNumFrameScaleDef : frame_setup.ScaleDef, NULL, 0); + + EnableWindow(hlist, SendMessage(hlist, CB_GETCOUNT, 0, 0) > 1 ? TRUE : FALSE); + OnScalingUpdate(hlist, frame_setup, windowed); +} + +void WinSetupDialog::InitGfxModes() +{ + InitDriverDescFromFactory("D3D9"); + InitDriverDescFromFactory("OGL"); + InitDriverDescFromFactory("Software"); + + if (_drvDescMap.size() == 0) + MessageBox(_hwnd, "Unable to detect any supported graphic drivers!", "Initialization error", MB_OK | MB_ICONERROR); +} + +// "Less" predicate that compares two display modes only by their screen metrics +bool SizeLess(const DisplayMode &first, const DisplayMode &second) +{ + return Size(first.Width, first.Height) < Size(second.Width, second.Height); +} + +void WinSetupDialog::InitDriverDescFromFactory(const String &id) +{ + IGfxDriverFactory *gfx_factory = GetGfxDriverFactory(id); + if (!gfx_factory) + return; + IGraphicsDriver *gfx_driver = gfx_factory->GetDriver(); + if (!gfx_driver) + { + gfx_factory->Shutdown(); + return; + } + + PDriverDesc drv_desc(new DriverDesc()); + drv_desc->Id = gfx_driver->GetDriverID(); + drv_desc->UserName = gfx_driver->GetDriverName(); + drv_desc->UseColorDepth = + gfx_driver->GetDisplayDepthForNativeDepth(_winCfg.GameColourDepth ? _winCfg.GameColourDepth : 32); + + IGfxModeList *gfxm_list = gfx_driver->GetSupportedModeList(drv_desc->UseColorDepth); + VDispModes &modes = drv_desc->GfxModeList.Modes; + if (gfxm_list) + { + std::set unique_sizes; // trying to hide modes which only have different refresh rates + for (int i = 0; i < gfxm_list->GetModeCount(); ++i) + { + DisplayMode mode; + gfxm_list->GetMode(i, mode); + if (mode.ColorDepth != drv_desc->UseColorDepth || unique_sizes.count(Size(mode.Width, mode.Height)) != 0) + continue; + unique_sizes.insert(Size(mode.Width, mode.Height)); + modes.push_back(mode); + } + std::sort(modes.begin(), modes.end(), SizeLess); + delete gfxm_list; + } + if (modes.size() == 0) + { + // Add two default modes in hope that engine will be able to handle them (or fallbacks to something else) + modes.push_back(DisplayMode(GraphicResolution(_desktopSize.Width, _desktopSize.Height, drv_desc->UseColorDepth))); + modes.push_back(DisplayMode(GraphicResolution(_winCfg.GameResolution.Width, _winCfg.GameResolution.Height, drv_desc->UseColorDepth))); + } + + drv_desc->FilterList.resize(gfx_factory->GetFilterCount()); + for (size_t i = 0; i < drv_desc->FilterList.size(); ++i) + { + drv_desc->FilterList[i] = *gfx_factory->GetFilterInfo(i); + } + + gfx_factory->Shutdown(); + _drvDescMap[drv_desc->Id] = drv_desc; +} + +void WinSetupDialog::SaveSetup() +{ + const bool custom_save_dir = GetCheck(_hCustomSaveDirCheck); + if (custom_save_dir) + { + // Compare user path with the game data directory. If user chose + // path pointing inside game's directory, then store relative + // path instead; thus the path will keep pointing at game's + // directory if user moves game elsewhere. + String save_dir; + save_dir = GetText(_hCustomSaveDir); + char full_data_dir[MAX_PATH] = {0}; + char full_save_dir[MAX_PATH] = {0}; + MakeFullLongPath(_winCfg.DataDirectory, full_data_dir, MAX_PATH); + MakeFullLongPath(save_dir, full_save_dir, MAX_PATH); + char rel_save_dir[MAX_PATH] = {0}; + if (PathRelativePathTo(rel_save_dir, full_data_dir, FILE_ATTRIBUTE_DIRECTORY, full_save_dir, FILE_ATTRIBUTE_DIRECTORY) && + strstr(rel_save_dir, "..") == NULL) + { + _winCfg.UserSaveDir = rel_save_dir; + } + else + { + _winCfg.UserSaveDir = save_dir; + } + } + else + { + _winCfg.UserSaveDir = ""; + } + + _winCfg.DigiID = GetCurItemData(_hDigiDriverList); + _winCfg.MidiID = GetCurItemData(_hMidiDriverList); + + if (GetCurSel(_hLanguageList) == 0) + _winCfg.Language.Empty(); + else + _winCfg.Language = GetText(_hLanguageList); + _winCfg.SpriteCacheSize = GetCurItemData(_hSpriteCacheList) * 1024; + _winCfg.ThreadedAudio = GetCheck(_hThreadedAudio); + _winCfg.UseVoicePack = GetCheck(_hUseVoicePack); + _winCfg.VSync = GetCheck(_hVSync); + _winCfg.RenderAtScreenRes = GetCheck(_hRenderAtScreenRes); + _winCfg.AntialiasSprites = GetCheck(_hAntialiasSprites); + _winCfg.RefreshRate = GetCheck(_hRefresh85Hz) ? 85 : 0; + _winCfg.GfxFilterId = (LPCTSTR)GetCurItemData(_hGfxFilterList); + + _winCfg.MouseAutoLock = GetCheck(_hMouseLock); + int slider_pos = GetSliderPos(_hMouseSpeed); + _winCfg.MouseSpeed = (float)slider_pos / 10.f; + + _winCfg.Save(_cfgOut); +} + +void WinSetupDialog::SelectNearestGfxMode(const Size screen_size) +{ + if (!_drvDesc) + { + OnGfxModeUpdate(); + return; + } + + // First check two special modes + if (screen_size == _desktopSize) + { + SetCurSelToItemData(_hGfxModeList, kGfxMode_Desktop); + } + else if (screen_size == _winCfg.GameResolution) + { + SetCurSelToItemData(_hGfxModeList, kGfxMode_GameRes); + } + else + { + // Look up for the nearest supported mode + int index = -1; + DisplayMode dm; + if (find_nearest_supported_mode(_drvDesc->GfxModeList, screen_size, _drvDesc->UseColorDepth, + NULL, NULL, dm, &index)) + { + SetCurSelToItemData(_hGfxModeList, index, NULL, kGfxMode_Desktop); + } + else + SetCurSelToItemData(_hGfxModeList, kGfxMode_Desktop); + } + OnGfxModeUpdate(); +} + +void WinSetupDialog::SetGfxModeText() +{ + Size sz; + const GameFrameSetup &frame_setup = _winCfg.WinGameFrame; + if (frame_setup.ScaleDef == kFrame_MaxStretch) + { + sz = _maxWindowSize; + } + else if (frame_setup.ScaleDef == kFrame_MaxProportional) + { + sz = ProportionalStretch(_maxWindowSize, _winCfg.GameResolution); + } + else + { + int scale = 0; + if (frame_setup.ScaleDef == kFrame_MaxRound) + scale = _maxGameScale; + else + scale = frame_setup.ScaleFactor; + + if (scale >= 0) + { + sz.Width = _winCfg.GameResolution.Width * scale; + sz.Height = _winCfg.GameResolution.Height * scale; + } + else + { + sz.Width = _winCfg.GameResolution.Width / (-scale); + sz.Height = _winCfg.GameResolution.Height / (-scale); + } + } + String text = String::FromFormat("%d x %d", sz.Width, sz.Height); + SetText(_hGfxModeText, text); +} + +void WinSetupDialog::UpdateMouseSpeedText() +{ + int slider_pos = GetSliderPos(_hMouseSpeed); + float mouse_speed = (float)slider_pos / 10.f; + String text = mouse_speed == 1.f ? "Mouse speed: x 1.0 (Default)" : String::FromFormat("Mouse speed: x %0.1f", mouse_speed); + SetText(_hMouseSpeedText, text); +} + +//============================================================================= +// +// Windows setup entry point. +// +//============================================================================= +void SetWinIcon() +{ + SetClassLong(win_get_window(),GCL_HICON, + (LONG) LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDI_ICON))); +} + +SetupReturnValue WinSetup(const ConfigTree &cfg_in, ConfigTree &cfg_out, + const String &game_data_dir, const String &version_str) +{ + return WinSetupDialog::ShowModal(cfg_in, cfg_out, game_data_dir, version_str); +} + +} // namespace Engine +} // namespace AGS + +#endif // AGS_PLATFORM_OS_WINDOWS \ No newline at end of file diff --git a/engines/ags/engine/platform/windows/setup/winsetup.h b/engines/ags/engine/platform/windows/setup/winsetup.h new file mode 100644 index 00000000000..e030cf33b92 --- /dev/null +++ b/engines/ags/engine/platform/windows/setup/winsetup.h @@ -0,0 +1,38 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Built-in setup dialog for Windows version +// +//============================================================================= + +#ifndef __AGS_EE_SETUP__WINSETUP_H +#define __AGS_EE_SETUP__WINSETUP_H + +#include "util/ini_util.h" + +namespace AGS +{ +namespace Engine +{ + +using namespace Common; + +void SetWinIcon(); +SetupReturnValue WinSetup(const ConfigTree &cfg_in, ConfigTree &cfg_out, + const String &game_data_dir, const String &version_str); + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_SETUP__WINSETUP_H diff --git a/engines/ags/engine/platform/windows/win_ex_handling.cpp b/engines/ags/engine/platform/windows/win_ex_handling.cpp new file mode 100644 index 00000000000..27d56c67fe0 --- /dev/null +++ b/engines/ags/engine/platform/windows/win_ex_handling.cpp @@ -0,0 +1,97 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include "core/platform.h" + +#if AGS_PLATFORM_OS_WINDOWS +#include +#include +#include +#include "ac/common.h" +#include "ac/common_defines.h" +#include "debug/debugger.h" +#include "debug/out.h" +#include "main/main.h" +#include "util/ini_util.h" + +#if !AGS_PLATFORM_DEBUG +#define USE_CUSTOM_EXCEPTION_HANDLER +#endif + +using namespace AGS::Common; + +extern int our_eip; +extern int eip_guinum; +extern int eip_guiobj; +extern int proper_exit; + +char tempmsg[100]; +char *printfworkingspace; + +#ifdef USE_CUSTOM_EXCEPTION_HANDLER +void CreateMiniDump(EXCEPTION_POINTERS *pep); + +extern int CustomExceptionHandler(LPEXCEPTION_POINTERS exinfo); +extern EXCEPTION_RECORD excinfo; +extern int miniDumpResultCode; + +static void DisplayException() +{ + String script_callstack = get_cur_script(5); + sprintf(printfworkingspace, "An exception 0x%X occurred in ACWIN.EXE at EIP = 0x%08X; program pointer is %+d, ACI version %s, gtags (%d,%d)\n\n" + "AGS cannot continue, this exception was fatal. Please note down the numbers above, remember what you were doing at the time and post the details on the AGS Technical Forum.\n\n%s\n\n" + "Most versions of Windows allow you to press Ctrl+C now to copy this entire message to the clipboard for easy reporting.\n\n%s (code %d)", + excinfo.ExceptionCode, (intptr_t)excinfo.ExceptionAddress, our_eip, EngineVersion.LongString.GetCStr(), eip_guinum, eip_guiobj, script_callstack.GetCStr(), + (miniDumpResultCode == 0) ? "An error file CrashInfo.dmp has been created. You may be asked to upload this file when reporting this problem on the AGS Forums." : + "Unable to create an error dump file.", miniDumpResultCode); + MessageBoxA(win_get_window(), printfworkingspace, "Illegal exception", MB_ICONSTOP | MB_OK); +} + +int initialize_engine_with_exception_handling( + int (initialize_engine)(const AGS::Common::ConfigTree &startup_opts), + const ConfigTree &startup_opts) +{ + __try + { + Debug::Printf(kDbgMsg_Info, "Installing exception handler"); + return initialize_engine(startup_opts); + } + __except (CustomExceptionHandler(GetExceptionInformation())) + { + DisplayException(); + proper_exit = 1; + } + return EXIT_CRASH; +} +#endif // USE_CUSTOM_EXCEPTION_HANDLER + + +int malloc_fail_handler(size_t amountwanted) +{ +#ifdef USE_CUSTOM_EXCEPTION_HANDLER + CreateMiniDump(NULL); +#endif + free(printfworkingspace); + sprintf(tempmsg, "Out of memory: failed to allocate %ld bytes (at PP=%d)", amountwanted, our_eip); + quit(tempmsg); + return 0; +} + +void setup_malloc_handling() +{ + _set_new_handler(malloc_fail_handler); + _set_new_mode(1); + printfworkingspace = (char*)malloc(7000); +} + +#endif // AGS_PLATFORM_OS_WINDOWS diff --git a/engines/ags/engine/platform/windows/win_ex_handling.h b/engines/ags/engine/platform/windows/win_ex_handling.h new file mode 100644 index 00000000000..30f458945d2 --- /dev/null +++ b/engines/ags/engine/platform/windows/win_ex_handling.h @@ -0,0 +1,24 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __AGS_EE_PLATFORM__WIN_EXCEPTION_HANDLING_H +#define __AGS_EE_PLATFORM__WIN_EXCEPTION_HANDLING_H + +#include "util/ini_util.h" + +void setup_malloc_handling(); +int initialize_engine_with_exception_handling( + int (initialize_engine)(const AGS::Common::ConfigTree &startup_opts), + const AGS::Common::ConfigTree &startup_opts); + +#endif // __AGS_EE_PLATFORM__WIN_EXCEPTION_HANDLING_H diff --git a/engines/ags/engine/platform/windows/winapi_exclusive.h b/engines/ags/engine/platform/windows/winapi_exclusive.h new file mode 100644 index 00000000000..e82c4175f9d --- /dev/null +++ b/engines/ags/engine/platform/windows/winapi_exclusive.h @@ -0,0 +1,51 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// An excerpt from , declaring most commonly used WinAPI types +// and attributes. Meant to avoid including itself when it may +// cause naming conflicts with its dreaded heap of macros. +// +//============================================================================= +#ifndef __AGS_EE_PLATFORM__WINAPI_EXCLUSIVE_H +#define __AGS_EE_PLATFORM__WINAPI_EXCLUSIVE_H + +#ifndef _WINDOWS_ // do not include if windows.h was included first +#define _WINDOWS_ // there can be only one + +typedef unsigned long DWORD; +typedef int BOOL; +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef char CHAR; +#define CONST const +typedef CONST CHAR *LPCSTR, *PCSTR; + +#define DECLSPEC_IMPORT __declspec(dllimport) +#define WINBASEAPI DECLSPEC_IMPORT +#define WINAPI __stdcall + +typedef void *PVOID; +typedef PVOID HANDLE; +typedef HANDLE HINSTANCE; +typedef HANDLE HMODULE; +typedef HANDLE HWND; + +#define FAR +#define NEAR + +typedef int (FAR WINAPI *FARPROC)(); + +#endif // _WINDOWS_ + +#endif // __AGS_EE_PLATFORM__WINAPI_EXCLUSIVE_H diff --git a/engines/ags/engine/plugin/agsplugin.cpp b/engines/ags/engine/plugin/agsplugin.cpp new file mode 100644 index 00000000000..240f0d14fcf --- /dev/null +++ b/engines/ags/engine/plugin/agsplugin.cpp @@ -0,0 +1,1109 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "core/platform.h" +#if AGS_PLATFORM_OS_WINDOWS +#include "platform/windows/winapi_exclusive.h" +#endif +#include "util/wgt2allg.h" +#include "ac/common.h" +#include "ac/view.h" +#include "ac/charactercache.h" +#include "ac/display.h" +#include "ac/draw.h" +#include "ac/dynamicsprite.h" +#include "ac/gamesetup.h" +#include "ac/gamesetupstruct.h" +#include "ac/global_audio.h" +#include "ac/global_plugin.h" +#include "ac/global_walkablearea.h" +#include "ac/keycode.h" +#include "ac/mouse.h" +#include "ac/movelist.h" +#include "ac/objectcache.h" +#include "ac/parser.h" +#include "ac/path_helper.h" +#include "ac/roomstatus.h" +#include "ac/string.h" +#include "ac/dynobj/cc_dynamicobject_addr_and_manager.h" +#include "font/fonts.h" +#include "util/string_compat.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" +#include "device/mousew32.h" +#include "gui/guidefines.h" +#include "main/game_run.h" +#include "main/engine.h" +#include "plugin/agsplugin.h" +#include "plugin/plugin_engine.h" +#include "plugin/plugin_builtin.h" +#include "plugin/pluginobjectreader.h" +#include "script/script.h" +#include "script/script_runtime.h" +#include "ac/spritecache.h" +#include "util/stream.h" +#include "gfx/bitmap.h" +#include "gfx/graphicsdriver.h" +#include "gfx/gfxfilter.h" +#include "script/runtimescriptvalue.h" +#include "debug/out.h" +#include "ac/dynobj/scriptstring.h" +#include "main/graphics_mode.h" +#include "gfx/gfx_util.h" +#include "util/memory.h" +#include "util/filestream.h" +#include "media/audio/audio_system.h" + +using namespace AGS::Common; +using namespace AGS::Common::Memory; +using namespace AGS::Engine; + + +#if defined(BUILTIN_PLUGINS) +#include "../Plugins/AGSflashlight/agsflashlight.h" +#include "../Plugins/agsblend/agsblend.h" +#include "../Plugins/ags_snowrain/ags_snowrain.h" +#include "../Plugins/ags_parallax/ags_parallax.h" +#include "../Plugins/agspalrender/agspalrender.h" +#if AGS_PLATFORM_OS_IOS +#include "../Plugins/agstouch/agstouch.h" +#endif // AGS_PLATFORM_OS_IOS +#endif // BUILTIN_PLUGINS + + +extern IGraphicsDriver *gfxDriver; +extern int mousex, mousey; +extern int displayed_room; +extern RoomStruct thisroom; +extern GameSetupStruct game; +extern RoomStatus*croom; +extern SpriteCache spriteset; +extern ViewStruct*views; +extern int game_paused; +extern GameSetup usetup; +extern int inside_script; +extern ccInstance *gameinst, *roominst; +extern CharacterCache *charcache; +extern ObjectCache objcache[MAX_ROOM_OBJECTS]; +extern MoveList *mls; +extern color palette[256]; +extern PluginObjectReader pluginReaders[MAX_PLUGIN_OBJECT_READERS]; +extern int numPluginReaders; +extern RuntimeScriptValue GlobalReturnValue; +extern ScriptString myScriptStringImpl; + +// **************** PLUGIN IMPLEMENTATION **************** + + +#include "util/library.h" + + + + +struct EnginePlugin { + char filename[PLUGIN_FILENAME_MAX+1]; + AGS::Engine::Library library; + bool available; + char *savedata; + int savedatasize; + int wantHook; + int invalidatedRegion; + void (*engineStartup) (IAGSEngine *) = nullptr; + void (*engineShutdown) () = nullptr; + int (*onEvent) (int, int) = nullptr; + void (*initGfxHook) (const char *driverName, void *data) = nullptr; + int (*debugHook) (const char * whichscript, int lineNumber, int reserved) = nullptr; + IAGSEngine eiface; + bool builtin; + + EnginePlugin() { + filename[0] = 0; + wantHook = 0; + invalidatedRegion = 0; + savedata = nullptr; + savedatasize = 0; + builtin = false; + available = false; + eiface.version = 0; + eiface.pluginId = 0; + } +}; +#define MAXPLUGINS 20 +EnginePlugin plugins[MAXPLUGINS]; +int numPlugins = 0; +int pluginsWantingDebugHooks = 0; + +std::vector _registered_builtin_plugins; + +void IAGSEngine::AbortGame (const char *reason) { + quit ((char*)reason); +} +const char* IAGSEngine::GetEngineVersion () { + return get_engine_version(); +} +void IAGSEngine::RegisterScriptFunction (const char*name, void*addy) { + ccAddExternalPluginFunction (name, addy); +} +const char* IAGSEngine::GetGraphicsDriverID() +{ + if (gfxDriver == nullptr) + return nullptr; + + return gfxDriver->GetDriverID(); +} + +BITMAP *IAGSEngine::GetScreen () +{ + // TODO: we could actually return stage buffer here, will that make a difference? + if (!gfxDriver->UsesMemoryBackBuffer()) + quit("!This plugin requires software graphics driver."); + + Bitmap *buffer = gfxDriver->GetMemoryBackBuffer(); + return buffer ? (BITMAP*)buffer->GetAllegroBitmap() : nullptr; +} + +BITMAP *IAGSEngine::GetVirtualScreen () +{ + Bitmap *stage = gfxDriver->GetStageBackBuffer(); + return stage ? (BITMAP*)stage->GetAllegroBitmap() : nullptr; +} + +void IAGSEngine::RequestEventHook (int32 event) { + if (event >= AGSE_TOOHIGH) + quit("!IAGSEngine::RequestEventHook: invalid event requested"); + + if (plugins[this->pluginId].onEvent == nullptr) + quit("!IAGSEngine::RequestEventHook: no callback AGS_EngineOnEvent function exported from plugin"); + + if ((event & AGSE_SCRIPTDEBUG) && + ((plugins[this->pluginId].wantHook & AGSE_SCRIPTDEBUG) == 0)) { + pluginsWantingDebugHooks++; + ccSetDebugHook(scriptDebugHook); + } + + if (event & AGSE_AUDIODECODE) { + quit("Plugin requested AUDIODECODE, which is no longer supported"); + } + + + plugins[this->pluginId].wantHook |= event; +} + +void IAGSEngine::UnrequestEventHook(int32 event) { + if (event >= AGSE_TOOHIGH) + quit("!IAGSEngine::UnrequestEventHook: invalid event requested"); + + if ((event & AGSE_SCRIPTDEBUG) && + (plugins[this->pluginId].wantHook & AGSE_SCRIPTDEBUG)) { + pluginsWantingDebugHooks--; + if (pluginsWantingDebugHooks < 1) + ccSetDebugHook(nullptr); + } + + plugins[this->pluginId].wantHook &= ~event; +} + +int IAGSEngine::GetSavedData (char *buffer, int32 bufsize) { + int savedatasize = plugins[this->pluginId].savedatasize; + + if (bufsize < savedatasize) + quit("!IAGSEngine::GetSavedData: buffer too small"); + + if (savedatasize > 0) + memcpy (buffer, plugins[this->pluginId].savedata, savedatasize); + + return savedatasize; +} + +void IAGSEngine::DrawText (int32 x, int32 y, int32 font, int32 color, char *text) +{ + Bitmap *ds = gfxDriver->GetStageBackBuffer(); + if (!ds) + return; + color_t text_color = ds->GetCompatibleColor(color); + draw_and_invalidate_text(ds, x, y, font, text_color, text); +} + +void IAGSEngine::GetScreenDimensions (int32 *width, int32 *height, int32 *coldepth) { + if (width != nullptr) + width[0] = play.GetMainViewport().GetWidth(); + if (height != nullptr) + height[0] = play.GetMainViewport().GetHeight(); + if (coldepth != nullptr) + coldepth[0] = scsystem.coldepth; +} + +unsigned char ** IAGSEngine::GetRawBitmapSurface (BITMAP *bmp) +{ + if (!is_linear_bitmap(bmp)) + quit("!IAGSEngine::GetRawBitmapSurface: invalid bitmap for access to surface"); + acquire_bitmap(bmp); + + Bitmap *stage = gfxDriver->GetStageBackBuffer(); + if (stage && bmp == stage->GetAllegroBitmap()) + plugins[this->pluginId].invalidatedRegion = 0; + + return bmp->line; +} + +void IAGSEngine::ReleaseBitmapSurface (BITMAP *bmp) +{ + release_bitmap (bmp); + + Bitmap *stage = gfxDriver->GetStageBackBuffer(); + if (stage && bmp == stage->GetAllegroBitmap()) + { + // plugin does not manaually invalidate stuff, so + // we must invalidate the whole screen to be safe + if (!plugins[this->pluginId].invalidatedRegion) + invalidate_screen(); + } +} + +void IAGSEngine::GetMousePosition (int32 *x, int32 *y) { + if (x) x[0] = mousex; + if (y) y[0] = mousey; +} +int IAGSEngine::GetCurrentRoom () { + return displayed_room; +} +int IAGSEngine::GetNumBackgrounds () { + return thisroom.BgFrameCount; +} +int IAGSEngine::GetCurrentBackground () { + return play.bg_frame; +} +BITMAP *IAGSEngine::GetBackgroundScene (int32 index) { + return (BITMAP*)thisroom.BgFrames[index].Graphic->GetAllegroBitmap(); +} +void IAGSEngine::GetBitmapDimensions (BITMAP *bmp, int32 *width, int32 *height, int32 *coldepth) { + if (bmp == nullptr) + return; + + if (width != nullptr) + width[0] = bmp->w; + if (height != nullptr) + height[0] = bmp->h; + if (coldepth != nullptr) + coldepth[0] = bitmap_color_depth(bmp); +} + +// On save/restore, the Engine will provide the plugin with a handle. Because we only ever save to one file at a time, +// we can reuse the same handle. + +static long pl_file_handle = -1; +static Stream *pl_file_stream = nullptr; + +void pl_set_file_handle(long data, Stream *stream) { + pl_file_handle = data; + pl_file_stream = stream; +} + +void pl_clear_file_handle() { + pl_file_handle = -1; + pl_file_stream = nullptr; +} + +int IAGSEngine::FRead (void *buffer, int32 len, int32 handle) { + if (handle != pl_file_handle) { + quitprintf("IAGSEngine::FRead: invalid file handle: %d", handle); + } + if (!pl_file_stream) { + quit("IAGSEngine::FRead: file stream not set"); + } + return pl_file_stream->Read(buffer, len); +} + +int IAGSEngine::FWrite (void *buffer, int32 len, int32 handle) { + if (handle != pl_file_handle) { + quitprintf("IAGSEngine::FWrite: invalid file handle: %d", handle); + } + if (!pl_file_stream) { + quit("IAGSEngine::FWrite: file stream not set"); + } + return pl_file_stream->Write(buffer, len); +} + +void IAGSEngine::DrawTextWrapped (int32 xx, int32 yy, int32 wid, int32 font, int32 color, const char*text) +{ + // TODO: use generic function from the engine instead of having copy&pasted code here + int linespacing = getfontspacing_outlined(font); + + if (break_up_text_into_lines(text, Lines, wid, font) == 0) + return; + + Bitmap *ds = gfxDriver->GetStageBackBuffer(); + if (!ds) + return; + color_t text_color = ds->GetCompatibleColor(color); + data_to_game_coords((int*)&xx, (int*)&yy); // stupid! quick tweak + for (size_t i = 0; i < Lines.Count(); i++) + draw_and_invalidate_text(ds, xx, yy + linespacing*i, font, text_color, Lines[i]); +} + +Bitmap glVirtualScreenWrap; +void IAGSEngine::SetVirtualScreen (BITMAP *bmp) +{ + if (!gfxDriver->UsesMemoryBackBuffer()) + { + debug_script_warn("SetVirtualScreen: this plugin requires software graphics driver to work correctly."); + // we let it continue since gfxDriver is supposed to ignore this request without throwing an exception + } + + if (bmp) + { + glVirtualScreenWrap.WrapAllegroBitmap(bmp, true); + gfxDriver->SetMemoryBackBuffer(&glVirtualScreenWrap); + } + else + { + glVirtualScreenWrap.Destroy(); + gfxDriver->SetMemoryBackBuffer(nullptr); + } +} + +int IAGSEngine::LookupParserWord (const char *word) { + return find_word_in_dictionary ((char*)word); +} + +void IAGSEngine::BlitBitmap (int32 x, int32 y, BITMAP *bmp, int32 masked) +{ + Bitmap *ds = gfxDriver->GetStageBackBuffer(); + if (!ds) + return; + wputblock_raw(ds, x, y, bmp, masked); + invalidate_rect(x, y, x + bmp->w, y + bmp->h, false); +} + +void IAGSEngine::BlitSpriteTranslucent(int32 x, int32 y, BITMAP *bmp, int32 trans) +{ + Bitmap *ds = gfxDriver->GetStageBackBuffer(); + if (!ds) + return; + Bitmap wrap(bmp, true); + if (gfxDriver->UsesMemoryBackBuffer()) + GfxUtil::DrawSpriteWithTransparency(ds, &wrap, x, y, trans); + else + GfxUtil::DrawSpriteBlend(ds, Point(x,y), &wrap, kBlendMode_Alpha, true, false, trans); +} + +void IAGSEngine::BlitSpriteRotated(int32 x, int32 y, BITMAP *bmp, int32 angle) +{ + Bitmap *ds = gfxDriver->GetStageBackBuffer(); + if (!ds) + return; + // FIXME: call corresponding Graphics Blit + rotate_sprite(ds->GetAllegroBitmap(), bmp, x, y, itofix(angle)); +} + +extern void domouse(int); + +void IAGSEngine::PollSystem () { + + domouse(DOMOUSE_NOCURSOR); + update_polled_stuff_if_runtime(); + int mbut, mwheelz; + if (run_service_mb_controls(mbut, mwheelz) && mbut >= 0 && !play.IsIgnoringInput()) + pl_run_plugin_hooks (AGSE_MOUSECLICK, mbut); + int kp; + if (run_service_key_controls(kp) && !play.IsIgnoringInput()) { + pl_run_plugin_hooks (AGSE_KEYPRESS, kp); + } + +} +AGSCharacter* IAGSEngine::GetCharacter (int32 charnum) { + if (charnum >= game.numcharacters) + quit("!AGSEngine::GetCharacter: invalid character request"); + + return (AGSCharacter*)&game.chars[charnum]; +} +AGSGameOptions* IAGSEngine::GetGameOptions () { + return (AGSGameOptions*)&play; +} +AGSColor* IAGSEngine::GetPalette () { + return (AGSColor*)&palette[0]; +} +void IAGSEngine::SetPalette (int32 start, int32 finish, AGSColor *cpl) { + set_palette_range((color*)cpl, start, finish, 0); +} +int IAGSEngine::GetNumCharacters () { + return game.numcharacters; +} +int IAGSEngine::GetPlayerCharacter () { + return game.playercharacter; +} +void IAGSEngine::RoomToViewport (int32 *x, int32 *y) { + Point scrp = play.RoomToScreen(x ? data_to_game_coord(*x) : 0, y ? data_to_game_coord(*y) : 0); + if (x) + *x = scrp.X; + if (y) + *y = scrp.Y; +} +void IAGSEngine::ViewportToRoom (int32 *x, int32 *y) { + // NOTE: This is an old function that did not account for custom/multiple viewports + // and does not expect to fail, therefore we always use primary viewport here. + // (Not sure if it's good though) + VpPoint vpt = play.ScreenToRoom(x ? game_to_data_coord(*x) : 0, y ? game_to_data_coord(*y) : 0); + if (x) + *x = vpt.first.X; + if (y) + *y = vpt.first.Y; +} +int IAGSEngine::GetNumObjects () { + return croom->numobj; +} +AGSObject *IAGSEngine::GetObject (int32 num) { + if (num >= croom->numobj) + quit("!IAGSEngine::GetObject: invalid object"); + + return (AGSObject*)&croom->obj[num]; +} +BITMAP *IAGSEngine::CreateBlankBitmap (int32 width, int32 height, int32 coldep) { + // [IKM] We should not create Bitmap object here, because + // a) we are returning raw allegro bitmap and therefore loosing control over it + // b) plugin won't use Bitmap anyway + BITMAP *tempb = create_bitmap_ex(coldep, width, height); + clear_to_color(tempb, bitmap_mask_color(tempb)); + return tempb; +} +void IAGSEngine::FreeBitmap (BITMAP *tofree) { + if (tofree) + destroy_bitmap (tofree); +} +BITMAP *IAGSEngine::GetSpriteGraphic (int32 num) { + return (BITMAP*)spriteset[num]->GetAllegroBitmap(); +} +BITMAP *IAGSEngine::GetRoomMask (int32 index) { + if (index == MASK_WALKABLE) + return (BITMAP*)thisroom.WalkAreaMask->GetAllegroBitmap(); + else if (index == MASK_WALKBEHIND) + return (BITMAP*)thisroom.WalkBehindMask->GetAllegroBitmap(); + else if (index == MASK_HOTSPOT) + return (BITMAP*)thisroom.HotspotMask->GetAllegroBitmap(); + else if (index == MASK_REGIONS) + return (BITMAP*)thisroom.RegionMask->GetAllegroBitmap(); + else + quit("!IAGSEngine::GetRoomMask: invalid mask requested"); + return nullptr; +} +AGSViewFrame *IAGSEngine::GetViewFrame (int32 view, int32 loop, int32 frame) { + view--; + if ((view < 0) || (view >= game.numviews)) + quit("!IAGSEngine::GetViewFrame: invalid view"); + if ((loop < 0) || (loop >= views[view].numLoops)) + quit("!IAGSEngine::GetViewFrame: invalid loop"); + if ((frame < 0) || (frame >= views[view].loops[loop].numFrames)) + return nullptr; + + return (AGSViewFrame*)&views[view].loops[loop].frames[frame]; +} + +int IAGSEngine::GetRawPixelColor(int32 color) +{ + // Convert the standardized colour to the local gfx mode color + // NOTE: it is unclear whether this has to be game colour depth or display color depth. + // there was no difference in the original engine, but there is now. + int result; + __my_setcolor(&result, color, game.GetColorDepth()); + return result; +} + +int IAGSEngine::GetWalkbehindBaseline (int32 wa) { + if ((wa < 1) || (wa >= MAX_WALK_BEHINDS)) + quit("!IAGSEngine::GetWalkBehindBase: invalid walk-behind area specified"); + return croom->walkbehind_base[wa]; +} +void* IAGSEngine::GetScriptFunctionAddress (const char *funcName) { + return ccGetSymbolAddressForPlugin ((char*)funcName); +} +int IAGSEngine::GetBitmapTransparentColor(BITMAP *bmp) { + return bitmap_mask_color (bmp); +} +// get the character scaling level at a particular point +int IAGSEngine::GetAreaScaling (int32 x, int32 y) { + return GetScalingAt(x,y); +} +int IAGSEngine::IsGamePaused () { + return game_paused; +} +int IAGSEngine::GetSpriteWidth (int32 slot) { + return game.SpriteInfos[slot].Width; +} +int IAGSEngine::GetSpriteHeight (int32 slot) { + return game.SpriteInfos[slot].Height; +} +void IAGSEngine::GetTextExtent (int32 font, const char *text, int32 *width, int32 *height) { + if ((font < 0) || (font >= game.numfonts)) { + if (width != nullptr) width[0] = 0; + if (height != nullptr) height[0] = 0; + return; + } + + if (width != nullptr) + width[0] = wgettextwidth_compensate (text, font); + if (height != nullptr) + height[0] = wgettextheight ((char*)text, font); +} +void IAGSEngine::PrintDebugConsole (const char *text) { + debug_script_log("[PLUGIN] %s", text); + platform->WriteStdOut("[PLUGIN] %s", text); +} +int IAGSEngine::IsChannelPlaying (int32 channel) { + return ::IsChannelPlaying (channel); +} +void IAGSEngine::PlaySoundChannel (int32 channel, int32 soundType, int32 volume, int32 loop, const char *filename) { + stop_and_destroy_channel (channel); + // Not sure if it's right to let it play on *any* channel, but this is plugin so let it go... + // we must correctly stop background voice speech if it takes over speech chan + if (channel == SCHAN_SPEECH && play.IsNonBlockingVoiceSpeech()) + stop_voice_nonblocking(); + + SOUNDCLIP *newcha = nullptr; + + if (((soundType == PSND_MP3STREAM) || (soundType == PSND_OGGSTREAM)) + && (loop != 0)) + quit("IAGSEngine::PlaySoundChannel: streamed samples cannot loop"); + + // TODO: find out how engine was supposed to decide on where to load the sound from + AssetPath asset_name("", filename); + + if (soundType == PSND_WAVE) + newcha = my_load_wave (asset_name, volume, loop); + else if (soundType == PSND_MP3STREAM) + newcha = my_load_mp3 (asset_name, volume); + else if (soundType == PSND_OGGSTREAM) + newcha = my_load_ogg (asset_name, volume); + else if (soundType == PSND_MP3STATIC) + newcha = my_load_static_mp3 (asset_name, volume, (loop != 0)); + else if (soundType == PSND_OGGSTATIC) + newcha = my_load_static_ogg (asset_name, volume, (loop != 0)); + else if (soundType == PSND_MIDI) { + if (midi_pos >= 0) + quit("!IAGSEngine::PlaySoundChannel: MIDI already in use"); + newcha = my_load_midi (asset_name, loop); + newcha->set_volume (volume); + } +#ifndef PSP_NO_MOD_PLAYBACK + else if (soundType == PSND_MOD) { + newcha = my_load_mod (asset_name, loop); + newcha->set_volume (volume); + } +#endif + else + quit("!IAGSEngine::PlaySoundChannel: unknown sound type"); + + set_clip_to_channel(channel, newcha); +} +// Engine interface 12 and above are below +void IAGSEngine::MarkRegionDirty(int32 left, int32 top, int32 right, int32 bottom) { + invalidate_rect(left, top, right, bottom, false); + plugins[this->pluginId].invalidatedRegion++; +} +AGSMouseCursor * IAGSEngine::GetMouseCursor(int32 cursor) { + if ((cursor < 0) || (cursor >= game.numcursors)) + return nullptr; + + return (AGSMouseCursor*)&game.mcurs[cursor]; +} +void IAGSEngine::GetRawColorComponents(int32 coldepth, int32 color, int32 *red, int32 *green, int32 *blue, int32 *alpha) { + if (red) + *red = getr_depth(coldepth, color); + if (green) + *green = getg_depth(coldepth, color); + if (blue) + *blue = getb_depth(coldepth, color); + if (alpha) + *alpha = geta_depth(coldepth, color); +} +int IAGSEngine::MakeRawColorPixel(int32 coldepth, int32 red, int32 green, int32 blue, int32 alpha) { + return makeacol_depth(coldepth, red, green, blue, alpha); +} +int IAGSEngine::GetFontType(int32 fontNum) { + if ((fontNum < 0) || (fontNum >= game.numfonts)) + return FNT_INVALID; + + if (font_supports_extended_characters(fontNum)) + return FNT_TTF; + + return FNT_SCI; +} +int IAGSEngine::CreateDynamicSprite(int32 coldepth, int32 width, int32 height) { + + // TODO: why is this implemented right here, should not an existing + // script handling implementation be called instead? + + int gotSlot = spriteset.GetFreeIndex(); + if (gotSlot <= 0) + return 0; + + if ((width < 1) || (height < 1)) + quit("!IAGSEngine::CreateDynamicSprite: invalid width/height requested by plugin"); + + // resize the sprite to the requested size + Bitmap *newPic = BitmapHelper::CreateTransparentBitmap(width, height, coldepth); + if (newPic == nullptr) + return 0; + + // add it into the sprite set + add_dynamic_sprite(gotSlot, newPic); + return gotSlot; +} +void IAGSEngine::DeleteDynamicSprite(int32 slot) { + free_dynamic_sprite(slot); +} +int IAGSEngine::IsSpriteAlphaBlended(int32 slot) { + if (game.SpriteInfos[slot].Flags & SPF_ALPHACHANNEL) + return 1; + return 0; +} + +// disable AGS's sound engine +void IAGSEngine::DisableSound() { + shutdown_sound(); + usetup.digicard = DIGI_NONE; + usetup.midicard = MIDI_NONE; + reserve_voices(0, 0); + install_sound(DIGI_NONE, MIDI_NONE, nullptr); +} +int IAGSEngine::CanRunScriptFunctionNow() { + if (inside_script) + return 0; + return 1; +} +int IAGSEngine::CallGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1, long arg2, long arg3) { + if (inside_script) + return -300; + + ccInstance *toRun = GetScriptInstanceByType(globalScript ? kScInstGame : kScInstRoom); + + RuntimeScriptValue params[3]; + params[0].SetPluginArgument(arg1); + params[1].SetPluginArgument(arg2); + params[2].SetPluginArgument(arg3); + int toret = RunScriptFunctionIfExists(toRun, (char*)name, numArgs, params); + return toret; +} + +void IAGSEngine::NotifySpriteUpdated(int32 slot) { + int ff; + // wipe the character cache when we change rooms + for (ff = 0; ff < game.numcharacters; ff++) { + if ((charcache[ff].inUse) && (charcache[ff].sppic == slot)) { + delete charcache[ff].image; + charcache[ff].image = nullptr; + charcache[ff].inUse = 0; + } + } + + // clear the object cache + for (ff = 0; ff < MAX_ROOM_OBJECTS; ff++) { + if ((objcache[ff].image != nullptr) && (objcache[ff].sppic == slot)) { + delete objcache[ff].image; + objcache[ff].image = nullptr; + } + } +} + +void IAGSEngine::SetSpriteAlphaBlended(int32 slot, int32 isAlphaBlended) { + + game.SpriteInfos[slot].Flags &= ~SPF_ALPHACHANNEL; + + if (isAlphaBlended) + game.SpriteInfos[slot].Flags |= SPF_ALPHACHANNEL; +} + +void IAGSEngine::QueueGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1, long arg2) { + if (!inside_script) { + this->CallGameScriptFunction(name, globalScript, numArgs, arg1, arg2, 0); + return; + } + + if (numArgs < 0 || numArgs > 2) + quit("IAGSEngine::QueueGameScriptFunction: invalid number of arguments"); + + curscript->run_another(name, globalScript ? kScInstGame : kScInstRoom, numArgs, + RuntimeScriptValue().SetPluginArgument(arg1), RuntimeScriptValue().SetPluginArgument(arg2)); +} + +int IAGSEngine::RegisterManagedObject(const void *object, IAGSScriptManagedObject *callback) { + GlobalReturnValue.SetPluginObject((void*)object, (ICCDynamicObject*)callback); + return ccRegisterManagedObject(object, (ICCDynamicObject*)callback, true); +} + +void IAGSEngine::AddManagedObjectReader(const char *typeName, IAGSManagedObjectReader *reader) { + if (numPluginReaders >= MAX_PLUGIN_OBJECT_READERS) + quit("Plugin error: IAGSEngine::AddObjectReader: Too many object readers added"); + + if ((typeName == nullptr) || (typeName[0] == 0)) + quit("Plugin error: IAGSEngine::AddObjectReader: invalid name for type"); + + for (int ii = 0; ii < numPluginReaders; ii++) { + if (strcmp(pluginReaders[ii].type, typeName) == 0) + quitprintf("Plugin error: IAGSEngine::AddObjectReader: type '%s' has been registered already", typeName); + } + + pluginReaders[numPluginReaders].reader = reader; + pluginReaders[numPluginReaders].type = typeName; + numPluginReaders++; +} + +void IAGSEngine::RegisterUnserializedObject(int key, const void *object, IAGSScriptManagedObject *callback) { + GlobalReturnValue.SetPluginObject((void*)object, (ICCDynamicObject*)callback); + ccRegisterUnserializedObject(key, object, (ICCDynamicObject*)callback, true); +} + +int IAGSEngine::GetManagedObjectKeyByAddress(const char *address) { + return ccGetObjectHandleFromAddress(address); +} + +void* IAGSEngine::GetManagedObjectAddressByKey(int key) { + void *object; + ICCDynamicObject *manager; + ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(key, object, manager); + if (obj_type == kScValPluginObject) + { + GlobalReturnValue.SetPluginObject(object, manager); + } + else + { + GlobalReturnValue.SetDynamicObject(object, manager); + } + return object; +} + +const char* IAGSEngine::CreateScriptString(const char *fromText) { + const char *string = CreateNewScriptString(fromText); + // Should be still standard dynamic object, because not managed by plugin + GlobalReturnValue.SetDynamicObject((void*)string, &myScriptStringImpl); + return string; +} + +int IAGSEngine::IncrementManagedObjectRefCount(const char *address) { + return ccAddObjectReference(GetManagedObjectKeyByAddress(address)); +} + +int IAGSEngine::DecrementManagedObjectRefCount(const char *address) { + return ccReleaseObjectReference(GetManagedObjectKeyByAddress(address)); +} + +void IAGSEngine::SetMousePosition(int32 x, int32 y) { + Mouse::SetPosition(Point(x, y)); + RefreshMouse(); +} + +void IAGSEngine::SimulateMouseClick(int32 button) { + PluginSimulateMouseClick(button); +} + +int IAGSEngine::GetMovementPathWaypointCount(int32 pathId) { + return mls[pathId % TURNING_AROUND].numstage; +} + +int IAGSEngine::GetMovementPathLastWaypoint(int32 pathId) { + return mls[pathId % TURNING_AROUND].onstage; +} + +void IAGSEngine::GetMovementPathWaypointLocation(int32 pathId, int32 waypoint, int32 *x, int32 *y) { + *x = (mls[pathId % TURNING_AROUND].pos[waypoint] >> 16) & 0x0000ffff; + *y = (mls[pathId % TURNING_AROUND].pos[waypoint] & 0x0000ffff); +} + +void IAGSEngine::GetMovementPathWaypointSpeed(int32 pathId, int32 waypoint, int32 *xSpeed, int32 *ySpeed) { + *xSpeed = mls[pathId % TURNING_AROUND].xpermove[waypoint]; + *ySpeed = mls[pathId % TURNING_AROUND].ypermove[waypoint]; +} + +int IAGSEngine::IsRunningUnderDebugger() +{ + return (editor_debugging_enabled != 0) ? 1 : 0; +} + +void IAGSEngine::GetPathToFileInCompiledFolder(const char*fileName, char *buffer) +{ + get_install_dir_path(buffer, fileName); +} + +void IAGSEngine::BreakIntoDebugger() +{ + break_on_next_script_step = 1; +} + +IAGSFontRenderer* IAGSEngine::ReplaceFontRenderer(int fontNumber, IAGSFontRenderer *newRenderer) +{ + return font_replace_renderer(fontNumber, newRenderer); +} + + +// *********** General plugin implementation ********** + +void pl_stop_plugins() { + int a; + ccSetDebugHook(nullptr); + + for (a = 0; a < numPlugins; a++) { + if (plugins[a].available) { + if (plugins[a].engineShutdown != nullptr) + plugins[a].engineShutdown(); + plugins[a].wantHook = 0; + if (plugins[a].savedata) { + free(plugins[a].savedata); + plugins[a].savedata = nullptr; + } + if (!plugins[a].builtin) { + plugins[a].library.Unload(); + } + } + } + numPlugins = 0; +} + +void pl_startup_plugins() { + int i; + for (i = 0; i < numPlugins; i++) { + if (plugins[i].available) + plugins[i].engineStartup (&plugins[i].eiface); + } +} + +int pl_run_plugin_hooks (int event, int data) { + int i, retval = 0; + for (i = 0; i < numPlugins; i++) { + if (plugins[i].wantHook & event) { + retval = plugins[i].onEvent (event, data); + if (retval) + return retval; + } + } + return 0; +} + +int pl_run_plugin_debug_hooks (const char *scriptfile, int linenum) { + int i, retval = 0; + for (i = 0; i < numPlugins; i++) { + if (plugins[i].wantHook & AGSE_SCRIPTDEBUG) { + retval = plugins[i].debugHook(scriptfile, linenum, 0); + if (retval) + return retval; + } + } + return 0; +} + +void pl_run_plugin_init_gfx_hooks (const char *driverName, void *data) { + for (int i = 0; i < numPlugins; i++) + { + if (plugins[i].initGfxHook != nullptr) + { + plugins[i].initGfxHook(driverName, data); + } + } +} + +int pl_register_builtin_plugin(InbuiltPluginDetails const &details) { + _registered_builtin_plugins.push_back(details); + return 0; +} + +bool pl_use_builtin_plugin(EnginePlugin* apl) +{ +#if defined(BUILTIN_PLUGINS) + if (ags_stricmp(apl->filename, "agsflashlight") == 0) + { + apl->engineStartup = agsflashlight::AGS_EngineStartup; + apl->engineShutdown = agsflashlight::AGS_EngineShutdown; + apl->onEvent = agsflashlight::AGS_EngineOnEvent; + apl->debugHook = agsflashlight::AGS_EngineDebugHook; + apl->initGfxHook = agsflashlight::AGS_EngineInitGfx; + apl->available = true; + apl->builtin = true; + return true; + } + else if (ags_stricmp(apl->filename, "agsblend") == 0) + { + apl->engineStartup = agsblend::AGS_EngineStartup; + apl->engineShutdown = agsblend::AGS_EngineShutdown; + apl->onEvent = agsblend::AGS_EngineOnEvent; + apl->debugHook = agsblend::AGS_EngineDebugHook; + apl->initGfxHook = agsblend::AGS_EngineInitGfx; + apl->available = true; + apl->builtin = true; + return true; + } + else if (ags_stricmp(apl->filename, "ags_snowrain") == 0) + { + apl->engineStartup = ags_snowrain::AGS_EngineStartup; + apl->engineShutdown = ags_snowrain::AGS_EngineShutdown; + apl->onEvent = ags_snowrain::AGS_EngineOnEvent; + apl->debugHook = ags_snowrain::AGS_EngineDebugHook; + apl->initGfxHook = ags_snowrain::AGS_EngineInitGfx; + apl->available = true; + apl->builtin = true; + return true; + } + else if (ags_stricmp(apl->filename, "ags_parallax") == 0) + { + apl->engineStartup = ags_parallax::AGS_EngineStartup; + apl->engineShutdown = ags_parallax::AGS_EngineShutdown; + apl->onEvent = ags_parallax::AGS_EngineOnEvent; + apl->debugHook = ags_parallax::AGS_EngineDebugHook; + apl->initGfxHook = ags_parallax::AGS_EngineInitGfx; + apl->available = true; + apl->builtin = true; + return true; + } + else if (ags_stricmp(apl->filename, "agspalrender") == 0) + { + apl->engineStartup = agspalrender::AGS_EngineStartup; + apl->engineShutdown = agspalrender::AGS_EngineShutdown; + apl->onEvent = agspalrender::AGS_EngineOnEvent; + apl->debugHook = agspalrender::AGS_EngineDebugHook; + apl->initGfxHook = agspalrender::AGS_EngineInitGfx; + apl->available = true; + apl->builtin = true; + return true; + } +#if AGS_PLATFORM_OS_IOS + else if (ags_stricmp(apl->filename, "agstouch") == 0) + { + apl->engineStartup = agstouch::AGS_EngineStartup; + apl->engineShutdown = agstouch::AGS_EngineShutdown; + apl->onEvent = agstouch::AGS_EngineOnEvent; + apl->debugHook = agstouch::AGS_EngineDebugHook; + apl->initGfxHook = agstouch::AGS_EngineInitGfx; + apl->available = true; + apl->builtin = true; + return true; + } +#endif // IOS_VERSION +#endif // BUILTIN_PLUGINS + + for(std::vector::iterator it = _registered_builtin_plugins.begin(); it != _registered_builtin_plugins.end(); ++it) { + if (ags_stricmp(apl->filename, it->filename) == 0) { + apl->engineStartup = it->engineStartup; + apl->engineShutdown = it->engineShutdown; + apl->onEvent = it->onEvent; + apl->debugHook = it->debugHook; + apl->initGfxHook = it->initGfxHook; + apl->available = true; + apl->builtin = true; + return true; + } + } + return false; +} + +Engine::GameInitError pl_register_plugins(const std::vector &infos) +{ + numPlugins = 0; + for (size_t inf_index = 0; inf_index < infos.size(); ++inf_index) + { + const Common::PluginInfo &info = infos[inf_index]; + String name = info.Name; + if (name.GetLast() == '!') + continue; // editor-only plugin, ignore it + if (numPlugins == MAXPLUGINS) + return kGameInitErr_TooManyPlugins; + // AGS Editor currently saves plugin names in game data with + // ".dll" extension appended; we need to take care of that + const String name_ext = ".dll"; + if (name.GetLength() <= name_ext.GetLength() || name.GetLength() > PLUGIN_FILENAME_MAX + name_ext.GetLength() || + name.CompareRightNoCase(name_ext, name_ext.GetLength())) { + return kGameInitErr_PluginNameInvalid; + } + // remove ".dll" from plugin's name + name.ClipRight(name_ext.GetLength()); + + EnginePlugin *apl = &plugins[numPlugins++]; + // Copy plugin info + snprintf(apl->filename, sizeof(apl->filename), "%s", name.GetCStr()); + if (info.DataLen) + { + apl->savedata = (char*)malloc(info.DataLen); + memcpy(apl->savedata, info.Data.get(), info.DataLen); + } + apl->savedatasize = info.DataLen; + + // Compatibility with the old SnowRain module + if (ags_stricmp(apl->filename, "ags_SnowRain20") == 0) { + strcpy(apl->filename, "ags_snowrain"); + } + + String expect_filename = apl->library.GetFilenameForLib(apl->filename); + if (apl->library.Load(apl->filename)) + { + AGS::Common::Debug::Printf(kDbgMsg_Info, "Plugin '%s' loaded as '%s', resolving imports...", apl->filename, expect_filename.GetCStr()); + + if (apl->library.GetFunctionAddress("AGS_PluginV2") == nullptr) { + quitprintf("Plugin '%s' is an old incompatible version.", apl->filename); + } + apl->engineStartup = (void(*)(IAGSEngine*))apl->library.GetFunctionAddress("AGS_EngineStartup"); + apl->engineShutdown = (void(*)())apl->library.GetFunctionAddress("AGS_EngineShutdown"); + + if (apl->engineStartup == nullptr) { + quitprintf("Plugin '%s' is not a valid AGS plugin (no engine startup entry point)", apl->filename); + } + apl->onEvent = (int(*)(int,int))apl->library.GetFunctionAddress("AGS_EngineOnEvent"); + apl->debugHook = (int(*)(const char*,int,int))apl->library.GetFunctionAddress("AGS_EngineDebugHook"); + apl->initGfxHook = (void(*)(const char*, void*))apl->library.GetFunctionAddress("AGS_EngineInitGfx"); + } + else + { + AGS::Common::Debug::Printf(kDbgMsg_Info, "Plugin '%s' could not be loaded (expected '%s'), trying built-in plugins...", + apl->filename, expect_filename.GetCStr()); + if (pl_use_builtin_plugin(apl)) + { + AGS::Common::Debug::Printf(kDbgMsg_Info, "Build-in plugin '%s' found and being used.", apl->filename); + } + else + { + // Plugin loading has failed at this point, try using built-in plugin function stubs + if (RegisterPluginStubs((const char*)apl->filename)) + AGS::Common::Debug::Printf(kDbgMsg_Info, "Placeholder functions for the plugin '%s' found.", apl->filename); + else + AGS::Common::Debug::Printf(kDbgMsg_Info, "No placeholder functions for the plugin '%s' found. The game might fail to load!", apl->filename); + continue; + } + } + + apl->eiface.pluginId = numPlugins - 1; + apl->eiface.version = 24; + apl->wantHook = 0; + apl->available = true; + } + return kGameInitErr_NoError; +} + +bool pl_is_plugin_loaded(const char *pl_name) +{ + if (!pl_name) + return false; + + for (int i = 0; i < numPlugins; ++i) + { + if (ags_stricmp(pl_name, plugins[i].filename) == 0) + return plugins[i].available; + } + return false; +} + +bool pl_any_want_hook(int event) +{ + for (int i = 0; i < numPlugins; ++i) + { + if(plugins[i].wantHook & event) + return true; + } + return false; +} \ No newline at end of file diff --git a/engines/ags/engine/plugin/agsplugin.h b/engines/ags/engine/plugin/agsplugin.h new file mode 100644 index 00000000000..6a09ee93a92 --- /dev/null +++ b/engines/ags/engine/plugin/agsplugin.h @@ -0,0 +1,570 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AGS Plugin interface header file +// +// #define THIS_IS_THE_PLUGIN beforehand if including from the plugin +// +//============================================================================= + +#ifndef _AGS_PLUGIN_H +#define _AGS_PLUGIN_H + +// If the plugin isn't using DDraw, don't require the headers +#ifndef DIRECTDRAW_VERSION +typedef void *LPDIRECTDRAW2; +typedef void *LPDIRECTDRAWSURFACE2; +#endif + +#ifndef DIRECTSOUND_VERSION +typedef void *LPDIRECTSOUND; +#endif + +#ifndef DIRECTINPUT_VERSION +typedef void *LPDIRECTINPUTDEVICE; +#endif + +// If the user isn't using Allegro or WinGDI, define the BITMAP into something +#if !defined(ALLEGRO_H) && !defined(_WINGDI_) && !defined(BITMAP_DEFINED) +typedef char BITMAP; +#endif + +// If not using windows.h, define HWND +#if !defined(_WINDOWS_) +typedef int HWND; +#endif + +// This file is distributed as part of the Plugin API docs, so +// ensure that WINDOWS_VERSION is defined (if applicable) +#if defined(_WIN32) + #undef WINDOWS_VERSION + #define WINDOWS_VERSION +#endif + +// DOS engine doesn't know about stdcall / neither does Linux version +#if !defined (_WIN32) + #define __stdcall +#endif + +#ifndef int32 +#define int32 int +#endif + +#define AGSIFUNC(type) virtual type __stdcall + +#define MASK_WALKABLE 1 +#define MASK_WALKBEHIND 2 +#define MASK_HOTSPOT 3 +// MASK_REGIONS is interface version 11 and above only +#define MASK_REGIONS 4 + +// **** WARNING: DO NOT ALTER THESE CLASSES IN ANY WAY! +// **** CHANGING THE ORDER OF THE FUNCTIONS OR ADDING ANY VARIABLES +// **** WILL CRASH THE SYSTEM. + +struct AGSColor { + unsigned char r,g,b; + unsigned char padding; +}; + +struct AGSGameOptions { + int32 score; // player's current score + int32 usedmode; // set by ProcessClick to last cursor mode used + int32 disabled_user_interface; // >0 while in cutscene/etc + int32 gscript_timer; // obsolete + int32 debug_mode; // whether we're in debug mode + int32 globalvars[50]; // obsolete + int32 messagetime; // time left for auto-remove messages + int32 usedinv; // inventory item last used + int32 inv_top,inv_numdisp,inv_numorder,inv_numinline; + int32 text_speed; // how quickly text is removed + int32 sierra_inv_color; // background used to paint defualt inv window + int32 talkanim_speed; // animation speed of talking anims + int32 inv_item_wid,inv_item_hit; // set by SetInvDimensions + int32 speech_text_shadow; // colour of outline fonts (default black) + int32 swap_portrait_side; // sierra-style speech swap sides + int32 speech_textwindow_gui; // textwindow used for sierra-style speech + int32 follow_change_room_timer; // delay before moving following characters into new room + int32 totalscore; // maximum possible score + int32 skip_display; // how the user can skip normal Display windows + int32 no_multiloop_repeat; // for backwards compatibility + int32 roomscript_finished; // on_call finished in room + int32 used_inv_on; // inv item they clicked on + int32 no_textbg_when_voice; // no textwindow bgrnd when voice speech is used + int32 max_dialogoption_width; // max width of dialog options text window + int32 no_hicolor_fadein; // fade out but instant in for hi-color + int32 bgspeech_game_speed; // is background speech relative to game speed + int32 bgspeech_stay_on_display; // whether to remove bg speech when DisplaySpeech is used + int32 unfactor_speech_from_textlength; // remove "&10" when calculating time for text to stay + int32 mp3_loop_before_end; // loop this time before end of track (ms) + int32 speech_music_drop; // how much to drop music volume by when speech is played + int32 in_cutscene; // we are between a StartCutscene and EndCutscene + int32 fast_forward; // player has elected to skip cutscene + int32 room_width; // width of current room + int32 room_height; // height of current room +}; + +// AGSCharacter.flags +#define CHF_NOSCALING 1 +#define CHF_FIXVIEW 2 // between SetCharView and ReleaseCharView +#define CHF_NOINTERACT 4 +#define CHF_NODIAGONAL 8 +#define CHF_ALWAYSIDLE 0x10 +#define CHF_NOLIGHTING 0x20 +#define CHF_NOTURNING 0x40 +#define CHF_NOWALKBEHINDS 0x80 + +struct AGSCharacter { + int32 defview; + int32 talkview; + int32 view; + int32 room, prevroom; + int32 x, y, wait; + int32 flags; + short following; + short followinfo; + int32 idleview; // the loop will be randomly picked + short idletime, idleleft; // num seconds idle before playing anim + short transparency; // if character is transparent + short baseline; + int32 activeinv; + int32 talkcolor; + int32 thinkview; + int32 reserved[2]; + short walkspeed_y, pic_yoffs; + int32 z; + int32 reserved2[5]; + short loop, frame; + short walking, animating; + short walkspeed, animspeed; + short inv[301]; + short actx, acty; + char name[40]; + char scrname[20]; + char on; +}; + +// AGSObject.flags +#define OBJF_NOINTERACT 1 // not clickable +#define OBJF_NOWALKBEHINDS 2 // ignore walk-behinds + +struct AGSObject { + int32 x,y; + int32 transparent; // current transparency setting + int32 reserved[4]; + short num; // sprite slot number + short baseline; // <=0 to use Y co-ordinate; >0 for specific baseline + short view,loop,frame; // only used to track animation - 'num' holds the current sprite + short wait,moving; + char cycling; // is it currently animating? + char overall_speed; + char on; + char flags; +}; + +// AGSViewFrame.flags +#define FRAF_MIRRORED 1 // flipped left to right + +struct AGSViewFrame { + int32 pic; // sprite slot number + short xoffs, yoffs; + short speed; + int32 flags; + int32 sound; // play sound when this frame comes round + int32 reserved_for_future[2]; +}; + +// AGSMouseCursor.flags +#define MCF_ANIMATEMOVE 1 +#define MCF_DISABLED 2 +#define MCF_STANDARD 4 +#define MCF_ONLYANIMOVERHOTSPOT 8 + +struct AGSMouseCursor { + int32 pic; // sprite slot number + short hotx, hoty; // x,y hotspot co-ordinates + short view; // view (for animating cursors) or -1 + char name[10]; // name of cursor mode + char flags; // MCF_flags above +}; + +// The editor-to-plugin interface +class IAGSEditor { +public: + int32 version; + int32 pluginId; // used internally, do not touch this + +public: + // get the HWND of the main editor frame + AGSIFUNC(HWND) GetEditorHandle (); + // get the HWND of the current active window + AGSIFUNC(HWND) GetWindowHandle (); + // add some script to the default header + AGSIFUNC(void) RegisterScriptHeader (const char *header); + // de-register a script header (pass same pointer as when added) + AGSIFUNC(void) UnregisterScriptHeader (const char *header); + +}; + + +// Below are interface 3 and later +#define AGSE_KEYPRESS 1 +#define AGSE_MOUSECLICK 2 +#define AGSE_POSTSCREENDRAW 4 +// Below are interface 4 and later +#define AGSE_PRESCREENDRAW 8 +// Below are interface 5 and later +#define AGSE_SAVEGAME 0x10 +#define AGSE_RESTOREGAME 0x20 +// Below are interface 6 and later +#define AGSE_PREGUIDRAW 0x40 +#define AGSE_LEAVEROOM 0x80 +#define AGSE_ENTERROOM 0x100 +#define AGSE_TRANSITIONIN 0x200 +#define AGSE_TRANSITIONOUT 0x400 +// Below are interface 12 and later +#define AGSE_FINALSCREENDRAW 0x800 +#define AGSE_TRANSLATETEXT 0x1000 +// Below are interface 13 and later +#define AGSE_SCRIPTDEBUG 0x2000 +#define AGSE_AUDIODECODE 0x4000 // obsolete, no longer supported +// Below are interface 18 and later +#define AGSE_SPRITELOAD 0x8000 +// Below are interface 21 and later +#define AGSE_PRERENDER 0x10000 +// Below are interface 24 and later +#define AGSE_PRESAVEGAME 0x20000 +#define AGSE_POSTRESTOREGAME 0x40000 +#define AGSE_TOOHIGH 0x80000 + +// GetFontType font types +#define FNT_INVALID 0 +#define FNT_SCI 1 +#define FNT_TTF 2 + +// PlaySoundChannel sound types +#define PSND_WAVE 1 +#define PSND_MP3STREAM 2 +#define PSND_MP3STATIC 3 +#define PSND_OGGSTREAM 4 +#define PSND_OGGSTATIC 5 +#define PSND_MIDI 6 +#define PSND_MOD 7 + +class IAGSScriptManagedObject { +public: + // when a ref count reaches 0, this is called with the address + // of the object. Return 1 to remove the object from memory, 0 to + // leave it + virtual int Dispose(const char *address, bool force) = 0; + // return the type name of the object + virtual const char *GetType() = 0; + // serialize the object into BUFFER (which is BUFSIZE bytes) + // return number of bytes used + virtual int Serialize(const char *address, char *buffer, int bufsize) = 0; +protected: + IAGSScriptManagedObject() {}; + ~IAGSScriptManagedObject() {}; +}; + +class IAGSManagedObjectReader { +public: + virtual void Unserialize(int key, const char *serializedData, int dataSize) = 0; +protected: + IAGSManagedObjectReader() {}; + ~IAGSManagedObjectReader() {}; +}; + +class IAGSFontRenderer { +public: + virtual bool LoadFromDisk(int fontNumber, int fontSize) = 0; + virtual void FreeMemory(int fontNumber) = 0; + virtual bool SupportsExtendedCharacters(int fontNumber) = 0; + virtual int GetTextWidth(const char *text, int fontNumber) = 0; + virtual int GetTextHeight(const char *text, int fontNumber) = 0; + virtual void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) = 0; + virtual void AdjustYCoordinateForFont(int *ycoord, int fontNumber) = 0; + virtual void EnsureTextValidForFont(char *text, int fontNumber) = 0; +protected: + IAGSFontRenderer() {}; + ~IAGSFontRenderer() {}; +}; + +// The plugin-to-engine interface +class IAGSEngine { +public: + int32 version; + int32 pluginId; // used internally, do not touch + +public: + // quit the game + AGSIFUNC(void) AbortGame (const char *reason); + // get engine version + AGSIFUNC(const char*) GetEngineVersion (); + // register a script function with the system + AGSIFUNC(void) RegisterScriptFunction (const char *name, void *address); +#ifdef WINDOWS_VERSION + // get game window handle + AGSIFUNC(HWND) GetWindowHandle(); + // get reference to main DirectDraw interface + AGSIFUNC(LPDIRECTDRAW2) GetDirectDraw2 (); + // get the DDraw surface associated with a bitmap + AGSIFUNC(LPDIRECTDRAWSURFACE2) GetBitmapSurface (BITMAP *); +#endif + // get a reference to the screen bitmap + AGSIFUNC(BITMAP *) GetScreen (); + + // *** BELOW ARE INTERFACE VERSION 2 AND ABOVE ONLY + // ask the engine to call back when a certain event happens + AGSIFUNC(void) RequestEventHook (int32 event); + // get the options data saved in the editor + AGSIFUNC(int) GetSavedData (char *buffer, int32 bufsize); + + // *** BELOW ARE INTERFACE VERSION 3 AND ABOVE ONLY + // get the virtual screen + AGSIFUNC(BITMAP *) GetVirtualScreen (); + // write text to the screen in the specified font and colour + AGSIFUNC(void) DrawText (int32 x, int32 y, int32 font, int32 color, char *text); + // get screen dimensions + AGSIFUNC(void) GetScreenDimensions (int32 *width, int32 *height, int32 *coldepth); + // get screen surface to draw on + AGSIFUNC(unsigned char**) GetRawBitmapSurface (BITMAP *); + // release the surface + AGSIFUNC(void) ReleaseBitmapSurface (BITMAP *); + // get the current mouse co-ordinates + AGSIFUNC(void) GetMousePosition (int32 *x, int32 *y); + + // *** BELOW ARE INTERFACE VERSION 4 AND ABOVE ONLY + // get the current room number + AGSIFUNC(int) GetCurrentRoom (); + // get the number of background scenes in this room + AGSIFUNC(int) GetNumBackgrounds (); + // get the current background frame + AGSIFUNC(int) GetCurrentBackground (); + // get a background scene bitmap + AGSIFUNC(BITMAP *) GetBackgroundScene (int32); + // get dimensions of a bitmap + AGSIFUNC(void) GetBitmapDimensions (BITMAP *bmp, int32 *width, int32 *height, int32 *coldepth); + + // *** BELOW ARE INTERFACE VERSION 5 AND ABOVE ONLY + // similar to fwrite - buffer, size, filehandle + AGSIFUNC(int) FWrite (void *, int32, int32); + // similar to fread - buffer, size, filehandle + AGSIFUNC(int) FRead (void *, int32, int32); + // print text, wrapping as usual + AGSIFUNC(void) DrawTextWrapped (int32 x, int32 y, int32 width, int32 font, int32 color, const char *text); + // set the current active 'screen' + AGSIFUNC(void) SetVirtualScreen (BITMAP *); + // look up a word in the parser dictionary + AGSIFUNC(int) LookupParserWord (const char *word); + // draw a bitmap to the active screen + AGSIFUNC(void) BlitBitmap (int32 x, int32 y, BITMAP *, int32 masked); + // update the mouse and music + AGSIFUNC(void) PollSystem (); + + // *** BELOW ARE INTERFACE VERSION 6 AND ABOVE ONLY + // get number of characters in game + AGSIFUNC(int) GetNumCharacters (); + // get reference to specified character struct + AGSIFUNC(AGSCharacter*) GetCharacter (int32); + // get reference to game struct + AGSIFUNC(AGSGameOptions*) GetGameOptions (); + // get reference to current palette + AGSIFUNC(AGSColor*) GetPalette(); + // update palette + AGSIFUNC(void) SetPalette (int32 start, int32 finish, AGSColor*); + + // *** BELOW ARE INTERFACE VERSION 7 AND ABOVE ONLY + // get the current player character + AGSIFUNC(int) GetPlayerCharacter (); + // adjust to main viewport co-ordinates + AGSIFUNC(void) RoomToViewport (int32 *x, int32 *y); + // adjust from main viewport co-ordinates (ignores viewport bounds) + AGSIFUNC(void) ViewportToRoom (int32 *x, int32 *y); + // number of objects in current room + AGSIFUNC(int) GetNumObjects (); + // get reference to specified object + AGSIFUNC(AGSObject*) GetObject (int32); + // get sprite graphic + AGSIFUNC(BITMAP *) GetSpriteGraphic (int32); + // create a new blank bitmap + AGSIFUNC(BITMAP *) CreateBlankBitmap (int32 width, int32 height, int32 coldep); + // free a created bitamp + AGSIFUNC(void) FreeBitmap (BITMAP *); + + // *** BELOW ARE INTERFACE VERSION 8 AND ABOVE ONLY + // get one of the room area masks + AGSIFUNC(BITMAP *) GetRoomMask(int32); + + // *** BELOW ARE INTERFACE VERSION 9 AND ABOVE ONLY + // get a particular view frame + AGSIFUNC(AGSViewFrame *) GetViewFrame(int32 view, int32 loop, int32 frame); + // get the walk-behind baseline of a specific WB area + AGSIFUNC(int) GetWalkbehindBaseline(int32 walkbehind); + // get the address of a script function + AGSIFUNC(void *) GetScriptFunctionAddress(const char * funcName); + // get the transparent colour of a bitmap + AGSIFUNC(int) GetBitmapTransparentColor(BITMAP *); + // get the character scaling level at a particular point + AGSIFUNC(int) GetAreaScaling (int32 x, int32 y); + // equivalent to the text script function + AGSIFUNC(int) IsGamePaused(); + + // *** BELOW ARE INTERFACE VERSION 10 AND ABOVE ONLY + // get the raw pixel value to use for the specified AGS colour + AGSIFUNC(int) GetRawPixelColor (int32 color); + + // *** BELOW ARE INTERFACE VERSION 11 AND ABOVE ONLY + // get the width / height of the specified sprite + AGSIFUNC(int) GetSpriteWidth (int32); + AGSIFUNC(int) GetSpriteHeight (int32); + // get the dimensions of the specified string in the specified font + AGSIFUNC(void) GetTextExtent (int32 font, const char *text, int32 *width, int32 *height); + // print a message to the debug console + AGSIFUNC(void) PrintDebugConsole (const char *text); + // play a sound on the specified channel + AGSIFUNC(void) PlaySoundChannel (int32 channel, int32 soundType, int32 volume, int32 loop, const char *filename); + // same as text script function + AGSIFUNC(int) IsChannelPlaying (int32 channel); + + // *** BELOW ARE INTERFACE VERSION 12 AND ABOVE ONLY + // invalidate a region of the virtual screen + AGSIFUNC(void) MarkRegionDirty(int32 left, int32 top, int32 right, int32 bottom); + // get mouse cursor details + AGSIFUNC(AGSMouseCursor *) GetMouseCursor(int32 cursor); + // get the various components of a pixel + AGSIFUNC(void) GetRawColorComponents(int32 coldepth, int32 color, int32 *red, int32 *green, int32 *blue, int32 *alpha); + // make a pixel colour from the supplied components + AGSIFUNC(int) MakeRawColorPixel(int32 coldepth, int32 red, int32 green, int32 blue, int32 alpha); + // get whether the font is TTF or SCI + AGSIFUNC(int) GetFontType(int32 fontNum); + // create a new dynamic sprite slot + AGSIFUNC(int) CreateDynamicSprite(int32 coldepth, int32 width, int32 height); + // free a created dynamic sprite + AGSIFUNC(void) DeleteDynamicSprite(int32 slot); + // check if a sprite has an alpha channel + AGSIFUNC(int) IsSpriteAlphaBlended(int32 slot); + + // *** BELOW ARE INTERFACE VERSION 13 AND ABOVE ONLY + // un-request an event, requested earlier with RequestEventHook + AGSIFUNC(void) UnrequestEventHook(int32 event); + // draw a translucent bitmap to the active screen + AGSIFUNC(void) BlitSpriteTranslucent(int32 x, int32 y, BITMAP *, int32 trans); + // draw a sprite to the screen, but rotated around its centre + AGSIFUNC(void) BlitSpriteRotated(int32 x, int32 y, BITMAP *, int32 angle); + + // *** BELOW ARE INTERFACE VERSION 14 AND ABOVE ONLY +#ifdef WINDOWS_VERSION + // get reference to main DirectSound interface + AGSIFUNC(LPDIRECTSOUND) GetDirectSound(); +#endif + // disable AGS sound engine + AGSIFUNC(void) DisableSound(); + // check whether a script function can be run now + AGSIFUNC(int) CanRunScriptFunctionNow(); + // call a user-defined script function + AGSIFUNC(int) CallGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1 = 0, long arg2 = 0, long arg3 = 0); + + // *** BELOW ARE INTERFACE VERSION 15 AND ABOVE ONLY + // force any sprites on-screen using the slot to be updated + AGSIFUNC(void) NotifySpriteUpdated(int32 slot); + // change whether the specified sprite is a 32-bit alpha blended image + AGSIFUNC(void) SetSpriteAlphaBlended(int32 slot, int32 isAlphaBlended); + // run the specified script function whenever script engine is available + AGSIFUNC(void) QueueGameScriptFunction(const char *name, int32 globalScript, int32 numArgs, long arg1 = 0, long arg2 = 0); + // register a new dynamic managed script object + AGSIFUNC(int) RegisterManagedObject(const void *object, IAGSScriptManagedObject *callback); + // add an object reader for the specified object type + AGSIFUNC(void) AddManagedObjectReader(const char *typeName, IAGSManagedObjectReader *reader); + // register an un-serialized managed script object + AGSIFUNC(void) RegisterUnserializedObject(int key, const void *object, IAGSScriptManagedObject *callback); + + // *** BELOW ARE INTERFACE VERSION 16 AND ABOVE ONLY + // get the address of a managed object based on its key + AGSIFUNC(void*) GetManagedObjectAddressByKey(int key); + // get managed object's key from its address + AGSIFUNC(int) GetManagedObjectKeyByAddress(const char *address); + + // *** BELOW ARE INTERFACE VERSION 17 AND ABOVE ONLY + // create a new script string + AGSIFUNC(const char*) CreateScriptString(const char *fromText); + + // *** BELOW ARE INTERFACE VERSION 18 AND ABOVE ONLY + // increment reference count + AGSIFUNC(int) IncrementManagedObjectRefCount(const char *address); + // decrement reference count + AGSIFUNC(int) DecrementManagedObjectRefCount(const char *address); + // set mouse position + AGSIFUNC(void) SetMousePosition(int32 x, int32 y); + // simulate the mouse being clicked + AGSIFUNC(void) SimulateMouseClick(int32 button); + // get number of waypoints on this movement path + AGSIFUNC(int) GetMovementPathWaypointCount(int32 pathId); + // get the last waypoint that the char/obj passed + AGSIFUNC(int) GetMovementPathLastWaypoint(int32 pathId); + // get the co-ordinates of the specified waypoint + AGSIFUNC(void) GetMovementPathWaypointLocation(int32 pathId, int32 waypoint, int32 *x, int32 *y); + // get the speeds of the specified waypoint + AGSIFUNC(void) GetMovementPathWaypointSpeed(int32 pathId, int32 waypoint, int32 *xSpeed, int32 *ySpeed); + + // *** BELOW ARE INTERFACE VERSION 19 AND ABOVE ONLY + // get the current graphics driver ID + AGSIFUNC(const char*) GetGraphicsDriverID(); + + // *** BELOW ARE INTERFACE VERSION 22 AND ABOVE ONLY + // get whether we are running under the editor's debugger + AGSIFUNC(int) IsRunningUnderDebugger(); + // tells the engine to break into the debugger when the next line of script is run + AGSIFUNC(void) BreakIntoDebugger(); + // fills buffer with \fileName, as appropriate + AGSIFUNC(void) GetPathToFileInCompiledFolder(const char* fileName, char* buffer); + + // *** BELOW ARE INTERFACE VERSION 23 AND ABOVE ONLY +#ifdef WINDOWS_VERSION + // get reference to keyboard Direct Input device + AGSIFUNC(LPDIRECTINPUTDEVICE) GetDirectInputKeyboard(); + // get reference to mouse Direct Input device + AGSIFUNC(LPDIRECTINPUTDEVICE) GetDirectInputMouse(); +#endif + // install a replacement renderer for the specified font number + AGSIFUNC(IAGSFontRenderer*) ReplaceFontRenderer(int fontNumber, IAGSFontRenderer* newRenderer); +}; + +#ifdef THIS_IS_THE_PLUGIN + +#ifdef WINDOWS_VERSION +#define DLLEXPORT extern "C" __declspec(dllexport) +#else +// MAC VERSION: compile with -fvisibility=hidden +// gcc -dynamiclib -std=gnu99 agsplugin.c -fvisibility=hidden -o agsplugin.dylib +#define DLLEXPORT extern "C" __attribute__((visibility("default"))) +#endif + +DLLEXPORT const char * AGS_GetPluginName(void); +DLLEXPORT int AGS_EditorStartup (IAGSEditor *); +DLLEXPORT void AGS_EditorShutdown (void); +DLLEXPORT void AGS_EditorProperties (HWND); +DLLEXPORT int AGS_EditorSaveGame (char *, int); +DLLEXPORT void AGS_EditorLoadGame (char *, int); +DLLEXPORT void AGS_EngineStartup (IAGSEngine *); +DLLEXPORT void AGS_EngineShutdown (void); +DLLEXPORT int AGS_EngineOnEvent (int, int); +DLLEXPORT int AGS_EngineDebugHook(const char *, int, int); +DLLEXPORT void AGS_EngineInitGfx(const char* driverID, void *data); +// We export this to verify that we are an AGS Plugin +DLLEXPORT int AGS_PluginV2 ( ) { return 1; } + +#endif // THIS_IS_THE_PLUGIN + +#endif diff --git a/engines/ags/engine/plugin/global_plugin.cpp b/engines/ags/engine/plugin/global_plugin.cpp new file mode 100644 index 00000000000..001841fe2d4 --- /dev/null +++ b/engines/ags/engine/plugin/global_plugin.cpp @@ -0,0 +1,260 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Stubs for plugin functions. +// +//============================================================================= + +#include +#include "ac/global_plugin.h" +#include "ac/mouse.h" +#include "util/string_compat.h" + +int pluginSimulatedClick = NONE; + +void PluginSimulateMouseClick(int pluginButtonID) { + pluginSimulatedClick = pluginButtonID - 1; +} + +//============================================================================= +// +// Script API Functions +// +//============================================================================= + +#include "script/script_runtime.h" + +RuntimeScriptValue Sc_PluginStub_Void(const RuntimeScriptValue *params, int32_t param_count) +{ + return RuntimeScriptValue((int32_t)0); +} + +RuntimeScriptValue Sc_PluginStub_Int0(const RuntimeScriptValue *params, int32_t param_count) +{ + return RuntimeScriptValue().SetInt32(0); +} + +RuntimeScriptValue Sc_PluginStub_IntNeg1(const RuntimeScriptValue *params, int32_t param_count) +{ + return RuntimeScriptValue().SetInt32(-1); +} + +RuntimeScriptValue Sc_PluginStub_NullStr(const RuntimeScriptValue *params, int32_t param_count) +{ + return RuntimeScriptValue().SetStringLiteral(NULL); +} + +bool RegisterPluginStubs(const char* name) +{ + // Stubs for plugin functions. + + bool is_agsteam = (ags_stricmp(name, "agsteam") == 0) || (ags_stricmp(name, "agsteam-unified") == 0) || + (ags_stricmp(name, "agsteam-disjoint") == 0); + bool is_agsgalaxy = (ags_stricmp(name, "agsgalaxy") == 0) || (ags_stricmp(name, "agsgalaxy-unified") == 0) || + (ags_stricmp(name, "agsgalaxy-disjoint") == 0); + + if (ags_stricmp(name, "ags_shell") == 0) + { + // ags_shell.dll + ccAddExternalStaticFunction("ShellExecute", Sc_PluginStub_Void); + return true; + } + else if (ags_stricmp(name, "ags_snowrain") == 0) + { + // ags_snowrain.dll + ccAddExternalStaticFunction("srSetSnowDriftRange", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetSnowDriftSpeed", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetSnowFallSpeed", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srChangeSnowAmount", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetSnowBaseline", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetSnowTransparency", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetSnowDefaultView", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetSnowWindSpeed", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetSnowAmount", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetSnowView", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srChangeRainAmount", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetRainView", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetRainDefaultView", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetRainTransparency", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetRainWindSpeed", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetRainBaseline", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetRainAmount", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetRainFallSpeed", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetWindSpeed", Sc_PluginStub_Void); + ccAddExternalStaticFunction("srSetBaseline", Sc_PluginStub_Void); + return true; + } + else if (ags_stricmp(name, "agsjoy") == 0) + { + // agsjoy.dll + ccAddExternalStaticFunction("JoystickCount", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("JoystickName", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("JoystickRescan", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Joystick::Open^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Joystick::IsOpen^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Joystick::Click^1", Sc_PluginStub_Void); + ccAddExternalStaticFunction("Joystick::Close^0", Sc_PluginStub_Void); + ccAddExternalStaticFunction("Joystick::Valid^0", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Joystick::Unplugged^0", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Joystick::GetName^0", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Joystick::GetAxis^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Joystick::IsButtonDown^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Joystick::IsJoyBtnDown^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Joystick::Update^0", Sc_PluginStub_Void); + ccAddExternalStaticFunction("Joystick::DisableEvents^0", Sc_PluginStub_Void); + ccAddExternalStaticFunction("Joystick::EnableEvents^1", Sc_PluginStub_Void); + return true; + } + else if (ags_stricmp(name, "agsblend") == 0) + { + // agsblend.dll + ccAddExternalStaticFunction("DrawAlpha", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetAlpha", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("PutAlpha", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("Blur", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("HighPass", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("DrawAdd", Sc_PluginStub_Int0); + return true; + } + else if (ags_stricmp(name, "agsflashlight") == 0) + { + // agsflashlight.dll + ccAddExternalStaticFunction("SetFlashlightTint", Sc_PluginStub_Void); + ccAddExternalStaticFunction("GetFlashlightTintRed", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetFlashlightTintGreen", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetFlashlightTintBlue", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetFlashlightMinLightLevel", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetFlashlightMaxLightLevel", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("SetFlashlightDarkness", Sc_PluginStub_Void); + ccAddExternalStaticFunction("GetFlashlightDarkness", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("SetFlashlightDarknessSize", Sc_PluginStub_Void); + ccAddExternalStaticFunction("GetFlashlightDarknessSize", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("SetFlashlightBrightness", Sc_PluginStub_Void); + ccAddExternalStaticFunction("GetFlashlightBrightness", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("SetFlashlightBrightnessSize", Sc_PluginStub_Void); + ccAddExternalStaticFunction("GetFlashlightBrightnessSize", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("SetFlashlightPosition", Sc_PluginStub_Void); + ccAddExternalStaticFunction("GetFlashlightPositionX", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetFlashlightPositionY", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("SetFlashlightFollowMouse", Sc_PluginStub_Void); + ccAddExternalStaticFunction("GetFlashlightFollowMouse", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("SetFlashlightFollowCharacter", Sc_PluginStub_Void); + ccAddExternalStaticFunction("GetFlashlightFollowCharacter", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetFlashlightCharacterDX", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetFlashlightCharacterDY", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetFlashlightCharacterHorz", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("GetFlashlightCharacterVert", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("SetFlashlightMask", Sc_PluginStub_Void); + ccAddExternalStaticFunction("GetFlashlightMask", Sc_PluginStub_Int0); + return true; + } + else if (ags_stricmp(name, "agswadjetutil") == 0) + { + // agswadjetutil.dll + ccAddExternalStaticFunction("IsOnPhone", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("FakeKeypress", Sc_PluginStub_Void); + ccAddExternalStaticFunction("IosSetAchievementValue", Sc_PluginStub_Void); + ccAddExternalStaticFunction("IosGetAchievementValue", Sc_PluginStub_IntNeg1); + ccAddExternalStaticFunction("IosShowAchievements", Sc_PluginStub_Void); + ccAddExternalStaticFunction("IosResetAchievements", Sc_PluginStub_Void); + ccAddExternalStaticFunction("MobileGetAchievement", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("MobileSetAchievement", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("MobileShowAchievements", Sc_PluginStub_Void); + ccAddExternalStaticFunction("MobileResetAchievements", Sc_PluginStub_Void); + return true; + } + else if (ags_stricmp(name, "agsspritefont") == 0) + { + ccAddExternalStaticFunction("SetSpriteFont", Sc_PluginStub_Void); + ccAddExternalStaticFunction("SetVariableSpriteFont", Sc_PluginStub_Void); + ccAddExternalStaticFunction("SetGlyph", Sc_PluginStub_Void); + ccAddExternalStaticFunction("SetSpacing", Sc_PluginStub_Void); + ccAddExternalStaticFunction("SetLineHeightAdjust", Sc_PluginStub_Void); + return true; + } + else if (is_agsteam || is_agsgalaxy) + { + // agsteam.dll or agsgalaxy.dll + ccAddExternalStaticFunction("AGS2Client::IsAchievementAchieved^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::SetAchievementAchieved^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::ResetAchievement^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::GetIntStat^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::GetFloatStat^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::GetAverageRateStat^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::SetIntStat^2", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::SetFloatStat^2", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::UpdateAverageRateStat^3", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::ResetStatsAndAchievements^0", Sc_PluginStub_Void); + ccAddExternalStaticFunction("AGS2Client::get_Initialized", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::get_CurrentLeaderboardName", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGS2Client::RequestLeaderboard^3", Sc_PluginStub_Void); + ccAddExternalStaticFunction("AGS2Client::UploadScore^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::geti_LeaderboardNames", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGS2Client::geti_LeaderboardScores", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::get_LeaderboardCount", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGS2Client::GetUserName^0", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGS2Client::GetCurrentGameLanguage^0", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGS2Client::FindLeaderboard^1", Sc_PluginStub_Void); + ccAddExternalStaticFunction("AGS2Client::Initialize^2", Sc_PluginStub_Int0); + if (is_agsteam) + { + ccAddExternalStaticFunction("AGSteam::IsAchievementAchieved^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::SetAchievementAchieved^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::ResetAchievement^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::GetIntStat^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::GetFloatStat^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::GetAverageRateStat^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::SetIntStat^2", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::SetFloatStat^2", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::UpdateAverageRateStat^3", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::ResetStatsAndAchievements^0", Sc_PluginStub_Void); + ccAddExternalStaticFunction("AGSteam::get_Initialized", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::get_CurrentLeaderboardName", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGSteam::RequestLeaderboard^3", Sc_PluginStub_Void); + ccAddExternalStaticFunction("AGSteam::UploadScore^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::geti_LeaderboardNames", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGSteam::geti_LeaderboardScores", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::get_LeaderboardCount", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSteam::GetUserName^0", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGSteam::GetCurrentGameLanguage^0", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGSteam::FindLeaderboard^1", Sc_PluginStub_Void); + } + else // agsgalaxy + { + ccAddExternalStaticFunction("AGSGalaxy::IsAchievementAchieved^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::SetAchievementAchieved^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::ResetAchievement^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::GetIntStat^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::GetFloatStat^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::GetAverageRateStat^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::SetIntStat^2", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::SetFloatStat^2", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::UpdateAverageRateStat^3", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::ResetStatsAndAchievements^0", Sc_PluginStub_Void); + ccAddExternalStaticFunction("AGSGalaxy::get_Initialized", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::get_CurrentLeaderboardName", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGSGalaxy::RequestLeaderboard^3", Sc_PluginStub_Void); + ccAddExternalStaticFunction("AGSGalaxy::UploadScore^1", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::geti_LeaderboardNames", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGSGalaxy::geti_LeaderboardScores", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::get_LeaderboardCount", Sc_PluginStub_Int0); + ccAddExternalStaticFunction("AGSGalaxy::GetUserName^0", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGSGalaxy::GetCurrentGameLanguage^0", Sc_PluginStub_NullStr); + ccAddExternalStaticFunction("AGSGalaxy::Initialize^2", Sc_PluginStub_Int0); + } + return true; + } + + return false; +} diff --git a/engines/ags/engine/plugin/plugin_builtin.h b/engines/ags/engine/plugin/plugin_builtin.h new file mode 100644 index 00000000000..a25d8f03a2b --- /dev/null +++ b/engines/ags/engine/plugin/plugin_builtin.h @@ -0,0 +1,41 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Plugin system functions. +// +//============================================================================= +#ifndef __AGS_EE_PLUGIN__PLUGINBUILTIN_H +#define __AGS_EE_PLUGIN__PLUGINBUILTIN_H + +#define PLUGIN_FILENAME_MAX (49) + +class IAGSEngine; + +using namespace AGS; // FIXME later + +// Initial implementation for apps to register their own inbuilt plugins + +struct InbuiltPluginDetails { + char filename[PLUGIN_FILENAME_MAX+1]; + void (*engineStartup) (IAGSEngine *); + void (*engineShutdown) (); + int (*onEvent) (int, int); + void (*initGfxHook) (const char *driverName, void *data); + int (*debugHook) (const char * whichscript, int lineNumber, int reserved); +}; + +// Register a builtin plugin. +int pl_register_builtin_plugin(InbuiltPluginDetails const &details); + +#endif // __AGS_EE_PLUGIN__PLUGINBUILTIN_H diff --git a/engines/ags/engine/plugin/plugin_engine.h b/engines/ags/engine/plugin/plugin_engine.h new file mode 100644 index 00000000000..d5b3cb26569 --- /dev/null +++ b/engines/ags/engine/plugin/plugin_engine.h @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Plugin system functions. +// +//============================================================================= +#ifndef __AGS_EE_PLUGIN__PLUGINENGINE_H +#define __AGS_EE_PLUGIN__PLUGINENGINE_H + +#include +#include "game/game_init.h" +#include "game/plugininfo.h" + +#define PLUGIN_FILENAME_MAX (49) + +class IAGSEngine; +namespace AGS { namespace Common { class Stream; }} +using namespace AGS; // FIXME later + +void pl_stop_plugins(); +void pl_startup_plugins(); +int pl_run_plugin_hooks (int event, int data); +void pl_run_plugin_init_gfx_hooks(const char *driverName, void *data); +int pl_run_plugin_debug_hooks (const char *scriptfile, int linenum); +// Tries to register plugins, either by loading dynamic libraries, or getting any kind of replacement +Engine::GameInitError pl_register_plugins(const std::vector &infos); +bool pl_is_plugin_loaded(const char *pl_name); + +//returns whether _any_ plugins want a particular event +bool pl_any_want_hook(int event); + +void pl_set_file_handle(long data, AGS::Common::Stream *stream); +void pl_clear_file_handle(); + +#endif // __AGS_EE_PLUGIN__PLUGINENGINE_H diff --git a/engines/ags/engine/plugin/pluginobjectreader.cpp b/engines/ags/engine/plugin/pluginobjectreader.cpp new file mode 100644 index 00000000000..2d2700b31d5 --- /dev/null +++ b/engines/ags/engine/plugin/pluginobjectreader.cpp @@ -0,0 +1,19 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "plugin/pluginobjectreader.h" +#include "ac/runtime_defines.h" + +PluginObjectReader pluginReaders[MAX_PLUGIN_OBJECT_READERS]; +int numPluginReaders = 0; diff --git a/engines/ags/engine/plugin/pluginobjectreader.h b/engines/ags/engine/plugin/pluginobjectreader.h new file mode 100644 index 00000000000..7add04c40cc --- /dev/null +++ b/engines/ags/engine/plugin/pluginobjectreader.h @@ -0,0 +1,28 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_PLUGIN__PLUGINOBJECTREADER_H +#define __AGS_EE_PLUGIN__PLUGINOBJECTREADER_H + +class IAGSManagedObjectReader; + +struct PluginObjectReader { + IAGSManagedObjectReader *reader; + const char *type; +}; + +#endif // __AGS_EE_PLUGIN__PLUGINOBJECTREADER_H diff --git a/engines/ags/engine/resource/DefaultGDF.gdf.xml b/engines/ags/engine/resource/DefaultGDF.gdf.xml new file mode 100644 index 00000000000..df90691861f --- /dev/null +++ b/engines/ags/engine/resource/DefaultGDF.gdf.xml @@ -0,0 +1,18 @@ + + + + Gfx Driver 4 + This is an example GDF for the DirectX SDK. + 2006-03-03 + + Action/Adventure + + + + + + + Microsoft DirectX SDK + + + \ No newline at end of file diff --git a/engines/ags/engine/resource/game-1.ICO b/engines/ags/engine/resource/game-1.ICO new file mode 100644 index 00000000000..d26793b0036 Binary files /dev/null and b/engines/ags/engine/resource/game-1.ICO differ diff --git a/engines/ags/engine/resource/resource.h b/engines/ags/engine/resource/resource.h new file mode 100644 index 00000000000..bfacb7632ec --- /dev/null +++ b/engines/ags/engine/resource/resource.h @@ -0,0 +1,46 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by version.rc +// +#define IDOKRUN 3 +#define IDI_ICON 101 +#define IDD_SETUP 102 +#define IDC_DIGISOUND 1003 +#define IDC_MIDIMUSIC 1006 +#define IDC_WINDOWED 1007 +#define IDC_VERSION 1008 +#define IDC_LANGUAGE 1009 +#define IDC_SPRITECACHE 1010 +#define IDC_RENDERATSCREENRES 1011 +#define IDC_ADVANCED 1012 +#define IDC_VOICEPACK 1016 +#define IDC_REFRESH_85HZ 1017 +#define IDC_THREADEDAUDIO 1018 +#define IDC_ANTIALIAS 1021 +#define IDC_REDUCE32TO16 1022 +#define IDC_GFXFILTER 1023 +#define IDC_GFXDRIVER 1024 +#define IDC_RESOLUTION 1025 +#define IDC_GFXMODE 1028 +#define IDC_GFXMODETEXT 1029 +#define IDC_VSYNC 1031 +#define IDC_FSSCALING 1033 +#define IDC_WINDOWSCALING 1034 +#define IDC_GFXOPTIONS 1035 +#define IDC_MOUSESPEED 1036 +#define IDC_MOUSESPEED_TEXT 1037 +#define IDC_MOUSE_AUTOLOCK 1038 +#define IDC_CUSTOMSAVEDIRCHECK 1039 +#define IDC_CUSTOMSAVEDIR 1040 +#define IDC_CUSTOMSAVEDIRBTN 1041 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1039 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/engines/ags/engine/resource/tintshader.fx b/engines/ags/engine/resource/tintshader.fx new file mode 100644 index 00000000000..90ad5000a03 --- /dev/null +++ b/engines/ags/engine/resource/tintshader.fx @@ -0,0 +1,53 @@ + +float GetLuminance(float4 color) +{ + float colorMax = max (color[0], color[1]); + colorMax = max (colorMax, color[2]); + + return colorMax; +} + + +// Pixel shader input structure +struct PS_INPUT +{ + float2 Texture : TEXCOORD0; +}; + +// Pixel shader output structure +struct PS_OUTPUT +{ + float4 Color : COLOR0; +}; + +// Global variables +sampler2D Tex0; + +// tintHSV: parameters from AGS engine +// [0] = tint colour R +// [1] = tint colour G +// [2] = tint colour B +// [3] = tint saturation +const float4 tintRGB: register( c0 ); +// [0] = object transparency +// [1] = light level +const float4 transparency: register( c1 ); + +// Name: Simple Pixel Shader +// Type: Pixel shader +// Desc: Fetch texture and blend with constant color +// +PS_OUTPUT main( in PS_INPUT In ) +{ + PS_OUTPUT Out; //create an output pixel + + float amount = tintRGB[3]; + float4 pixel = tex2D(Tex0, In.Texture); // tint should be the RGB conversion of the HSL value, with 100% L + float lum = GetLuminance(pixel); + + // scale the colour by the luma, alpha blend it with the tint + Out.Color = (tintRGB * lum * amount + pixel*(1-amount)) * transparency[1]; + Out.Color[3] = pixel[3] * transparency[0]; + + return Out; //return output pixel +} diff --git a/engines/ags/engine/resource/tintshader.fxo b/engines/ags/engine/resource/tintshader.fxo new file mode 100644 index 00000000000..20667ce81a6 Binary files /dev/null and b/engines/ags/engine/resource/tintshader.fxo differ diff --git a/engines/ags/engine/resource/tintshaderLegacy.fx b/engines/ags/engine/resource/tintshaderLegacy.fx new file mode 100644 index 00000000000..82f89077c3f --- /dev/null +++ b/engines/ags/engine/resource/tintshaderLegacy.fx @@ -0,0 +1,72 @@ + +float GetValue(float4 color) +{ + float colorMax = max (color[0], color[1]); + colorMax = max (colorMax, color[2]); + + return colorMax; +} + +float4 hsv_to_rgb(float3 HSV) +{ + float4 RGB = HSV.z; + if ( HSV.y != 0 ) { + float var_h = HSV.x * 6; + float var_i = floor(var_h); // Or ... var_i = floor( var_h ) + float var_1 = HSV.z * (1.0 - HSV.y); + float var_2 = HSV.z * (1.0 - HSV.y * (var_h-var_i)); + float var_3 = HSV.z * (1.0 - HSV.y * (1-(var_h-var_i))); + if (var_i < 1.0) { RGB = float4(HSV.z, var_3, var_1, 1.0); } + else if (var_i < 2.0) { RGB = float4(var_2, HSV.z, var_1, 1.0); } + else if (var_i < 3.0) { RGB = float4(var_1, HSV.z, var_3, 1.0); } + else if (var_i < 4.0) { RGB = float4(var_1, var_2, HSV.z, 1.0); } + else if (var_i < 5.0) { RGB = float4(var_3, var_1, HSV.z, 1.0); } + else { RGB = float4(HSV.z, var_1, var_2, 1.0); } + } + return (RGB); +} + +// Pixel shader input structure +struct PS_INPUT +{ + float2 Texture : TEXCOORD0; +}; + +// Pixel shader output structure +struct PS_OUTPUT +{ + float4 Color : COLOR0; +}; + +// Global variables +sampler2D Tex0; + +// tintHSV: parameters from AGS engine +// [0] = tint colour H +// [1] = tint colour S +// [2] = tint colour V +// [3] = tint saturation +const float4 tintHSV: register( c0 ); +// [0] = object transparency +// [1] = light level +const float4 transparency: register( c1 ); + +// Name: Simple Pixel Shader +// Type: Pixel shader +// Desc: Fetch texture and blend with constant color +// +PS_OUTPUT main( in PS_INPUT In ) +{ + PS_OUTPUT Out; //create an output pixel + + float amount = tintHSV[3]; + float4 pixel = tex2D(Tex0, In.Texture); + float lum = GetValue(pixel); + lum = max(lum - (1.0 - transparency[1]), 0.0); + + // scale the colour by the luma, alpha blend it with the tint + Out.Color = (hsv_to_rgb(float3(tintHSV[0],tintHSV[1],lum)) * amount + pixel*(1-amount)); + Out.Color[3] = pixel[3] * transparency[0]; + + return Out; //return output pixel +} diff --git a/engines/ags/engine/resource/tintshaderLegacy.fxo b/engines/ags/engine/resource/tintshaderLegacy.fxo new file mode 100644 index 00000000000..cf4cd7392c1 Binary files /dev/null and b/engines/ags/engine/resource/tintshaderLegacy.fxo differ diff --git a/engines/ags/engine/resource/version.rc b/engines/ags/engine/resource/version.rc new file mode 100644 index 00000000000..f22cb431833 --- /dev/null +++ b/engines/ags/engine/resource/version.rc @@ -0,0 +1,219 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include +#define IDC_STATIC -1 +#include "../../Common/core/def_version.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United Kingdom) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// DATA +// + +__GDF_XML DATA "DefaultGDF.gdf.xml" + +PIXEL_SHADER DATA "tintshader.fxo" + +PIXEL_SHADER_LEGACY DATA "tintshaderLegacy.fxo" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION ACI_VERSION_MSRC_DEF + PRODUCTVERSION ACI_VERSION_MSRC_DEF + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080904b0" + BEGIN + VALUE "Comments", "This game was created using AGS - http://www.adventuregamestudio.co.uk/" + VALUE "CompanyName", "AGS Engine by Chris Jones et al. " + VALUE "FileDescription", "Adventure Game Studio run-time engine " + VALUE "FileVersion", ACI_VERSION_STR + VALUE "InternalName", "acwin" + VALUE "LegalCopyright", "AGS Copyright (c) 1999-2010 Chris Jones and " ACI_COPYRIGHT_YEARS " others." + VALUE "OriginalFilename", "acwin.exe" + VALUE "ProductName", "Made with Adventure Game Studio" + VALUE "ProductVersion", ACI_VERSION_STR + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x809, 1200 + END +END + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include \r\n" + "#define IDC_STATIC -1\r\n" + "#include ""../../Common/core/def_version.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_ICON ICON "game-1.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_SETUP DIALOGEX 0, 0, 518, 295 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "AGS Game Settings" +FONT 8, "Tahoma", 400, 0, 0x0 +BEGIN + GROUPBOX "Graphics settings",IDC_GFXOPTIONS,7,7,247,131 + CONTROL "Game resolution: XXXX x XXXX",IDC_RESOLUTION,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,15,19,161,10 + CONTROL "Driver:",IDC_STATIC,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,14,34,35,10 + COMBOBOX IDC_GFXDRIVER,50,32,136,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "&Start in a windowed mode",IDC_WINDOWED,"Button",BS_AUTOCHECKBOX | BS_LEFT | BS_VCENTER | WS_TABSTOP,14,49,172,9 + CONTROL "Mode:",IDC_STATIC,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,14,66,21,8 + LTEXT "",IDC_GFXMODETEXT,50,66,135,8 + CONTROL "Fullscreen scale:",IDC_STATIC,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,14,84,56,10 + COMBOBOX IDC_FSSCALING,75,82,166,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Windowed scale:",IDC_STATIC,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,14,101,54,10 + COMBOBOX IDC_WINDOWSCALING,75,99,166,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Scaling method:",IDC_STATIC,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,14,118,56,10 + COMBOBOX IDC_GFXFILTER,75,116,166,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "Gameplay settings",IDC_STATIC,7,141,247,33 + CONTROL "Game language:",IDC_STATIC,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,15,155,56,10 + COMBOBOX IDC_LANGUAGE,71,153,170,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CTEXT "Static",IDC_VERSION,7,190,247,9 + DEFPUSHBUTTON "S&ave",IDOK,7,207,56,14 + PUSHBUTTON "&Save and Run",IDOKRUN,67,207,56,14 + PUSHBUTTON "Cancel",IDCANCEL,127,207,56,14 + PUSHBUTTON "Ad&vanced >>>",IDC_ADVANCED,188,207,67,14 + GROUPBOX "Graphics options",IDC_STATIC,264,7,247,69 + CONTROL "Vertical sync",IDC_VSYNC,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,271,20,135,9 + CONTROL "Use 85 Hz display (CRT monitors only)",IDC_REFRESH_85HZ, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,271,33,138,9 + CONTROL "Smooth scaled sprites",IDC_ANTIALIAS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,271,46,138,9 + CONTROL "Render sprites at screen resolution",IDC_RENDERATSCREENRES, + "Button",BS_AUTOCHECKBOX | BS_LEFT | BS_VCENTER | WS_TABSTOP,271,59,172,9 + GROUPBOX "Sound options",IDC_STATIC,264,79,247,76 + LTEXT "Digital Sound:",IDC_STATIC,271,94,46,9 + COMBOBOX IDC_DIGISOUND,320,92,176,70,CBS_DROPDOWNLIST | WS_GROUP | WS_TABSTOP + LTEXT "MIDI music:",IDC_STATIC,271,110,47,10 + COMBOBOX IDC_MIDIMUSIC,320,108,176,70,CBS_DROPDOWNLIST | WS_TABSTOP + CONTROL "Enable threaded audio",IDC_THREADEDAUDIO,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,271,126,160,9 + CONTROL "Use voice pack if available",IDC_VOICEPACK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,271,140,160,9 + GROUPBOX "Mouse options",IDC_STATIC,264,158,247,58 + CONTROL "Auto lock to window",IDC_MOUSE_AUTOLOCK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,271,173,80,10 + LTEXT "Cursor speed: Default",IDC_MOUSESPEED_TEXT,271,187,176,8 + CONTROL "",IDC_MOUSESPEED,"msctls_trackbar32",WS_TABSTOP,271,198,226,14 + GROUPBOX "Sprite cache",IDC_STATIC,264,219,247,33 + CONTROL "Maximum size:",IDC_STATIC,"Static",SS_LEFTNOWORDWRAP | WS_GROUP,271,233,50,10 + COMBOBOX IDC_SPRITECACHE,325,231,118,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + GROUPBOX "",IDC_STATIC,264,250,247,36 + CONTROL "Custom game saves path",IDC_CUSTOMSAVEDIRCHECK,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,271,256,165,10,WS_EX_TRANSPARENT + EDITTEXT IDC_CUSTOMSAVEDIR,271,269,202,14,ES_AUTOHSCROLL | ES_READONLY + PUSHBUTTON "...",IDC_CUSTOMSAVEDIRBTN,477,269,20,14 + COMBOBOX IDC_GFXMODE,50,64,191,100,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_SETUP, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 511 + VERTGUIDE, 254 + VERTGUIDE, 264 + TOPMARGIN, 7 + BOTTOMMARGIN, 288 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_SETUP AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +#endif // English (United Kingdom) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/engines/ags/engine/script/cc_error_engine.cpp b/engines/ags/engine/script/cc_error_engine.cpp new file mode 100644 index 00000000000..e69de29bb2d diff --git a/engines/ags/engine/script/cc_instance.cpp b/engines/ags/engine/script/cc_instance.cpp new file mode 100644 index 00000000000..affee68e773 --- /dev/null +++ b/engines/ags/engine/script/cc_instance.cpp @@ -0,0 +1,2003 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "ac/common.h" +#include "ac/dynobj/cc_dynamicarray.h" +#include "ac/dynobj/managedobjectpool.h" +#include "gui/guidefines.h" +#include "script/cc_error.h" +#include "script/cc_instance.h" +#include "debug/debug_log.h" +#include "debug/out.h" +#include "script/cc_options.h" +#include "script/script.h" +#include "script/script_runtime.h" +#include "script/systemimports.h" +#include "util/bbop.h" +#include "util/stream.h" +#include "util/misc.h" +#include "util/textstreamwriter.h" +#include "ac/dynobj/scriptstring.h" +#include "ac/dynobj/scriptuserobject.h" +#include "ac/statobj/agsstaticobject.h" +#include "ac/statobj/staticarray.h" +#include "ac/dynobj/cc_dynamicobject_addr_and_manager.h" +#include "util/memory.h" +#include "util/string_utils.h" // linux strnicmp definition + +using namespace AGS::Common; +using namespace AGS::Common::Memory; + +extern ccInstance *loadedInstances[MAX_LOADED_INSTANCES]; // in script/script_runtime +extern int gameHasBeenRestored; // in ac/game +extern ExecutingScript*curscript; // in script/script +extern int maxWhileLoops; +extern new_line_hook_type new_line_hook; + +extern ScriptString myScriptStringImpl; + +enum ScriptOpArgIsReg +{ + kScOpNoArgIsReg = 0, + kScOpArg1IsReg = 0x0001, + kScOpArg2IsReg = 0x0002, + kScOpArg3IsReg = 0x0004, + kScOpOneArgIsReg = kScOpArg1IsReg, + kScOpTwoArgsAreReg = kScOpArg1IsReg | kScOpArg2IsReg, + kScOpTreeArgsAreReg = kScOpArg1IsReg | kScOpArg2IsReg | kScOpArg3IsReg +}; + +struct ScriptCommandInfo +{ + ScriptCommandInfo(int32_t code, const char *cmdname, int arg_count, ScriptOpArgIsReg arg_is_reg) + { + Code = code; + CmdName = cmdname; + ArgCount = arg_count; + ArgIsReg[0] = (arg_is_reg & kScOpArg1IsReg) != 0; + ArgIsReg[1] = (arg_is_reg & kScOpArg2IsReg) != 0; + ArgIsReg[2] = (arg_is_reg & kScOpArg3IsReg) != 0; + } + + int32_t Code; + const char *CmdName; + int ArgCount; + bool ArgIsReg[3]; +}; + +const ScriptCommandInfo sccmd_info[CC_NUM_SCCMDS] = +{ + ScriptCommandInfo( 0 , "NULL" , 0, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_ADD , "addi" , 2, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_SUB , "subi" , 2, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_REGTOREG , "mov" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_WRITELIT , "memwritelit" , 2, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_RET , "ret" , 0, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_LITTOREG , "movl" , 2, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MEMREAD , "memread4" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MEMWRITE , "memwrite4" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MULREG , "mul" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_DIVREG , "div" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_ADDREG , "add" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_SUBREG , "sub" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_BITAND , "and" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_BITOR , "or" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_ISEQUAL , "cmpeq" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_NOTEQUAL , "cmpne" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_GREATER , "gt" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_LESSTHAN , "lt" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_GTE , "gte" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_LTE , "lte" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_AND , "land" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_OR , "lor" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_CALL , "call" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MEMREADB , "memread1" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MEMREADW , "memread2" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MEMWRITEB , "memwrite1" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MEMWRITEW , "memwrite2" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_JZ , "jzi" , 1, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_PUSHREG , "push" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_POPREG , "pop" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_JMP , "jmpi" , 1, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_MUL , "muli" , 2, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_CALLEXT , "farcall" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_PUSHREAL , "farpush" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_SUBREALSTACK , "farsubsp" , 1, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_LINENUM , "sourceline" , 1, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_CALLAS , "callscr" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_THISBASE , "thisaddr" , 1, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_NUMFUNCARGS , "setfuncargs" , 1, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_MODREG , "mod" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_XORREG , "xor" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_NOTREG , "not" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_SHIFTLEFT , "shl" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_SHIFTRIGHT , "shr" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_CALLOBJ , "callobj" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_CHECKBOUNDS , "checkbounds" , 2, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MEMWRITEPTR , "memwrite.ptr" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MEMREADPTR , "memread.ptr" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_MEMZEROPTR , "memwrite.ptr.0" , 0, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_MEMINITPTR , "meminit.ptr" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_LOADSPOFFS , "load.sp.offs" , 1, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_CHECKNULL , "checknull.ptr" , 0, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_FADD , "faddi" , 2, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_FSUB , "fsubi" , 2, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_FMULREG , "fmul" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_FDIVREG , "fdiv" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_FADDREG , "fadd" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_FSUBREG , "fsub" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_FGREATER , "fgt" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_FLESSTHAN , "flt" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_FGTE , "fgte" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_FLTE , "flte" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_ZEROMEMORY , "zeromem" , 1, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_CREATESTRING , "newstring" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_STRINGSEQUAL , "streq" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_STRINGSNOTEQ , "strne" , 2, kScOpTwoArgsAreReg ), + ScriptCommandInfo( SCMD_CHECKNULLREG , "checknull" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_LOOPCHECKOFF , "loopcheckoff" , 0, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_MEMZEROPTRND , "memwrite.ptr.0.nd" , 0, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_JNZ , "jnzi" , 1, kScOpNoArgIsReg ), + ScriptCommandInfo( SCMD_DYNAMICBOUNDS , "dynamicbounds" , 1, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_NEWARRAY , "newarray" , 3, kScOpOneArgIsReg ), + ScriptCommandInfo( SCMD_NEWUSEROBJECT , "newuserobject" , 2, kScOpOneArgIsReg ), +}; + +const char *regnames[] = { "null", "sp", "mar", "ax", "bx", "cx", "op", "dx" }; + +const char *fixupnames[] = { "null", "fix_gldata", "fix_func", "fix_string", "fix_import", "fix_datadata", "fix_stack" }; + +ccInstance *current_instance; +// [IKM] 2012-10-21: +// NOTE: This is temporary solution (*sigh*, one of many) which allows certain +// exported functions return value as a RuntimeScriptValue object; +// Of 2012-12-20: now used only for plugin exports +RuntimeScriptValue GlobalReturnValue; + +// Function call stack is used to temporarily store +// values before passing them to script function +#define MAX_FUNC_PARAMS 20 +// An inverted parameter stack +struct FunctionCallStack +{ + FunctionCallStack() + { + Head = MAX_FUNC_PARAMS - 1; + Count = 0; + } + + inline RuntimeScriptValue *GetHead() + { + return &Entries[Head]; + } + inline RuntimeScriptValue *GetTail() + { + return &Entries[Head + Count]; + } + + RuntimeScriptValue Entries[MAX_FUNC_PARAMS + 1]; + int Head; + int Count; +}; + + +ccInstance *ccInstance::GetCurrentInstance() +{ + return current_instance; +} + +ccInstance *ccInstance::CreateFromScript(PScript scri) +{ + return CreateEx(scri, nullptr); +} + +ccInstance *ccInstance::CreateEx(PScript scri, ccInstance * joined) +{ + // allocate and copy all the memory with data, code and strings across + ccInstance *cinst = new ccInstance(); + if (!cinst->_Create(scri, joined)) + { + delete cinst; + return nullptr; + } + + return cinst; +} + +ccInstance::ccInstance() +{ + flags = 0; + globaldata = nullptr; + globaldatasize = 0; + code = nullptr; + runningInst = nullptr; + codesize = 0; + strings = nullptr; + stringssize = 0; + exports = nullptr; + stack = nullptr; + num_stackentries = 0; + stackdata = nullptr; + stackdatasize = 0; + stackdata_ptr = nullptr; + pc = 0; + line_number = 0; + callStackSize = 0; + loadedInstanceId = 0; + returnValue = 0; + numimports = 0; + resolved_imports = nullptr; + code_fixups = nullptr; + + memset(callStackLineNumber, 0, sizeof(callStackLineNumber)); + memset(callStackAddr, 0, sizeof(callStackAddr)); + memset(callStackCodeInst, 0, sizeof(callStackCodeInst)); +} + +ccInstance::~ccInstance() +{ + Free(); +} + +ccInstance *ccInstance::Fork() +{ + return CreateEx(instanceof, this); +} + +void ccInstance::Abort() +{ + if ((this != nullptr) && (pc != 0)) + flags |= INSTF_ABORTED; +} + +void ccInstance::AbortAndDestroy() +{ + if (this != nullptr) { + Abort(); + flags |= INSTF_FREE; + } +} + +#define ASSERT_STACK_SPACE_AVAILABLE(N) \ + if (registers[SREG_SP].RValue + N - &stack[0] >= CC_STACK_SIZE) \ + { \ + cc_error("stack overflow"); \ + return -1; \ + } + +#define ASSERT_STACK_SIZE(N) \ + if (registers[SREG_SP].RValue - N < &stack[0]) \ + { \ + cc_error("stack underflow"); \ + return -1; \ + } + +int ccInstance::CallScriptFunction(const char *funcname, int32_t numargs, const RuntimeScriptValue *params) +{ + ccError = 0; + currentline = 0; + + if (numargs > 0 && !params) + { + cc_error("internal error in ccInstance::CallScriptFunction"); + return -1; // TODO: correct error value + } + + if ((numargs >= 20) || (numargs < 0)) { + cc_error("too many arguments to function"); + return -3; + } + + if (pc != 0) { + cc_error("instance already being executed"); + return -4; + } + + int32_t startat = -1; + int k; + char mangledName[200]; + sprintf(mangledName, "%s$", funcname); + + for (k = 0; k < instanceof->numexports; k++) { + char *thisExportName = instanceof->exports[k]; + int match = 0; + + // check for a mangled name match + if (strncmp(thisExportName, mangledName, strlen(mangledName)) == 0) { + // found, compare the number of parameters + char *numParams = thisExportName + strlen(mangledName); + if (atoi(numParams) != numargs) { + cc_error("wrong number of parameters to exported function '%s' (expected %d, supplied %d)", funcname, atoi(numParams), numargs); + return -1; + } + match = 1; + } + // check for an exact match (if the script was compiled with + // an older version) + if ((match == 1) || (strcmp(thisExportName, funcname) == 0)) { + int32_t etype = (instanceof->export_addr[k] >> 24L) & 0x000ff; + if (etype != EXPORT_FUNCTION) { + cc_error("symbol is not a function"); + return -1; + } + startat = (instanceof->export_addr[k] & 0x00ffffff); + break; + } + } + + if (startat < 0) { + cc_error("function '%s' not found", funcname); + return -2; + } + + //numargs++; // account for return address + flags &= ~INSTF_ABORTED; + + // object pointer needs to start zeroed + registers[SREG_OP].SetDynamicObject(nullptr, nullptr); + + ccInstance* currentInstanceWas = current_instance; + registers[SREG_SP].SetStackPtr( &stack[0] ); + stackdata_ptr = stackdata; + // NOTE: Pushing parameters to stack in reverse order + ASSERT_STACK_SPACE_AVAILABLE(numargs + 1 /* return address */) + for (int i = numargs - 1; i >= 0; --i) + { + PushValueToStack(params[i]); + } + PushValueToStack(RuntimeScriptValue().SetInt32(0)); // return address on stack + if (ccError) + { + return -1; + } + runningInst = this; + + int reterr = Run(startat); + ASSERT_STACK_SIZE(numargs); + PopValuesFromStack(numargs); + pc = 0; + current_instance = currentInstanceWas; + + // NOTE that if proper multithreading is added this will need + // to be reconsidered, since the GC could be run in the middle + // of a RET from a function or something where there is an + // object with ref count 0 that is in use + pool.RunGarbageCollectionIfAppropriate(); + + if (new_line_hook) + new_line_hook(nullptr, 0); + + if (reterr) + return -6; + + if (flags & INSTF_ABORTED) { + flags &= ~INSTF_ABORTED; + + if (flags & INSTF_FREE) + Free(); + return 100; + } + + if (registers[SREG_SP].RValue != &stack[0]) { + cc_error("stack pointer was not zero at completion of script"); + return -5; + } + return ccError; +} + +// Macros to maintain the call stack +#define PUSH_CALL_STACK \ + if (callStackSize >= MAX_CALL_STACK) { \ + cc_error("CallScriptFunction stack overflow (recursive call error?)"); \ + return -1; \ + } \ + callStackLineNumber[callStackSize] = line_number; \ + callStackCodeInst[callStackSize] = runningInst; \ + callStackAddr[callStackSize] = pc; \ + callStackSize++ + +#define POP_CALL_STACK \ + if (callStackSize < 1) { \ + cc_error("CallScriptFunction stack underflow -- internal error"); \ + return -1; \ + } \ + callStackSize--;\ + line_number = callStackLineNumber[callStackSize];\ + currentline = line_number + +#define MAXNEST 50 // number of recursive function calls allowed +int ccInstance::Run(int32_t curpc) +{ + pc = curpc; + returnValue = -1; + + if ((curpc < 0) || (curpc >= runningInst->codesize)) { + cc_error("specified code offset is not valid"); + return -1; + } + + int32_t thisbase[MAXNEST], funcstart[MAXNEST]; + int was_just_callas = -1; + int curnest = 0; + int loopIterations = 0; + int num_args_to_func = -1; + int next_call_needs_object = 0; + int loopIterationCheckDisabled = 0; + thisbase[0] = 0; + funcstart[0] = pc; + current_instance = this; + ccInstance *codeInst = runningInst; + int write_debug_dump = ccGetOption(SCOPT_DEBUGRUN); + ScriptOperation codeOp; + + FunctionCallStack func_callstack; + + while (1) { + + /* + if (!codeInst->ReadOperation(codeOp, pc)) + { + return -1; + } + */ + /* ReadOperation */ + //===================================================================== + codeOp.Instruction.Code = codeInst->code[pc]; + codeOp.Instruction.InstanceId = (codeOp.Instruction.Code >> INSTANCE_ID_SHIFT) & INSTANCE_ID_MASK; + codeOp.Instruction.Code &= INSTANCE_ID_REMOVEMASK; // now this is pure instruction code + + if (codeOp.Instruction.Code < 0 || codeOp.Instruction.Code >= CC_NUM_SCCMDS) + { + cc_error("invalid instruction %d found in code stream", codeOp.Instruction.Code); + return -1; + } + + codeOp.ArgCount = sccmd_info[codeOp.Instruction.Code].ArgCount; + if (pc + codeOp.ArgCount >= codeInst->codesize) + { + cc_error("unexpected end of code data (%d; %d)", pc + codeOp.ArgCount, codeInst->codesize); + return -1; + } + + int pc_at = pc + 1; + for (int i = 0; i < codeOp.ArgCount; ++i, ++pc_at) + { + char fixup = codeInst->code_fixups[pc_at]; + if (fixup > 0) + { + // could be relative pointer or import address + /* + if (!FixupArgument(code[pc], fixup, codeOp.Args[i])) + { + return -1; + } + */ + /* FixupArgument */ + //===================================================================== + switch (fixup) + { + case FIXUP_GLOBALDATA: + { + ScriptVariable *gl_var = (ScriptVariable*)codeInst->code[pc_at]; + codeOp.Args[i].SetGlobalVar(&gl_var->RValue); + } + break; + case FIXUP_FUNCTION: + // originally commented -- CHECKME: could this be used in very old versions of AGS? + // code[fixup] += (long)&code[0]; + // This is a program counter value, presumably will be used as SCMD_CALL argument + codeOp.Args[i].SetInt32((int32_t)codeInst->code[pc_at]); + break; + case FIXUP_STRING: + codeOp.Args[i].SetStringLiteral(&codeInst->strings[0] + codeInst->code[pc_at]); + break; + case FIXUP_IMPORT: + { + const ScriptImport *import = simp.getByIndex((int32_t)codeInst->code[pc_at]); + if (import) + { + codeOp.Args[i] = import->Value; + } + else + { + cc_error("cannot resolve import, key = %ld", codeInst->code[pc_at]); + return -1; + } + } + break; + case FIXUP_STACK: + codeOp.Args[i] = GetStackPtrOffsetFw((int32_t)codeInst->code[pc_at]); + break; + default: + cc_error("internal fixup type error: %d", fixup); + return -1; + } + /* End FixupArgument */ + //===================================================================== + } + else + { + // should be a numeric literal (int32 or float) + codeOp.Args[i].SetInt32( (int32_t)codeInst->code[pc_at] ); + } + } + /* End ReadOperation */ + //===================================================================== + + // save the arguments for quick access + RuntimeScriptValue &arg1 = codeOp.Args[0]; + RuntimeScriptValue &arg2 = codeOp.Args[1]; + RuntimeScriptValue &arg3 = codeOp.Args[2]; + RuntimeScriptValue ®1 = + registers[arg1.IValue >= 0 && arg1.IValue < CC_NUM_REGISTERS ? arg1.IValue : 0]; + RuntimeScriptValue ®2 = + registers[arg2.IValue >= 0 && arg2.IValue < CC_NUM_REGISTERS ? arg2.IValue : 0]; + + const char *direct_ptr1; + const char *direct_ptr2; + + if (write_debug_dump) + { + DumpInstruction(codeOp); + } + + switch (codeOp.Instruction.Code) { + case SCMD_LINENUM: + line_number = arg1.IValue; + currentline = arg1.IValue; + if (new_line_hook) + new_line_hook(this, currentline); + break; + case SCMD_ADD: + // If the the register is SREG_SP, we are allocating new variable on the stack + if (arg1.IValue == SREG_SP) + { + // Only allocate new data if current stack entry is invalid; + // in some cases this may be advancing over value that was written by MEMWRITE* + ASSERT_STACK_SPACE_AVAILABLE(1); + if (reg1.RValue->IsValid()) + { + // TODO: perhaps should add a flag here to ensure this happens only after MEMWRITE-ing to stack + registers[SREG_SP].RValue++; + } + else + { + PushDataToStack(arg2.IValue); + if (ccError) + { + return -1; + } + } + } + else + { + reg1.IValue += arg2.IValue; + } + break; + case SCMD_SUB: + if (reg1.Type == kScValStackPtr) + { + // If this is SREG_SP, this is stack pop, which frees local variables; + // Other than SREG_SP this may be AGS 2.x method to offset stack in SREG_MAR; + // quote JJS: + // // AGS 2.x games also perform relative stack access by copying SREG_SP to SREG_MAR + // // and then subtracting from that. + if (arg1.IValue == SREG_SP) + { + PopDataFromStack(arg2.IValue); + } + else + { + // This is practically LOADSPOFFS + reg1 = GetStackPtrOffsetRw(arg2.IValue); + } + if (ccError) + { + return -1; + } + } + else + { + reg1.IValue -= arg2.IValue; + } + break; + case SCMD_REGTOREG: + reg2 = reg1; + break; + case SCMD_WRITELIT: + // Take the data address from reg[MAR] and copy there arg1 bytes from arg2 address + // + // NOTE: since it reads directly from arg2 (which originally was + // long, or rather int32 due x32 build), written value may normally + // be only up to 4 bytes large; + // I guess that's an obsolete way to do WRITE, WRITEW and WRITEB + switch (arg1.IValue) + { + case sizeof(char): + registers[SREG_MAR].WriteByte(arg2.IValue); + break; + case sizeof(int16_t): + registers[SREG_MAR].WriteInt16(arg2.IValue); + break; + case sizeof(int32_t): + // We do not know if this is math integer or some pointer, etc + registers[SREG_MAR].WriteValue(arg2); + break; + default: + cc_error("unexpected data size for WRITELIT op: %d", arg1.IValue); + break; + } + break; + case SCMD_RET: + { + if (loopIterationCheckDisabled > 0) + loopIterationCheckDisabled--; + + ASSERT_STACK_SIZE(1); + RuntimeScriptValue rval = PopValueFromStack(); + curnest--; + pc = rval.IValue; + if (pc == 0) + { + returnValue = registers[SREG_AX].IValue; + return 0; + } + current_instance = this; + POP_CALL_STACK; + continue; // continue so that the PC doesn't get overwritten + } + case SCMD_LITTOREG: + reg1 = arg2; + break; + case SCMD_MEMREAD: + // Take the data address from reg[MAR] and copy int32_t to reg[arg1] + reg1 = registers[SREG_MAR].ReadValue(); + break; + case SCMD_MEMWRITE: + // Take the data address from reg[MAR] and copy there int32_t from reg[arg1] + registers[SREG_MAR].WriteValue(reg1); + break; + case SCMD_LOADSPOFFS: + registers[SREG_MAR] = GetStackPtrOffsetRw(arg1.IValue); + if (ccError) + { + return -1; + } + break; + + // 64 bit: Force 32 bit math + case SCMD_MULREG: + reg1.SetInt32(reg1.IValue * reg2.IValue); + break; + case SCMD_DIVREG: + if (reg2.IValue == 0) { + cc_error("!Integer divide by zero"); + return -1; + } + reg1.SetInt32(reg1.IValue / reg2.IValue); + break; + case SCMD_ADDREG: + // This may be pointer arithmetics, in which case IValue stores offset from base pointer + reg1.IValue += reg2.IValue; + break; + case SCMD_SUBREG: + // This may be pointer arithmetics, in which case IValue stores offset from base pointer + reg1.IValue -= reg2.IValue; + break; + case SCMD_BITAND: + reg1.SetInt32(reg1.IValue & reg2.IValue); + break; + case SCMD_BITOR: + reg1.SetInt32(reg1.IValue | reg2.IValue); + break; + case SCMD_ISEQUAL: + reg1.SetInt32AsBool(reg1 == reg2); + break; + case SCMD_NOTEQUAL: + reg1.SetInt32AsBool(reg1 != reg2); + break; + case SCMD_GREATER: + reg1.SetInt32AsBool(reg1.IValue > reg2.IValue); + break; + case SCMD_LESSTHAN: + reg1.SetInt32AsBool(reg1.IValue < reg2.IValue); + break; + case SCMD_GTE: + reg1.SetInt32AsBool(reg1.IValue >= reg2.IValue); + break; + case SCMD_LTE: + reg1.SetInt32AsBool(reg1.IValue <= reg2.IValue); + break; + case SCMD_AND: + reg1.SetInt32AsBool(reg1.IValue && reg2.IValue); + break; + case SCMD_OR: + reg1.SetInt32AsBool(reg1.IValue || reg2.IValue); + break; + case SCMD_XORREG: + reg1.SetInt32(reg1.IValue ^ reg2.IValue); + break; + case SCMD_MODREG: + if (reg2.IValue == 0) { + cc_error("!Integer divide by zero"); + return -1; + } + reg1.SetInt32(reg1.IValue % reg2.IValue); + break; + case SCMD_NOTREG: + reg1 = !(reg1); + break; + case SCMD_CALL: + // CallScriptFunction another function within same script, just save PC + // and continue from there + if (curnest >= MAXNEST - 1) { + cc_error("!call stack overflow, recursive call problem?"); + return -1; + } + + PUSH_CALL_STACK; + + ASSERT_STACK_SPACE_AVAILABLE(1); + PushValueToStack(RuntimeScriptValue().SetInt32(pc + codeOp.ArgCount + 1)); + if (ccError) + { + return -1; + } + + if (thisbase[curnest] == 0) + pc = reg1.IValue; + else { + pc = funcstart[curnest]; + pc += (reg1.IValue - thisbase[curnest]); + } + + next_call_needs_object = 0; + + if (loopIterationCheckDisabled) + loopIterationCheckDisabled++; + + curnest++; + thisbase[curnest] = 0; + funcstart[curnest] = pc; + continue; // continue so that the PC doesn't get overwritten + case SCMD_MEMREADB: + // Take the data address from reg[MAR] and copy byte to reg[arg1] + reg1.SetUInt8(registers[SREG_MAR].ReadByte()); + break; + case SCMD_MEMREADW: + // Take the data address from reg[MAR] and copy int16_t to reg[arg1] + reg1.SetInt16(registers[SREG_MAR].ReadInt16()); + break; + case SCMD_MEMWRITEB: + // Take the data address from reg[MAR] and copy there byte from reg[arg1] + registers[SREG_MAR].WriteByte(reg1.IValue); + break; + case SCMD_MEMWRITEW: + // Take the data address from reg[MAR] and copy there int16_t from reg[arg1] + registers[SREG_MAR].WriteInt16(reg1.IValue); + break; + case SCMD_JZ: + if (registers[SREG_AX].IsNull()) + pc += arg1.IValue; + break; + case SCMD_JNZ: + if (!registers[SREG_AX].IsNull()) + pc += arg1.IValue; + break; + case SCMD_PUSHREG: + // Push reg[arg1] value to the stack + ASSERT_STACK_SPACE_AVAILABLE(1); + PushValueToStack(reg1); + if (ccError) + { + return -1; + } + break; + case SCMD_POPREG: + ASSERT_STACK_SIZE(1); + reg1 = PopValueFromStack(); + break; + case SCMD_JMP: + pc += arg1.IValue; + + if ((arg1.IValue < 0) && (maxWhileLoops > 0) && (loopIterationCheckDisabled == 0)) { + // Make sure it's not stuck in a While loop + loopIterations ++; + if (flags & INSTF_RUNNING) { + loopIterations = 0; + flags &= ~INSTF_RUNNING; + } + else if (loopIterations > maxWhileLoops) { + cc_error("!Script appears to be hung (a while loop ran %d times). The problem may be in a calling function; check the call stack.", loopIterations); + return -1; + } + } + break; + case SCMD_MUL: + reg1.IValue *= arg2.IValue; + break; + case SCMD_CHECKBOUNDS: + if ((reg1.IValue < 0) || + (reg1.IValue >= arg2.IValue)) { + cc_error("!Array index out of bounds (index: %d, bounds: 0..%d)", reg1.IValue, arg2.IValue - 1); + return -1; + } + break; + case SCMD_DYNAMICBOUNDS: + { + // TODO: test reg[MAR] type here; + // That might be dynamic object, but also a non-managed dynamic array, "allocated" + // on global or local memspace (buffer) + int32_t upperBoundInBytes = *((int32_t *)(registers[SREG_MAR].GetPtrWithOffset() - 4)); + if ((reg1.IValue < 0) || + (reg1.IValue >= upperBoundInBytes)) { + int32_t upperBound = *((int32_t *)(registers[SREG_MAR].GetPtrWithOffset() - 8)) & (~ARRAY_MANAGED_TYPE_FLAG); + if (upperBound <= 0) + { + cc_error("!Array has an invalid size (%d) and cannot be accessed", upperBound); + } + else + { + int elementSize = (upperBoundInBytes / upperBound); + cc_error("!Array index out of bounds (index: %d, bounds: 0..%d)", reg1.IValue / elementSize, upperBound - 1); + } + return -1; + } + break; + } + + // 64 bit: Handles are always 32 bit values. They are not C pointer. + + case SCMD_MEMREADPTR: { + ccError = 0; + + int32_t handle = registers[SREG_MAR].ReadInt32(); + void *object; + ICCDynamicObject *manager; + ScriptValueType obj_type = ccGetObjectAddressAndManagerFromHandle(handle, object, manager); + if (obj_type == kScValPluginObject) + { + reg1.SetPluginObject( object, manager ); + } + else + { + reg1.SetDynamicObject( object, manager ); + } + + // if error occurred, cc_error will have been set + if (ccError) + return -1; + break; } + case SCMD_MEMWRITEPTR: { + + int32_t handle = registers[SREG_MAR].ReadInt32(); + char *address = nullptr; + + if (reg1.Type == kScValStaticArray && reg1.StcArr->GetDynamicManager()) + { + address = (char*)reg1.StcArr->GetElementPtr(reg1.Ptr, reg1.IValue); + } + else if (reg1.Type == kScValDynamicObject || + reg1.Type == kScValPluginObject) + { + address = reg1.Ptr; + } + else if (reg1.Type == kScValPluginArg) + {// TODO: plugin API is currently strictly 32-bit, so this may break on 64-bit systems + address = Int32ToPtr(reg1.IValue); + } + // There's one possible case when the reg1 is 0, which means writing nullptr + else if (!reg1.IsNull()) + { + cc_error("internal error: MEMWRITEPTR argument is not dynamic object"); + return -1; + } + + int32_t newHandle = ccGetObjectHandleFromAddress(address); + if (newHandle == -1) + return -1; + + if (handle != newHandle) { + ccReleaseObjectReference(handle); + ccAddObjectReference(newHandle); + registers[SREG_MAR].WriteInt32(newHandle); + } + break; + } + case SCMD_MEMINITPTR: { + char *address = nullptr; + + if (reg1.Type == kScValStaticArray && reg1.StcArr->GetDynamicManager()) + { + address = (char*)reg1.StcArr->GetElementPtr(reg1.Ptr, reg1.IValue); + } + else if (reg1.Type == kScValDynamicObject || + reg1.Type == kScValPluginObject) + { + address = reg1.Ptr; + } + else if (reg1.Type == kScValPluginArg) + {// TODO: plugin API is currently strictly 32-bit, so this may break on 64-bit systems + address = Int32ToPtr(reg1.IValue); + } + // There's one possible case when the reg1 is 0, which means writing nullptr + else if (!reg1.IsNull()) + { + cc_error("internal error: SCMD_MEMINITPTR argument is not dynamic object"); + return -1; + } + // like memwriteptr, but doesn't attempt to free the old one + int32_t newHandle = ccGetObjectHandleFromAddress(address); + if (newHandle == -1) + return -1; + + ccAddObjectReference(newHandle); + registers[SREG_MAR].WriteInt32(newHandle); + break; + } + case SCMD_MEMZEROPTR: { + int32_t handle = registers[SREG_MAR].ReadInt32(); + ccReleaseObjectReference(handle); + registers[SREG_MAR].WriteInt32(0); + break; + } + case SCMD_MEMZEROPTRND: { + int32_t handle = registers[SREG_MAR].ReadInt32(); + + // don't do the Dispose check for the object being returned -- this is + // for returning a String (or other pointer) from a custom function. + // Note: we might be freeing a dynamic array which contains the DisableDispose + // object, that will be handled inside the recursive call to SubRef. + // CHECKME!! what type of data may reg1 point to? + pool.disableDisposeForObject = (const char*)registers[SREG_AX].Ptr; + ccReleaseObjectReference(handle); + pool.disableDisposeForObject = nullptr; + registers[SREG_MAR].WriteInt32(0); + break; + } + case SCMD_CHECKNULL: + if (registers[SREG_MAR].IsNull()) { + cc_error("!Null pointer referenced"); + return -1; + } + break; + case SCMD_CHECKNULLREG: + if (reg1.IsNull()) { + cc_error("!Null string referenced"); + return -1; + } + break; + case SCMD_NUMFUNCARGS: + num_args_to_func = arg1.IValue; + break; + case SCMD_CALLAS:{ + PUSH_CALL_STACK; + + // CallScriptFunction to a function in another script + + // If there are nested CALLAS calls, the stack might + // contain 2 calls worth of parameters, so only + // push args for this call + if (num_args_to_func < 0) + { + num_args_to_func = func_callstack.Count; + } + ASSERT_STACK_SPACE_AVAILABLE(num_args_to_func + 1 /* return address */); + for (const RuntimeScriptValue *prval = func_callstack.GetHead() + num_args_to_func; + prval > func_callstack.GetHead(); --prval) + { + PushValueToStack(*prval); + } + + // 0, so that the cc_run_code returns + RuntimeScriptValue oldstack = registers[SREG_SP]; + PushValueToStack(RuntimeScriptValue().SetInt32(0)); + if (ccError) + { + return -1; + } + + int oldpc = pc; + ccInstance *wasRunning = runningInst; + + // extract the instance ID + int32_t instId = codeOp.Instruction.InstanceId; + // determine the offset into the code of the instance we want + runningInst = loadedInstances[instId]; + intptr_t callAddr = reg1.Ptr - (char*)&runningInst->code[0]; + if (callAddr % sizeof(intptr_t) != 0) { + cc_error("call address not aligned"); + return -1; + } + callAddr /= sizeof(intptr_t); // size of ccScript::code elements + + if (Run((int32_t)callAddr)) + return -1; + + runningInst = wasRunning; + + if (flags & INSTF_ABORTED) + return 0; + + if (oldstack != registers[SREG_SP]) { + cc_error("stack corrupt after function call"); + return -1; + } + + next_call_needs_object = 0; + + pc = oldpc; + was_just_callas = func_callstack.Count; + num_args_to_func = -1; + POP_CALL_STACK; + break; + } + case SCMD_CALLEXT: { + // CallScriptFunction to a real 'C' code function + was_just_callas = -1; + if (num_args_to_func < 0) + { + num_args_to_func = func_callstack.Count; + } + + // Convert pointer arguments to simple types + for (RuntimeScriptValue *prval = func_callstack.GetHead() + num_args_to_func; + prval > func_callstack.GetHead(); --prval) + { + prval->DirectPtr(); + } + + RuntimeScriptValue return_value; + + if (reg1.Type == kScValPluginFunction) + { + GlobalReturnValue.Invalidate(); + int32_t int_ret_val; + if (next_call_needs_object) + { + RuntimeScriptValue obj_rval = registers[SREG_OP]; + obj_rval.DirectPtrObj(); + int_ret_val = call_function((intptr_t)reg1.Ptr, &obj_rval, num_args_to_func, func_callstack.GetHead() + 1); + } + else + { + int_ret_val = call_function((intptr_t)reg1.Ptr, nullptr, num_args_to_func, func_callstack.GetHead() + 1); + } + + if (GlobalReturnValue.IsValid()) + { + return_value = GlobalReturnValue; + } + else + { + return_value.SetPluginArgument(int_ret_val); + } + } + else if (next_call_needs_object) + { + // member function call + if (reg1.Type == kScValObjectFunction) + { + RuntimeScriptValue obj_rval = registers[SREG_OP]; + obj_rval.DirectPtrObj(); + return_value = reg1.ObjPfn(obj_rval.Ptr, func_callstack.GetHead() + 1, num_args_to_func); + } + else + { + cc_error("invalid pointer type for object function call: %d", reg1.Type); + } + } + else if (reg1.Type == kScValStaticFunction) + { + return_value = reg1.SPfn(func_callstack.GetHead() + 1, num_args_to_func); + } + else if (reg1.Type == kScValObjectFunction) + { + cc_error("unexpected object function pointer on SCMD_CALLEXT"); + } + else + { + cc_error("invalid pointer type for function call: %d", reg1.Type); + } + + if (ccError) + { + return -1; + } + + registers[SREG_AX] = return_value; + current_instance = this; + next_call_needs_object = 0; + num_args_to_func = -1; + break; + } + case SCMD_PUSHREAL: + PushToFuncCallStack(func_callstack, reg1); + break; + case SCMD_SUBREALSTACK: + PopFromFuncCallStack(func_callstack, arg1.IValue); + if (was_just_callas >= 0) + { + ASSERT_STACK_SIZE(arg1.IValue); + PopValuesFromStack(arg1.IValue); + was_just_callas = -1; + } + break; + case SCMD_CALLOBJ: + // set the OP register + if (reg1.IsNull()) { + cc_error("!Null pointer referenced"); + return -1; + } + switch (reg1.Type) + { + // This might be a static object, passed to the user-defined extender function + case kScValStaticObject: + case kScValDynamicObject: + case kScValPluginObject: + case kScValPluginArg: + // This might be an object of USER-DEFINED type, calling its MEMBER-FUNCTION. + // Note, that this is the only case known when such object is written into reg[SREG_OP]; + // in any other case that would count as error. + case kScValGlobalVar: + case kScValStackPtr: + registers[SREG_OP] = reg1; + break; + case kScValStaticArray: + if (reg1.StcArr->GetDynamicManager()) + { + registers[SREG_OP].SetDynamicObject( + (char*)reg1.StcArr->GetElementPtr(reg1.Ptr, reg1.IValue), + reg1.StcArr->GetDynamicManager()); + break; + } + // fall-through intended + default: + cc_error("internal error: SCMD_CALLOBJ argument is not an object of built-in or user-defined type"); + return -1; + } + next_call_needs_object = 1; + break; + case SCMD_SHIFTLEFT: + reg1.SetInt32(reg1.IValue << reg2.IValue); + break; + case SCMD_SHIFTRIGHT: + reg1.SetInt32(reg1.IValue >> reg2.IValue); + break; + case SCMD_THISBASE: + thisbase[curnest] = arg1.IValue; + break; + case SCMD_NEWARRAY: + { + int numElements = reg1.IValue; + if (numElements < 1) + { + cc_error("invalid size for dynamic array; requested: %d, range: 1..%d", numElements, INT32_MAX); + return -1; + } + DynObjectRef ref = globalDynamicArray.Create(numElements, arg2.IValue, arg3.GetAsBool()); + reg1.SetDynamicObject(ref.second, &globalDynamicArray); + break; + } + case SCMD_NEWUSEROBJECT: + { + const int32_t size = arg2.IValue; + if (size < 0) + { + cc_error("Invalid size for user object; requested: %u (or %d), range: 0..%d", (uint32_t)size, size, INT_MAX); + return -1; + } + ScriptUserObject *suo = ScriptUserObject::CreateManaged(size); + reg1.SetDynamicObject(suo, suo); + break; + } + case SCMD_FADD: + reg1.SetFloat(reg1.FValue + arg2.IValue); // arg2 was used as int here originally + break; + case SCMD_FSUB: + reg1.SetFloat(reg1.FValue - arg2.IValue); // arg2 was used as int here originally + break; + case SCMD_FMULREG: + reg1.SetFloat(reg1.FValue * reg2.FValue); + break; + case SCMD_FDIVREG: + if (reg2.FValue == 0.0) { + cc_error("!Floating point divide by zero"); + return -1; + } + reg1.SetFloat(reg1.FValue / reg2.FValue); + break; + case SCMD_FADDREG: + reg1.SetFloat(reg1.FValue + reg2.FValue); + break; + case SCMD_FSUBREG: + reg1.SetFloat(reg1.FValue - reg2.FValue); + break; + case SCMD_FGREATER: + reg1.SetFloatAsBool(reg1.FValue > reg2.FValue); + break; + case SCMD_FLESSTHAN: + reg1.SetFloatAsBool(reg1.FValue < reg2.FValue); + break; + case SCMD_FGTE: + reg1.SetFloatAsBool(reg1.FValue >= reg2.FValue); + break; + case SCMD_FLTE: + reg1.SetFloatAsBool(reg1.FValue <= reg2.FValue); + break; + case SCMD_ZEROMEMORY: + // Check if we are zeroing at stack tail + if (registers[SREG_MAR] == registers[SREG_SP]) { + // creating a local variable -- check the stack to ensure no mem overrun + int currentStackSize = registers[SREG_SP].RValue - &stack[0]; + int currentDataSize = stackdata_ptr - stackdata; + if (currentStackSize + 1 >= CC_STACK_SIZE || + currentDataSize + arg1.IValue >= CC_STACK_DATA_SIZE) + { + cc_error("stack overflow, attempted grow to %d bytes", currentDataSize + arg1.IValue); + return -1; + } + // NOTE: according to compiler's logic, this is always followed + // by SCMD_ADD, and that is where the data is "allocated", here we + // just clean the place. + // CHECKME -- since we zero memory in PushDataToStack anyway, this is not needed at all? + memset(stackdata_ptr, 0, arg1.IValue); + } + else + { + cc_error("internal error: stack tail address expected on SCMD_ZEROMEMORY instruction, reg[MAR] type is %d", + registers[SREG_MAR].Type); + return -1; + } + break; + case SCMD_CREATESTRING: + if (stringClassImpl == nullptr) { + cc_error("No string class implementation set, but opcode was used"); + return -1; + } + direct_ptr1 = (const char*)reg1.GetDirectPtr(); + reg1.SetDynamicObject( + stringClassImpl->CreateString(direct_ptr1).second, + &myScriptStringImpl); + break; + case SCMD_STRINGSEQUAL: + if ((reg1.IsNull()) || (reg2.IsNull())) { + cc_error("!Null pointer referenced"); + return -1; + } + direct_ptr1 = (const char*)reg1.GetDirectPtr(); + direct_ptr2 = (const char*)reg2.GetDirectPtr(); + reg1.SetInt32AsBool(strcmp(direct_ptr1, direct_ptr2) == 0); + + break; + case SCMD_STRINGSNOTEQ: + if ((reg1.IsNull()) || (reg2.IsNull())) { + cc_error("!Null pointer referenced"); + return -1; + } + direct_ptr1 = (const char*)reg1.GetDirectPtr(); + direct_ptr2 = (const char*)reg2.GetDirectPtr(); + reg1.SetInt32AsBool(strcmp(direct_ptr1, direct_ptr2) != 0 ); + break; + case SCMD_LOOPCHECKOFF: + if (loopIterationCheckDisabled == 0) + loopIterationCheckDisabled++; + break; + default: + cc_error("instruction %d is not implemented", codeOp.Instruction.Code); + return -1; + } + + if (flags & INSTF_ABORTED) + return 0; + + pc += codeOp.ArgCount + 1; + } +} + +String ccInstance::GetCallStack(int maxLines) +{ + String buffer = String::FromFormat("in \"%s\", line %d\n", runningInst->instanceof->GetSectionName(pc), line_number); + + int linesDone = 0; + for (int j = callStackSize - 1; (j >= 0) && (linesDone < maxLines); j--, linesDone++) + { + String lineBuffer = String::FromFormat("from \"%s\", line %d\n", + callStackCodeInst[j]->instanceof->GetSectionName(callStackAddr[j]), callStackLineNumber[j]); + buffer.Append(lineBuffer); + if (linesDone == maxLines - 1) + buffer.Append("(and more...)\n"); + } + return buffer; +} + +void ccInstance::GetScriptPosition(ScriptPosition &script_pos) +{ + script_pos.Section = runningInst->instanceof->GetSectionName(pc); + script_pos.Line = line_number; +} + +// get a pointer to a variable or function exported by the script +RuntimeScriptValue ccInstance::GetSymbolAddress(const char *symname) +{ + int k; + char altName[200]; + sprintf(altName, "%s$", symname); + RuntimeScriptValue rval_null; + + for (k = 0; k < instanceof->numexports; k++) { + if (strcmp(instanceof->exports[k], symname) == 0) + return exports[k]; + // mangled function name + if (strncmp(instanceof->exports[k], altName, strlen(altName)) == 0) + return exports[k]; + } + return rval_null; +} + +void ccInstance::DumpInstruction(const ScriptOperation &op) +{ + // line_num local var should be shared between all the instances + static int line_num = 0; + + if (op.Instruction.Code == SCMD_LINENUM) + { + line_num = op.Args[0].IValue; + return; + } + + Stream *data_s = ci_fopen("script.log", kFile_Create, kFile_Write); + TextStreamWriter writer(data_s); + writer.WriteFormat("Line %3d, IP:%8d (SP:%p) ", line_num, pc, registers[SREG_SP].RValue); + + const ScriptCommandInfo &cmd_info = sccmd_info[op.Instruction.Code]; + writer.WriteString(cmd_info.CmdName); + + for (int i = 0; i < cmd_info.ArgCount; ++i) + { + if (i > 0) + { + writer.WriteChar(','); + } + if (cmd_info.ArgIsReg[i]) + { + writer.WriteFormat(" %s", regnames[op.Args[i].IValue]); + } + else + { + // MACPORT FIX 9/6/5: changed %d to %ld + // FIXME: check type and write appropriate values + writer.WriteFormat(" %ld", op.Args[i].GetPtrWithOffset()); + } + } + writer.WriteLineBreak(); + // the writer will delete data stream internally +} + +bool ccInstance::IsBeingRun() const +{ + return pc != 0; +} + +bool ccInstance::_Create(PScript scri, ccInstance * joined) +{ + int i; + currentline = -1; + if ((scri == nullptr) && (joined != nullptr)) + scri = joined->instanceof; + + if (scri == nullptr) { + cc_error("null pointer passed"); + return false; + } + + if (joined != nullptr) { + // share memory space with an existing instance (ie. this is a thread/fork) + globalvars = joined->globalvars; + globaldatasize = joined->globaldatasize; + globaldata = joined->globaldata; + code = joined->code; + codesize = joined->codesize; + } + else { + // create own memory space + // NOTE: globalvars are created in CreateGlobalVars() + globalvars.reset(new ScVarMap()); + globaldatasize = scri->globaldatasize; + globaldata = nullptr; + if (globaldatasize > 0) + { + globaldata = (char *)malloc(globaldatasize); + memcpy(globaldata, scri->globaldata, globaldatasize); + } + + codesize = scri->codesize; + code = nullptr; + if (codesize > 0) + { + code = (intptr_t*)malloc(codesize * sizeof(intptr_t)); + // 64 bit: Read code into 8 byte array, necessary for being able to perform + // relocations on the references. + for (int i = 0; i < codesize; ++i) + code[i] = scri->code[i]; + } + } + + // just use the pointer to the strings since they don't change + strings = scri->strings; + stringssize = scri->stringssize; + // create a stack + stackdatasize = CC_STACK_DATA_SIZE; + // This is quite a random choice; there's no way to deduce number of stack + // entries needed without knowing amount of local variables (at least) + num_stackentries = CC_STACK_SIZE; + stack = new RuntimeScriptValue[num_stackentries]; + stackdata = new char[stackdatasize]; + if (stack == nullptr || stackdata == nullptr) { + cc_error("not enough memory to allocate stack"); + return false; + } + + // find a LoadedInstance slot for it + for (i = 0; i < MAX_LOADED_INSTANCES; i++) { + if (loadedInstances[i] == nullptr) { + loadedInstances[i] = this; + loadedInstanceId = i; + break; + } + if (i == MAX_LOADED_INSTANCES - 1) { + cc_error("too many active instances"); + return false; + } + } + + if (joined) + { + resolved_imports = joined->resolved_imports; + code_fixups = joined->code_fixups; + } + else + { + if (!ResolveScriptImports(scri)) + { + return false; + } + if (!CreateGlobalVars(scri)) + { + return false; + } + if (!CreateRuntimeCodeFixups(scri)) + { + return false; + } + } + + exports = new RuntimeScriptValue[scri->numexports]; + + // find the real address of the exports + for (i = 0; i < scri->numexports; i++) { + int32_t etype = (scri->export_addr[i] >> 24L) & 0x000ff; + int32_t eaddr = (scri->export_addr[i] & 0x00ffffff); + if (etype == EXPORT_FUNCTION) + { + // NOTE: unfortunately, there seems to be no way to know if + // that's an extender function that expects object pointer + exports[i].SetCodePtr((char *)((intptr_t)eaddr * sizeof(intptr_t) + (char*)(&code[0]))); + } + else if (etype == EXPORT_DATA) + { + ScriptVariable *gl_var = FindGlobalVar(eaddr); + if (gl_var) + { + exports[i].SetGlobalVar(&gl_var->RValue); + } + else + { + cc_error("cannot resolve global variable, key = %d", eaddr); + return false; + } + } + else { + cc_error("internal export fixup error"); + return false; + } + } + instanceof = scri; + pc = 0; + flags = 0; + if (joined != nullptr) + flags = INSTF_SHAREDATA; + scri->instances++; + + if ((scri->instances == 1) && (ccGetOption(SCOPT_AUTOIMPORT) != 0)) { + // import all the exported stuff from this script + for (i = 0; i < scri->numexports; i++) { + if (!ccAddExternalScriptSymbol(scri->exports[i], exports[i], this)) { + cc_error("Export table overflow at '%s'", scri->exports[i]); + return false; + } + } + } + return true; +} + +void ccInstance::Free() +{ + if (instanceof != nullptr) { + instanceof->instances--; + if (instanceof->instances == 0) + { + simp.RemoveScriptExports(this); + } + } + + // remove from the Active Instances list + if (loadedInstances[loadedInstanceId] == this) + loadedInstances[loadedInstanceId] = nullptr; + + if ((flags & INSTF_SHAREDATA) == 0) + { + nullfree(globaldata); + nullfree(code); + } + globalvars.reset(); + globaldata = nullptr; + code = nullptr; + strings = nullptr; + + delete [] stack; + delete [] stackdata; + delete [] exports; + stack = nullptr; + stackdata = nullptr; + exports = nullptr; + + if ((flags & INSTF_SHAREDATA) == 0) + { + delete [] resolved_imports; + delete [] code_fixups; + } + resolved_imports = nullptr; + code_fixups = nullptr; +} + +bool ccInstance::ResolveScriptImports(PScript scri) +{ + // When the import is referenced in code, it's being addressed + // by it's index in the script imports array. That index is + // NOT unique and relative to script only. + // Script keeps information of used imports as an array of + // names. + // To allow real-time import use we should put resolved imports + // to the array keeping the order of their names in script's + // array of names. + + // resolve all imports referenced in the script + numimports = scri->numimports; + if (numimports == 0) + { + resolved_imports = nullptr; + return false; + } + resolved_imports = new int[numimports]; + + for (int i = 0; i < scri->numimports; ++i) { + // MACPORT FIX 9/6/5: changed from NULL TO 0 + if (scri->imports[i] == nullptr) { + continue; + } + + resolved_imports[i] = simp.get_index_of(scri->imports[i]); + if (resolved_imports[i] < 0) { + cc_error("unresolved import '%s'", scri->imports[i]); + return false; + } + } + return true; +} + +// TODO: it is possible to deduce global var's size at start with +// certain accuracy after all global vars are registered. Each +// global var's size would be limited by closest next var's ScAddress +// and globaldatasize. +bool ccInstance::CreateGlobalVars(PScript scri) +{ + ScriptVariable glvar; + + // Step One: deduce global variables from fixups + for (int i = 0; i < scri->numfixups; ++i) + { + switch (scri->fixuptypes[i]) + { + case FIXUP_GLOBALDATA: + // GLOBALDATA fixup takes relative address of global data element from code array; + // this is the address of actual data + glvar.ScAddress = (int32_t)code[scri->fixups[i]]; + glvar.RValue.SetData(globaldata + glvar.ScAddress, 0); + break; + case FIXUP_DATADATA: + { + // DATADATA fixup takes relative address of global data element from fixups array; + // this is the address of element, which stores address of actual data + glvar.ScAddress = scri->fixups[i]; + int32_t data_addr = BBOp::Int32FromLE(*(int32_t*)&globaldata[glvar.ScAddress]); + if (glvar.ScAddress - data_addr != 200 /* size of old AGS string */) + { + // CHECKME: probably replace with mere warning in the log? + cc_error("unexpected old-style string's alignment"); + return false; + } + // TODO: register this explicitly as a string instead (can do this later) + glvar.RValue.SetStaticObject(globaldata + data_addr, &GlobalStaticManager); + } + break; + default: + // other fixups are of no use here + continue; + } + + AddGlobalVar(glvar); + } + + // Step Two: deduce global variables from exports + for (int i = 0; i < scri->numexports; ++i) + { + int32_t etype = (scri->export_addr[i] >> 24L) & 0x000ff; + int32_t eaddr = (scri->export_addr[i] & 0x00ffffff); + if (etype == EXPORT_DATA) + { + // NOTE: old-style strings could not be exported in AGS, + // no need to worry about these here + glvar.ScAddress = eaddr; + glvar.RValue.SetData(globaldata + glvar.ScAddress, 0); + AddGlobalVar(glvar); + } + } + + return true; +} + +bool ccInstance::AddGlobalVar(const ScriptVariable &glvar) +{ + // [IKM] 2013-02-23: + // !!! TODO + // "Metal Dead" game (built with AGS 3.21.1115) fails to pass this check, + // because one of its fixups in script creates reference beyond global + // data buffer. The error will be suppressed until root of the problem is + // found, and some proper workaround invented. + if (glvar.ScAddress < 0 || glvar.ScAddress >= globaldatasize) + { + /* + return false; + */ + Debug::Printf(kDbgMsg_Warn, "WARNING: global variable refers to data beyond allocated buffer (%d, %d)", glvar.ScAddress, globaldatasize); + } + globalvars->insert(std::make_pair(glvar.ScAddress, glvar)); + return true; +} + +ScriptVariable *ccInstance::FindGlobalVar(int32_t var_addr) +{ + // NOTE: see comment for AddGlobalVar() + if (var_addr < 0 || var_addr >= globaldatasize) + { + /* + return NULL; + */ + Debug::Printf(kDbgMsg_Warn, "WARNING: looking up for global variable beyond allocated buffer (%d, %d)", var_addr, globaldatasize); + } + ScVarMap::iterator it = globalvars->find(var_addr); + return it != globalvars->end() ? &it->second : nullptr; +} + +bool ccInstance::CreateRuntimeCodeFixups(PScript scri) +{ + code_fixups = new char[scri->codesize]; + memset(code_fixups, 0, scri->codesize); + for (int i = 0; i < scri->numfixups; ++i) + { + if (scri->fixuptypes[i] == FIXUP_DATADATA) + { + continue; + } + + int32_t fixup = scri->fixups[i]; + code_fixups[fixup] = scri->fixuptypes[i]; + + switch (scri->fixuptypes[i]) + { + case FIXUP_GLOBALDATA: + { + ScriptVariable *gl_var = FindGlobalVar((int32_t)code[fixup]); + if (!gl_var) + { + cc_error("cannot resolve global variable, key = %d", (int32_t)code[fixup]); + return false; + } + code[fixup] = (intptr_t)gl_var; + } + break; + case FIXUP_FUNCTION: + case FIXUP_STRING: + case FIXUP_STACK: + break; // do nothing yet + case FIXUP_IMPORT: + // we do not need to save import's address now when we have + // resolved imports kept so far as instance exists, but we + // must fixup the following instruction in certain case + { + int import_index = resolved_imports[code[fixup]]; + const ScriptImport *import = simp.getByIndex(import_index); + if (!import) + { + cc_error("cannot resolve import, key = %d", import_index); + return false; + } + code[fixup] = import_index; + // If the call is to another script function next CALLEXT + // must be replaced with CALLAS + if (import->InstancePtr != nullptr && (code[fixup + 1] & INSTANCE_ID_REMOVEMASK) == SCMD_CALLEXT) + { + code[fixup + 1] = SCMD_CALLAS | (import->InstancePtr->loadedInstanceId << INSTANCE_ID_SHIFT); + } + } + break; + default: + cc_error("internal fixup index error: %d", scri->fixuptypes[i]); + return false; + } + } + return true; +} + +/* +bool ccInstance::ReadOperation(ScriptOperation &op, int32_t at_pc) +{ + op.Instruction.Code = code[at_pc]; + op.Instruction.InstanceId = (op.Instruction.Code >> INSTANCE_ID_SHIFT) & INSTANCE_ID_MASK; + op.Instruction.Code &= INSTANCE_ID_REMOVEMASK; // now this is pure instruction code + + int want_args = sccmd_info[op.Instruction.Code].ArgCount; + if (at_pc + want_args >= codesize) + { + cc_error("unexpected end of code data at %d", at_pc + want_args); + return false; + } + op.ArgCount = want_args; + + at_pc++; + for (int i = 0; i < op.ArgCount; ++i, ++at_pc) + { + char fixup = code_fixups[at_pc]; + if (fixup > 0) + { + // could be relative pointer or import address + if (!FixupArgument(code[at_pc], fixup, op.Args[i])) + { + return false; + } + } + else + { + // should be a numeric literal (int32 or float) + op.Args[i].SetInt32( (int32_t)code[at_pc] ); + } + } + + return true; +} +*/ +/* +bool ccInstance::FixupArgument(intptr_t code_value, char fixup_type, RuntimeScriptValue &argument) +{ + switch (fixup_type) + { + case FIXUP_GLOBALDATA: + { + ScriptVariable *gl_var = (ScriptVariable*)code_value; + argument.SetGlobalVar(&gl_var->RValue); + } + break; + case FIXUP_FUNCTION: + // originally commented -- CHECKME: could this be used in very old versions of AGS? + // code[fixup] += (long)&code[0]; + // This is a program counter value, presumably will be used as SCMD_CALL argument + argument.SetInt32((int32_t)code_value); + break; + case FIXUP_STRING: + argument.SetStringLiteral(&strings[0] + code_value); + break; + case FIXUP_IMPORT: + { + const ScriptImport *import = simp.getByIndex((int32_t)code_value); + if (import) + { + argument = import->Value; + } + else + { + cc_error("cannot resolve import, key = %ld", code_value); + return false; + } + } + break; + case FIXUP_STACK: + argument = GetStackPtrOffsetFw((int32_t)code_value); + break; + default: + cc_error("internal fixup type error: %d", fixup_type); + return false;; + } + return true; +} +*/ +//----------------------------------------------------------------------------- + +void ccInstance::PushValueToStack(const RuntimeScriptValue &rval) +{ + // Write value to the stack tail and advance stack ptr + registers[SREG_SP].WriteValue(rval); + registers[SREG_SP].RValue++; +} + +void ccInstance::PushDataToStack(int32_t num_bytes) +{ + if (registers[SREG_SP].RValue->IsValid()) + { + cc_error("internal error: valid data beyond stack ptr"); + return; + } + // Zero memory, assign pointer to data block to the stack tail, advance both stack ptr and stack data ptr + memset(stackdata_ptr, 0, num_bytes); + registers[SREG_SP].RValue->SetData(stackdata_ptr, num_bytes); + stackdata_ptr += num_bytes; + registers[SREG_SP].RValue++; +} + +RuntimeScriptValue ccInstance::PopValueFromStack() +{ + // rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail + registers[SREG_SP].RValue--; + RuntimeScriptValue rval = *registers[SREG_SP].RValue; + if (rval.Type == kScValData) + { + stackdata_ptr -= rval.Size; + } + registers[SREG_SP].RValue->Invalidate(); + return rval; +} + +void ccInstance::PopValuesFromStack(int32_t num_entries = 1) +{ + for (int i = 0; i < num_entries; ++i) + { + // rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail + registers[SREG_SP].RValue--; + if (registers[SREG_SP].RValue->Type == kScValData) + { + stackdata_ptr -= registers[SREG_SP].RValue->Size; + } + registers[SREG_SP].RValue->Invalidate(); + } +} + +void ccInstance::PopDataFromStack(int32_t num_bytes) +{ + int32_t total_pop = 0; + while (total_pop < num_bytes && registers[SREG_SP].RValue > &stack[0]) + { + // rewind stack ptr to the last valid value, decrement stack data ptr if needed and invalidate the stack tail + registers[SREG_SP].RValue--; + // remember popped bytes count + total_pop += registers[SREG_SP].RValue->Size; + if (registers[SREG_SP].RValue->Type == kScValData) + { + stackdata_ptr -= registers[SREG_SP].RValue->Size; + } + registers[SREG_SP].RValue->Invalidate(); + } + if (total_pop < num_bytes) + { + cc_error("stack underflow"); + } + else if (total_pop > num_bytes) + { + cc_error("stack pointer points inside local variable after pop, stack corrupted?"); + } +} + +RuntimeScriptValue ccInstance::GetStackPtrOffsetFw(int32_t fw_offset) +{ + int32_t total_off = 0; + RuntimeScriptValue *stack_entry = &stack[0]; + while (total_off < fw_offset && stack_entry - &stack[0] < CC_STACK_SIZE ) + { + if (stack_entry->Size > 0) + { + total_off += stack_entry->Size; + } + stack_entry++; + } + if (total_off < fw_offset) + { + cc_error("accessing address beyond stack's tail"); + return RuntimeScriptValue(); + } + RuntimeScriptValue stack_ptr; + stack_ptr.SetStackPtr(stack_entry); + if (total_off > fw_offset) + { + // Forward offset should always set ptr at the beginning of stack entry + cc_error("stack offset forward: trying to access stack data inside stack entry, stack corrupted?"); + } + return stack_ptr; +} + +RuntimeScriptValue ccInstance::GetStackPtrOffsetRw(int32_t rw_offset) +{ + int32_t total_off = 0; + RuntimeScriptValue *stack_entry = registers[SREG_SP].RValue; + while (total_off < rw_offset && stack_entry >= &stack[0]) + { + stack_entry--; + total_off += stack_entry->Size; + } + if (total_off < rw_offset) + { + cc_error("accessing address before stack's head"); + return RuntimeScriptValue(); + } + RuntimeScriptValue stack_ptr; + stack_ptr.SetStackPtr(stack_entry); + if (total_off > rw_offset) + { + // Could be accessing array element, so state error only if stack entry does not refer to data array + if (stack_entry->Type == kScValData) + { + stack_ptr.IValue += total_off - rw_offset; + } + else + { + cc_error("stack offset backward: trying to access stack data inside stack entry, stack corrupted?"); + } + } + return stack_ptr; +} + +void ccInstance::PushToFuncCallStack(FunctionCallStack &func_callstack, const RuntimeScriptValue &rval) +{ + if (func_callstack.Count >= MAX_FUNC_PARAMS) + { + cc_error("function callstack overflow"); + return; + } + + func_callstack.Entries[func_callstack.Head] = rval; + func_callstack.Head--; + func_callstack.Count++; +} + +void ccInstance::PopFromFuncCallStack(FunctionCallStack &func_callstack, int32_t num_entries) +{ + if (func_callstack.Count == 0) + { + cc_error("function callstack underflow"); + return; + } + + func_callstack.Head += num_entries; + func_callstack.Count -= num_entries; +} diff --git a/engines/ags/engine/script/cc_instance.h b/engines/ags/engine/script/cc_instance.h new file mode 100644 index 00000000000..7f6be855433 --- /dev/null +++ b/engines/ags/engine/script/cc_instance.h @@ -0,0 +1,221 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// 'C'-style script interpreter +// +//============================================================================= + +#ifndef __CC_INSTANCE_H +#define __CC_INSTANCE_H + +#include +#include + +#include "script/script_common.h" +#include "script/cc_script.h" // ccScript +#include "script/nonblockingscriptfunction.h" +#include "util/string.h" + +using namespace AGS; + +#define INSTF_SHAREDATA 1 +#define INSTF_ABORTED 2 +#define INSTF_FREE 4 +#define INSTF_RUNNING 8 // set by main code to confirm script isn't stuck +#define CC_STACK_SIZE 250 +#define CC_STACK_DATA_SIZE (1000 * sizeof(int32_t)) +#define MAX_CALL_STACK 100 + +// 256 because we use 8 bits to hold instance number +#define MAX_LOADED_INSTANCES 256 + +#define INSTANCE_ID_SHIFT 24LL +#define INSTANCE_ID_MASK 0x00000000000000ffLL +#define INSTANCE_ID_REMOVEMASK 0x0000000000ffffffLL + +struct ccInstance; +struct ScriptImport; + +struct ScriptInstruction +{ + ScriptInstruction() + { + Code = 0; + InstanceId = 0; + } + + int32_t Code; + int32_t InstanceId; +}; + +struct ScriptOperation +{ + ScriptOperation() + { + ArgCount = 0; + } + + ScriptInstruction Instruction; + RuntimeScriptValue Args[MAX_SCMD_ARGS]; + int ArgCount; +}; + +struct ScriptVariable +{ + ScriptVariable() + { + ScAddress = -1; // address = 0 is valid one, -1 means undefined + } + + int32_t ScAddress; // original 32-bit relative data address, written in compiled script; + // if we are to use Map or HashMap, this could be used as Key + RuntimeScriptValue RValue; +}; + +struct FunctionCallStack; + +struct ScriptPosition +{ + ScriptPosition() + : Line(0) + { + } + + ScriptPosition(const Common::String §ion, int32_t line) + : Section(section) + , Line(line) + { + } + + Common::String Section; + int32_t Line; +}; + +// Running instance of the script +struct ccInstance +{ +public: + // TODO: change to std:: if moved to C++11 + typedef std::unordered_map ScVarMap; + typedef std::shared_ptr PScVarMap; +public: + int32_t flags; + PScVarMap globalvars; + char *globaldata; + int32_t globaldatasize; + // Executed byte-code. Unlike ccScript's code array which is int32_t, the one + // in ccInstance must be intptr_t to accomodate real pointers placed after + // performing fixups. + intptr_t *code; + ccInstance *runningInst; // might point to another instance if in far call + int32_t codesize; + char *strings; + int32_t stringssize; + RuntimeScriptValue *exports; + RuntimeScriptValue *stack; + int num_stackentries; + // An array for keeping stack data; stack entries reference unknown data from here + // TODO: probably change to dynamic array later + char *stackdata; // for storing stack data of unknown type + char *stackdata_ptr;// works similar to original stack pointer, points to the next unused byte in stack data array + int32_t stackdatasize; // conventional size of stack data in bytes + // + RuntimeScriptValue registers[CC_NUM_REGISTERS]; + int32_t pc; // program counter + int32_t line_number; // source code line number + PScript instanceof; + int loadedInstanceId; + int returnValue; + + int callStackSize; + int32_t callStackLineNumber[MAX_CALL_STACK]; + int32_t callStackAddr[MAX_CALL_STACK]; + ccInstance *callStackCodeInst[MAX_CALL_STACK]; + + // array of real import indexes used in script + int *resolved_imports; + int numimports; + + char *code_fixups; + + // returns the currently executing instance, or NULL if none + static ccInstance *GetCurrentInstance(void); + // create a runnable instance of the supplied script + static ccInstance *CreateFromScript(PScript script); + static ccInstance *CreateEx(PScript scri, ccInstance * joined); + + ccInstance(); + ~ccInstance(); + // Create a runnable instance of the same script, sharing global memory + ccInstance *Fork(); + // Specifies that when the current function returns to the script, it + // will stop and return from CallInstance + void Abort(); + // Aborts instance, then frees the memory later when it is done with + void AbortAndDestroy(); + + // Call an exported function in the script + int CallScriptFunction(const char *funcname, int32_t num_params, const RuntimeScriptValue *params); + // Begin executing script starting from the given bytecode index + int Run(int32_t curpc); + + // Get the script's execution position and callstack as human-readable text + Common::String GetCallStack(int maxLines); + // Get the script's execution position + void GetScriptPosition(ScriptPosition &script_pos); + // Get the address of an exported symbol (function or variable) in the script + RuntimeScriptValue GetSymbolAddress(const char *symname); + void DumpInstruction(const ScriptOperation &op); + // Tells whether this instance is in the process of executing the byte-code + bool IsBeingRun() const; + +protected: + bool _Create(PScript scri, ccInstance * joined); + // free the memory associated with the instance + void Free(); + + bool ResolveScriptImports(PScript scri); + bool CreateGlobalVars(PScript scri); + bool AddGlobalVar(const ScriptVariable &glvar); + ScriptVariable *FindGlobalVar(int32_t var_addr); + bool CreateRuntimeCodeFixups(PScript scri); + //bool ReadOperation(ScriptOperation &op, int32_t at_pc); + + // Runtime fixups + //bool FixupArgument(intptr_t code_value, char fixup_type, RuntimeScriptValue &argument); + + // Stack processing + // Push writes new value and increments stack ptr; + // stack ptr now points to the __next empty__ entry + void PushValueToStack(const RuntimeScriptValue &rval); + void PushDataToStack(int32_t num_bytes); + // Pop decrements stack ptr, returns last stored value and invalidates! stack tail; + // stack ptr now points to the __next empty__ entry + RuntimeScriptValue PopValueFromStack(); + // helper function to pop & dump several values + void PopValuesFromStack(int32_t num_entries); + void PopDataFromStack(int32_t num_bytes); + // Return stack ptr at given offset from stack head; + // Offset is in data bytes; program stack ptr is __not__ changed + RuntimeScriptValue GetStackPtrOffsetFw(int32_t fw_offset); + // Return stack ptr at given offset from stack tail; + // Offset is in data bytes; program stack ptr is __not__ changed + RuntimeScriptValue GetStackPtrOffsetRw(int32_t rw_offset); + + // Function call stack processing + void PushToFuncCallStack(FunctionCallStack &func_callstack, const RuntimeScriptValue &rval); + void PopFromFuncCallStack(FunctionCallStack &func_callstack, int32_t num_entries); +}; + +#endif // __CC_INSTANCE_H diff --git a/engines/ags/engine/script/executingscript.cpp b/engines/ags/engine/script/executingscript.cpp new file mode 100644 index 00000000000..ba1fb22f3f2 --- /dev/null +++ b/engines/ags/engine/script/executingscript.cpp @@ -0,0 +1,88 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "executingscript.h" +#include "debug/debug_log.h" +#include "debug/debugger.h" + +QueuedScript::QueuedScript() + : Instance(kScInstGame) + , ParamCount(0) +{ +} + +int ExecutingScript::queue_action(PostScriptAction act, int data, const char *aname) { + if (numPostScriptActions >= MAX_QUEUED_ACTIONS) + quitprintf("!%s: Cannot queue action, post-script queue full", aname); + + if (numPostScriptActions > 0) { + // if something that will terminate the room has already + // been queued, don't allow a second thing to be queued + switch (postScriptActions[numPostScriptActions - 1]) { + case ePSANewRoom: + case ePSARestoreGame: + case ePSARestoreGameDialog: + case ePSARunAGSGame: + case ePSARestartGame: + quitprintf("!%s: Cannot run this command, since there was a %s command already queued to run in \"%s\", line %d", + aname, postScriptActionNames[numPostScriptActions - 1], + postScriptActionPositions[numPostScriptActions - 1].Section.GetCStr(), postScriptActionPositions[numPostScriptActions - 1].Line); + break; + // MACPORT FIX 9/6/5: added default clause to remove warning + default: + break; + } + } + + postScriptActions[numPostScriptActions] = act; + postScriptActionData[numPostScriptActions] = data; + postScriptActionNames[numPostScriptActions] = aname; + get_script_position(postScriptActionPositions[numPostScriptActions]); + numPostScriptActions++; + return numPostScriptActions - 1; +} + +void ExecutingScript::run_another(const char *namm, ScriptInstType scinst, size_t param_count, const RuntimeScriptValue &p1, const RuntimeScriptValue &p2) { + if (numanother < MAX_QUEUED_SCRIPTS) + numanother++; + else { + /*debug_script_warn("Warning: too many scripts to run, ignored %s(%d,%d)", + script_run_another[numanother - 1], run_another_p1[numanother - 1], + run_another_p2[numanother - 1]);*/ + } + int thisslot = numanother - 1; + QueuedScript &script = ScFnQueue[thisslot]; + script.FnName.SetString(namm, MAX_FUNCTION_NAME_LEN); + script.Instance = scinst; + script.ParamCount = param_count; + script.Param1 = p1; + script.Param2 = p2; +} + +void ExecutingScript::init() { + inst = nullptr; + forked = 0; + numanother = 0; + numPostScriptActions = 0; + + memset(postScriptActions, 0, sizeof(postScriptActions)); + memset(postScriptActionNames, 0, sizeof(postScriptActionNames)); + memset(postScriptSaveSlotDescription, 0, sizeof(postScriptSaveSlotDescription)); + memset(postScriptActionData, 0, sizeof(postScriptActionData)); +} + +ExecutingScript::ExecutingScript() { + init(); +} diff --git a/engines/ags/engine/script/executingscript.h b/engines/ags/engine/script/executingscript.h new file mode 100644 index 00000000000..52f7b02e622 --- /dev/null +++ b/engines/ags/engine/script/executingscript.h @@ -0,0 +1,74 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_SCRIPT__EXECUTINGSCRIPT_H +#define __AGS_EE_SCRIPT__EXECUTINGSCRIPT_H + +#include "script/cc_instance.h" + +enum PostScriptAction { + ePSANewRoom, + ePSAInvScreen, + ePSARestoreGame, + ePSARestoreGameDialog, + ePSARunAGSGame, + ePSARunDialog, + ePSARestartGame, + ePSASaveGame, + ePSASaveGameDialog +}; + +#define MAX_QUEUED_SCRIPTS 4 +#define MAX_QUEUED_ACTIONS 5 +#define MAX_FUNCTION_NAME_LEN 60 + +enum ScriptInstType +{ + kScInstGame, + kScInstRoom +}; + +struct QueuedScript +{ + Common::String FnName; + ScriptInstType Instance; + size_t ParamCount; + RuntimeScriptValue Param1; + RuntimeScriptValue Param2; + + QueuedScript(); +}; + +struct ExecutingScript { + ccInstance *inst; + PostScriptAction postScriptActions[MAX_QUEUED_ACTIONS]; + const char *postScriptActionNames[MAX_QUEUED_ACTIONS]; + ScriptPosition postScriptActionPositions[MAX_QUEUED_ACTIONS]; + char postScriptSaveSlotDescription[MAX_QUEUED_ACTIONS][100]; + int postScriptActionData[MAX_QUEUED_ACTIONS]; + int numPostScriptActions; + QueuedScript ScFnQueue[MAX_QUEUED_SCRIPTS]; + int numanother; + char forked; + + int queue_action(PostScriptAction act, int data, const char *aname); + void run_another(const char *namm, ScriptInstType scinst, size_t param_count, const RuntimeScriptValue &p1, const RuntimeScriptValue &p2); + void init(); + ExecutingScript(); +}; + +#endif // __AGS_EE_SCRIPT__EXECUTINGSCRIPT_H diff --git a/engines/ags/engine/script/exports.cpp b/engines/ags/engine/script/exports.cpp new file mode 100644 index 00000000000..5bad7b5d706 --- /dev/null +++ b/engines/ags/engine/script/exports.cpp @@ -0,0 +1,98 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Registering symbols for the script system +// +//============================================================================= + +#include "ac/gamestructdefines.h" + +extern void RegisterAudioChannelAPI(); +extern void RegisterAudioClipAPI(); +extern void RegisterButtonAPI(); +extern void RegisterCharacterAPI(ScriptAPIVersion base_api, ScriptAPIVersion compat_api); +extern void RegisterContainerAPI(); +extern void RegisterDateTimeAPI(); +extern void RegisterDialogAPI(); +extern void RegisterDialogOptionsRenderingAPI(); +extern void RegisterDrawingSurfaceAPI(ScriptAPIVersion base_api, ScriptAPIVersion compat_api); +extern void RegisterDynamicSpriteAPI(); +extern void RegisterFileAPI(); +extern void RegisterGameAPI(); +extern void RegisterGlobalAPI(); +extern void RegisterGUIAPI(); +extern void RegisterGUIControlAPI(); +extern void RegisterHotspotAPI(); +extern void RegisterInventoryItemAPI(); +extern void RegisterInventoryWindowAPI(); +extern void RegisterLabelAPI(); +extern void RegisterListBoxAPI(); +extern void RegisterMathAPI(); +extern void RegisterMouseAPI(); +extern void RegisterObjectAPI(); +extern void RegisterOverlayAPI(); +extern void RegisterParserAPI(); +extern void RegisterRegionAPI(); +extern void RegisterRoomAPI(); +extern void RegisterScreenAPI(); +extern void RegisterSliderAPI(); +extern void RegisterSpeechAPI(ScriptAPIVersion base_api, ScriptAPIVersion compat_api); +extern void RegisterStringAPI(); +extern void RegisterSystemAPI(); +extern void RegisterTextBoxAPI(); +extern void RegisterViewFrameAPI(); +extern void RegisterViewportAPI(); + +extern void RegisterStaticObjects(); + +void setup_script_exports(ScriptAPIVersion base_api, ScriptAPIVersion compat_api) +{ + RegisterAudioChannelAPI(); + RegisterAudioClipAPI(); + RegisterButtonAPI(); + RegisterCharacterAPI(base_api, compat_api); + RegisterContainerAPI(); + RegisterDateTimeAPI(); + RegisterDialogAPI(); + RegisterDialogOptionsRenderingAPI(); + RegisterDrawingSurfaceAPI(base_api, compat_api); + RegisterDynamicSpriteAPI(); + RegisterFileAPI(); + RegisterGameAPI(); + RegisterGlobalAPI(); + RegisterGUIAPI(); + RegisterGUIControlAPI(); + RegisterHotspotAPI(); + RegisterInventoryItemAPI(); + RegisterInventoryWindowAPI(); + RegisterLabelAPI(); + RegisterListBoxAPI(); + RegisterMathAPI(); + RegisterMouseAPI(); + RegisterObjectAPI(); + RegisterOverlayAPI(); + RegisterParserAPI(); + RegisterRegionAPI(); + RegisterRoomAPI(); + RegisterScreenAPI(); + RegisterSliderAPI(); + RegisterSpeechAPI(base_api, compat_api); + RegisterStringAPI(); + RegisterSystemAPI(); + RegisterTextBoxAPI(); + RegisterViewFrameAPI(); + RegisterViewportAPI(); + + RegisterStaticObjects(); +} diff --git a/engines/ags/engine/script/exports.h b/engines/ags/engine/script/exports.h new file mode 100644 index 00000000000..cfb8ffcc159 --- /dev/null +++ b/engines/ags/engine/script/exports.h @@ -0,0 +1,23 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Registering symbols for the script system +// +//============================================================================= +#ifndef __AGS_EE_SCRIPT__EXPORTS_H +#define __AGS_EE_SCRIPT__EXPORTS_H + +void setup_script_exports(ScriptAPIVersion base_api, ScriptAPIVersion compat_api); + +#endif // __AGS_EE_SCRIPT__EXPORTS_H diff --git a/engines/ags/engine/script/nonblockingscriptfunction.h b/engines/ags/engine/script/nonblockingscriptfunction.h new file mode 100644 index 00000000000..6c49e1b42df --- /dev/null +++ b/engines/ags/engine/script/nonblockingscriptfunction.h @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_SCRIPT__NONBLOCKINGSCRIPTFUNCTION_H +#define __AGS_EE_SCRIPT__NONBLOCKINGSCRIPTFUNCTION_H + +#include "ac/runtime_defines.h" +#include "script/runtimescriptvalue.h" + +#include + +struct NonBlockingScriptFunction +{ + const char* functionName; + int numParameters; + //void* param1; + //void* param2; + RuntimeScriptValue params[2]; + bool roomHasFunction; + bool globalScriptHasFunction; + std::vector moduleHasFunction; + bool atLeastOneImplementationExists; + + NonBlockingScriptFunction(const char*funcName, int numParams) + { + this->functionName = funcName; + this->numParameters = numParams; + atLeastOneImplementationExists = false; + roomHasFunction = true; + globalScriptHasFunction = true; + } +}; + +#endif // __AGS_EE_SCRIPT__NONBLOCKINGSCRIPTFUNCTION_H diff --git a/engines/ags/engine/script/runtimescriptvalue.cpp b/engines/ags/engine/script/runtimescriptvalue.cpp new file mode 100644 index 00000000000..f74e8d15b78 --- /dev/null +++ b/engines/ags/engine/script/runtimescriptvalue.cpp @@ -0,0 +1,321 @@ + +#include "script/cc_error.h" +#include "script/runtimescriptvalue.h" +#include "ac/dynobj/cc_dynamicobject.h" +#include "ac/statobj/staticobject.h" +#include "util/memory.h" + +#include // for memcpy() + +using namespace AGS::Common; + +// +// NOTE to future optimizers: I am using 'this' ptr here to better +// distinguish Runtime Values. +// + +// TODO: test again if stack entry really can hold an offset itself + +// TODO: use endian-agnostic method to access global vars + +uint8_t RuntimeScriptValue::ReadByte() +{ + if (this->Type == kScValStackPtr || this->Type == kScValGlobalVar) + { + if (RValue->Type == kScValData) + { + return *(uint8_t*)(RValue->GetPtrWithOffset() + this->IValue); + } + else + { + return RValue->IValue; // get RValue as int + } + } + else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) + { + return this->StcMgr->ReadInt8(this->Ptr, this->IValue); + } + else if (this->Type == kScValDynamicObject) + { + return this->DynMgr->ReadInt8(this->Ptr, this->IValue); + } + return *((uint8_t*)this->GetPtrWithOffset()); +} + +int16_t RuntimeScriptValue::ReadInt16() +{ + if (this->Type == kScValStackPtr) + { + if (RValue->Type == kScValData) + { + return *(int16_t*)(RValue->GetPtrWithOffset() + this->IValue); + } + else + { + return RValue->IValue; // get RValue as int + } + } + else if (this->Type == kScValGlobalVar) + { + if (RValue->Type == kScValData) + { + return Memory::ReadInt16LE(RValue->GetPtrWithOffset() + this->IValue); + } + else + { + return RValue->IValue; // get RValue as int + } + } + else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) + { + return this->StcMgr->ReadInt16(this->Ptr, this->IValue); + } + else if (this->Type == kScValDynamicObject) + { + return this->DynMgr->ReadInt16(this->Ptr, this->IValue); + } + return *((int16_t*)this->GetPtrWithOffset()); +} + +int32_t RuntimeScriptValue::ReadInt32() +{ + if (this->Type == kScValStackPtr) + { + if (RValue->Type == kScValData) + { + return *(int32_t*)(RValue->GetPtrWithOffset() + this->IValue); + } + else + { + return RValue->IValue; // get RValue as int + } + } + else if (this->Type == kScValGlobalVar) + { + if (RValue->Type == kScValData) + { + return Memory::ReadInt32LE(RValue->GetPtrWithOffset() + this->IValue); + } + else + { + return RValue->IValue; // get RValue as int + } + } + else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) + { + return this->StcMgr->ReadInt32(this->Ptr, this->IValue); + } + else if (this->Type == kScValDynamicObject) + { + return this->DynMgr->ReadInt32(this->Ptr, this->IValue); + } + return *((int32_t*)this->GetPtrWithOffset()); +} + +bool RuntimeScriptValue::WriteByte(uint8_t val) +{ + if (this->Type == kScValStackPtr || this->Type == kScValGlobalVar) + { + if (RValue->Type == kScValData) + { + *(uint8_t*)(RValue->GetPtrWithOffset() + this->IValue) = val; + } + else + { + RValue->SetUInt8(val); // set RValue as int + } + } + else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) + { + this->StcMgr->WriteInt8(this->Ptr, this->IValue, val); + } + else if (this->Type == kScValDynamicObject) + { + this->DynMgr->WriteInt8(this->Ptr, this->IValue, val); + } + else + { + *((uint8_t*)this->GetPtrWithOffset()) = val; + } + return true; +} + +bool RuntimeScriptValue::WriteInt16(int16_t val) +{ + if (this->Type == kScValStackPtr) + { + if (RValue->Type == kScValData) + { + *(int16_t*)(RValue->GetPtrWithOffset() + this->IValue) = val; + } + else + { + RValue->SetInt16(val); // set RValue as int + } + } + else if (this->Type == kScValGlobalVar) + { + if (RValue->Type == kScValData) + { + Memory::WriteInt16LE(RValue->GetPtrWithOffset() + this->IValue, val); + } + else + { + RValue->SetInt16(val); // set RValue as int + } + } + else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) + { + this->StcMgr->WriteInt16(this->Ptr, this->IValue, val); + } + else if (this->Type == kScValDynamicObject) + { + this->DynMgr->WriteInt16(this->Ptr, this->IValue, val); + } + else + { + *((int16_t*)this->GetPtrWithOffset()) = val; + } + return true; +} + +bool RuntimeScriptValue::WriteInt32(int32_t val) +{ + if (this->Type == kScValStackPtr) + { + if (RValue->Type == kScValData) + { + *(int32_t*)(RValue->GetPtrWithOffset() + this->IValue) = val; + } + else + { + RValue->SetInt32(val); // set RValue as int + } + } + else if (this->Type == kScValGlobalVar) + { + if (RValue->Type == kScValData) + { + Memory::WriteInt32LE(RValue->GetPtrWithOffset() + this->IValue, val); + } + else + { + RValue->SetInt32(val); // set RValue as int + } + } + else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) + { + this->StcMgr->WriteInt32(this->Ptr, this->IValue, val); + } + else if (this->Type == kScValDynamicObject) + { + this->DynMgr->WriteInt32(this->Ptr, this->IValue, val); + } + else + { + *((int32_t*)this->GetPtrWithOffset()) = val; + } + return true; +} + +// Notice, that there are only two valid cases when a pointer may be written: +// when the destination is a stack entry or global variable of free type +// (not kScValData type). +// In any other case, only the numeric value (integer/float) will be written. +bool RuntimeScriptValue::WriteValue(const RuntimeScriptValue &rval) +{ + if (this->Type == kScValStackPtr) + { + if (RValue->Type == kScValData) + { + *(int32_t*)(RValue->GetPtrWithOffset() + this->IValue) = rval.IValue; + } + else + { + // NOTE: we cannot just WriteValue here because when an integer + // is pushed to the stack, script assumes that it is always 4 + // bytes and uses that size when calculating offsets to local + // variables; + // Therefore if pushed value is of integer type, we should rather + // act as WriteInt32 (for int8, int16 and int32). + if (rval.Type == kScValInteger) + { + RValue->SetInt32(rval.IValue); + } + else + { + *RValue = rval; + } + } + } + else if (this->Type == kScValGlobalVar) + { + if (RValue->Type == kScValData) + { + Memory::WriteInt32LE(RValue->GetPtrWithOffset() + this->IValue, rval.IValue); + } + else + { + *RValue = rval; + } + } + else if (this->Type == kScValStaticObject || this->Type == kScValStaticArray) + { + this->StcMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue); + } + else if (this->Type == kScValDynamicObject) + { + this->DynMgr->WriteInt32(this->Ptr, this->IValue, rval.IValue); + } + else + { + *((int32_t*)this->GetPtrWithOffset()) = rval.IValue; + } + return true; +} + +RuntimeScriptValue &RuntimeScriptValue::DirectPtr() +{ + if (Type == kScValGlobalVar || Type == kScValStackPtr) + { + int ival = IValue; + *this = *RValue; + IValue += ival; + } + + if (Ptr) + { + if (Type == kScValDynamicObject) + Ptr = const_cast(DynMgr->GetFieldPtr(Ptr, IValue)); + else if (Type == kScValStaticObject) + Ptr = const_cast(StcMgr->GetFieldPtr(Ptr, IValue)); + else + Ptr += IValue; + IValue = 0; + } + return *this; +} + +RuntimeScriptValue &RuntimeScriptValue::DirectPtrObj() +{ + if (Type == kScValGlobalVar || Type == kScValStackPtr) + *this = *RValue; + return *this; +} + +intptr_t RuntimeScriptValue::GetDirectPtr() const +{ + const RuntimeScriptValue *temp_val = this; + int ival = temp_val->IValue; + if (temp_val->Type == kScValGlobalVar || temp_val->Type == kScValStackPtr) + { + temp_val = temp_val->RValue; + ival += temp_val->IValue; + } + if (temp_val->Type == kScValDynamicObject) + return (intptr_t)temp_val->DynMgr->GetFieldPtr(temp_val->Ptr, ival); + else if (temp_val->Type == kScValStaticObject) + return (intptr_t)temp_val->StcMgr->GetFieldPtr(temp_val->Ptr, ival); + else + return (intptr_t)(temp_val->Ptr + ival); +} diff --git a/engines/ags/engine/script/runtimescriptvalue.h b/engines/ags/engine/script/runtimescriptvalue.h new file mode 100644 index 00000000000..133267d203b --- /dev/null +++ b/engines/ags/engine/script/runtimescriptvalue.h @@ -0,0 +1,385 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Runtime script value struct +// +//============================================================================= +#ifndef __AGS_EE_SCRIPT__RUNTIMESCRIPTVALUE_H +#define __AGS_EE_SCRIPT__RUNTIMESCRIPTVALUE_H + +#include "script/script_api.h" +#include "util/memory.h" +#include "ac/dynobj/cc_dynamicobject.h" + +struct ICCStaticObject; +struct StaticArray; + +enum ScriptValueType +{ + kScValUndefined, // to detect errors + kScValInteger, // as strictly 32-bit integer (for integer math) + kScValFloat, // as float (for floating point math), 32-bit + kScValPluginArg, // an 32-bit value, passed to a script function when called + // directly by plugin; is allowed to represent object pointer + kScValStackPtr, // as a pointer to stack entry + kScValData, // as a container for randomly sized data (usually array) + kScValGlobalVar, // as a pointer to script variable; used only for global vars, + // as pointer to local vars must have StackPtr type so that the + // stack allocation could work + kScValStringLiteral,// as a pointer to literal string (array of chars) + kScValStaticObject, // as a pointer to static global script object + kScValStaticArray, // as a pointer to static global array (of static or dynamic objects) + kScValDynamicObject,// as a pointer to managed script object + kScValPluginObject, // as a pointer to object managed by plugin (similar to + // kScValDynamicObject, but has backward-compatible limitations) + kScValStaticFunction,// as a pointer to static function + kScValPluginFunction,// temporary workaround for plugins (unsafe function ptr) + kScValObjectFunction,// as a pointer to object member function, gets object pointer as + // first parameter + kScValCodePtr, // as a pointer to element in byte-code array +}; + +struct RuntimeScriptValue +{ +public: + RuntimeScriptValue() + { + Type = kScValUndefined; + IValue = 0; + Ptr = nullptr; + MgrPtr = nullptr; + Size = 0; + } + + RuntimeScriptValue(int32_t val) + { + Type = kScValInteger; + IValue = val; + Ptr = nullptr; + MgrPtr = nullptr; + Size = 4; + } + + ScriptValueType Type; + // The 32-bit value used for integer/float math and for storing + // variable/element offset relative to object (and array) address + union + { + int32_t IValue; // access Value as int32 type + float FValue; // access Value as float type + }; + // Pointer is used for storing... pointers - to objects, arrays, + // functions and stack entries (other RSV) + union + { + char *Ptr; // generic data pointer + RuntimeScriptValue *RValue;// access ptr as a pointer to Runtime Value + ScriptAPIFunction *SPfn; // access ptr as a pointer to Script API Static Function + ScriptAPIObjectFunction *ObjPfn; // access ptr as a pointer to Script API Object Function + }; + // TODO: separation to Ptr and MgrPtr is only needed so far as there's + // a separation between Script*, Dynamic* and game entity classes. + // Once those classes are merged, it will no longer be needed. + union + { + void *MgrPtr;// generic object manager pointer + ICCStaticObject *StcMgr;// static object manager + StaticArray *StcArr;// static array manager + ICCDynamicObject *DynMgr;// dynamic object manager + }; + // The "real" size of data, either one stored in I/FValue, + // or the one referenced by Ptr. Used for calculating stack + // offsets. + // Original AGS scripts always assumed pointer is 32-bit. + // Therefore for stored pointers Size is always 4 both for x32 + // and x64 builds, so that the script is interpreted correctly. + int Size; + + inline bool IsValid() const + { + return Type != kScValUndefined; + } + inline bool IsNull() const + { + return Ptr == nullptr && IValue == 0; + } + + inline bool GetAsBool() const + { + return !IsNull(); + } + inline char* GetPtrWithOffset() const + { + return Ptr + IValue; + } + + inline RuntimeScriptValue &Invalidate() + { + Type = kScValUndefined; + IValue = 0; + Ptr = nullptr; + MgrPtr = nullptr; + Size = 0; + return *this; + } + inline RuntimeScriptValue &SetUInt8(uint8_t val) + { + Type = kScValInteger; + IValue = val; + Ptr = nullptr; + MgrPtr = nullptr; + Size = 1; + return *this; + } + inline RuntimeScriptValue &SetInt16(int16_t val) + { + Type = kScValInteger; + IValue = val; + Ptr = nullptr; + MgrPtr = nullptr; + Size = 2; + return *this; + } + inline RuntimeScriptValue &SetInt32(int32_t val) + { + Type = kScValInteger; + IValue = val; + Ptr = nullptr; + MgrPtr = nullptr; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetFloat(float val) + { + Type = kScValFloat; + FValue = val; + Ptr = nullptr; + MgrPtr = nullptr; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetInt32AsBool(bool val) + { + return SetInt32(val ? 1 : 0); + } + inline RuntimeScriptValue &SetFloatAsBool(bool val) + { + return SetFloat(val ? 1.0F : 0.0F); + } + inline RuntimeScriptValue &SetPluginArgument(int32_t val) + { + Type = kScValPluginArg; + IValue = val; + Ptr = nullptr; + MgrPtr = nullptr; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetStackPtr(RuntimeScriptValue *stack_entry) + { + Type = kScValStackPtr; + IValue = 0; + RValue = stack_entry; + MgrPtr = nullptr; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetData(char *data, int size) + { + Type = kScValData; + IValue = 0; + Ptr = data; + MgrPtr = nullptr; + Size = size; + return *this; + } + inline RuntimeScriptValue &SetGlobalVar(RuntimeScriptValue *glvar_value) + { + Type = kScValGlobalVar; + IValue = 0; + RValue = glvar_value; + MgrPtr = nullptr; + Size = 4; + return *this; + } + // TODO: size? + inline RuntimeScriptValue &SetStringLiteral(const char *str) + { + Type = kScValStringLiteral; + IValue = 0; + Ptr = const_cast(str); + MgrPtr = nullptr; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetStaticObject(void *object, ICCStaticObject *manager) + { + Type = kScValStaticObject; + IValue = 0; + Ptr = (char*)object; + StcMgr = manager; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetStaticArray(void *object, StaticArray *manager) + { + Type = kScValStaticArray; + IValue = 0; + Ptr = (char*)object; + StcArr = manager; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetDynamicObject(void *object, ICCDynamicObject *manager) + { + Type = kScValDynamicObject; + IValue = 0; + Ptr = (char*)object; + DynMgr = manager; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetPluginObject(void *object, ICCDynamicObject *manager) + { + Type = kScValPluginObject; + IValue = 0; + Ptr = (char*)object; + DynMgr = manager; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetStaticFunction(ScriptAPIFunction *pfn) + { + Type = kScValStaticFunction; + IValue = 0; + SPfn = pfn; + MgrPtr = nullptr; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetPluginFunction(void *pfn) + { + Type = kScValPluginFunction; + IValue = 0; + Ptr = (char*)pfn; + MgrPtr = nullptr; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetObjectFunction(ScriptAPIObjectFunction *pfn) + { + Type = kScValObjectFunction; + IValue = 0; + ObjPfn = pfn; + MgrPtr = nullptr; + Size = 4; + return *this; + } + inline RuntimeScriptValue &SetCodePtr(char *ptr) + { + Type = kScValCodePtr; + IValue = 0; + Ptr = ptr; + MgrPtr = nullptr; + Size = 4; + return *this; + } + + inline RuntimeScriptValue operator !() const + { + return RuntimeScriptValue().SetInt32AsBool(!GetAsBool()); + } + + inline bool operator ==(const RuntimeScriptValue &rval) + { + return ((intptr_t)Ptr + (intptr_t)IValue) == ((intptr_t)rval.Ptr + (intptr_t)rval.IValue); + } + inline bool operator !=(const RuntimeScriptValue &rval) + { + return !(*this == rval); + } + + // FIXME: find out all certain cases when we are reading a pointer and store it + // as 32-bit value here. There should be a solution to distinct these cases and + // store value differently, otherwise it won't work for 64-bit build. + inline RuntimeScriptValue ReadValue() + { + RuntimeScriptValue rval; + switch(this->Type) { + case kScValStackPtr: + { + if (RValue->Type == kScValData) + { + rval.SetInt32(*(int32_t*)(RValue->GetPtrWithOffset() + this->IValue)); + } + else + { + rval = *RValue; + } + } + break; + case kScValGlobalVar: + { + if (RValue->Type == kScValData) + { + rval.SetInt32(AGS::Common::Memory::ReadInt32LE(RValue->GetPtrWithOffset() + this->IValue)); + } + else + { + rval = *RValue; + } + } + break; + case kScValStaticObject: case kScValStaticArray: + { + rval.SetInt32(this->StcMgr->ReadInt32(this->Ptr, this->IValue)); + } + break; + case kScValDynamicObject: + { + rval.SetInt32(this->DynMgr->ReadInt32(this->Ptr, this->IValue)); + } + break; + default: + { + // 64 bit: Memory reads are still 32 bit + rval.SetInt32(*(int32_t*)this->GetPtrWithOffset()); + } + } + return rval; + } + + + // Helper functions for reading or writing values from/to + // object, referenced by this Runtime Value. + // Copy implementation depends on value type. + uint8_t ReadByte(); + int16_t ReadInt16(); + int32_t ReadInt32(); + bool WriteByte(uint8_t val); + bool WriteInt16(int16_t val); + bool WriteInt32(int32_t val); + bool WriteValue(const RuntimeScriptValue &rval); + + // Convert to most simple pointer type by resolving RValue ptrs and applying offsets; + // non pointer types are left unmodified + RuntimeScriptValue &DirectPtr(); + // Similar to above, a slightly speed-optimised version for situations when we can + // tell for certain that we are expecting a pointer to the object and not its (first) field. + RuntimeScriptValue &DirectPtrObj(); + // Resolve and return direct pointer to the referenced data; non pointer types return IValue + intptr_t GetDirectPtr() const; +}; + +#endif // __AGS_EE_SCRIPT__RUNTIMESCRIPTVALUE_H diff --git a/engines/ags/engine/script/script.cpp b/engines/ags/engine/script/script.cpp new file mode 100644 index 00000000000..40f3fb915f9 --- /dev/null +++ b/engines/ags/engine/script/script.cpp @@ -0,0 +1,929 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "script/script.h" +#include "ac/common.h" +#include "ac/character.h" +#include "ac/dialog.h" +#include "ac/event.h" +#include "ac/game.h" +#include "ac/gamesetupstruct.h" +#include "ac/gamestate.h" +#include "ac/global_audio.h" +#include "ac/global_character.h" +#include "ac/global_dialog.h" +#include "ac/global_display.h" +#include "ac/global_game.h" +#include "ac/global_gui.h" +#include "ac/global_hotspot.h" +#include "ac/global_object.h" +#include "ac/global_room.h" +#include "ac/invwindow.h" +#include "ac/mouse.h" +#include "ac/room.h" +#include "ac/roomobject.h" +#include "script/cc_error.h" +#include "script/cc_options.h" +#include "debug/debugger.h" +#include "debug/debug_log.h" +#include "main/game_run.h" +#include "media/video/video.h" +#include "script/script_runtime.h" +#include "util/string_compat.h" +#include "media/audio/audio_system.h" + +extern GameSetupStruct game; +extern GameState play; +extern int gameHasBeenRestored, displayed_room; +extern unsigned int load_new_game; +extern RoomObject*objs; +extern int our_eip; +extern CharacterInfo*playerchar; + +ExecutingScript scripts[MAX_SCRIPT_AT_ONCE]; +ExecutingScript*curscript = nullptr; + +PScript gamescript; +PScript dialogScriptsScript; +ccInstance *gameinst = nullptr, *roominst = nullptr; +ccInstance *dialogScriptsInst = nullptr; +ccInstance *gameinstFork = nullptr, *roominstFork = nullptr; + +int num_scripts=0; +int post_script_cleanup_stack = 0; + +int inside_script=0,in_graph_script=0; +int no_blocking_functions = 0; // set to 1 while in rep_Exec_always + +NonBlockingScriptFunction repExecAlways(REP_EXEC_ALWAYS_NAME, 0); +NonBlockingScriptFunction lateRepExecAlways(LATE_REP_EXEC_ALWAYS_NAME, 0); +NonBlockingScriptFunction getDialogOptionsDimensionsFunc("dialog_options_get_dimensions", 1); +NonBlockingScriptFunction renderDialogOptionsFunc("dialog_options_render", 1); +NonBlockingScriptFunction getDialogOptionUnderCursorFunc("dialog_options_get_active", 1); +NonBlockingScriptFunction runDialogOptionMouseClickHandlerFunc("dialog_options_mouse_click", 2); +NonBlockingScriptFunction runDialogOptionKeyPressHandlerFunc("dialog_options_key_press", 2); +NonBlockingScriptFunction runDialogOptionRepExecFunc("dialog_options_repexec", 1); + +ScriptSystem scsystem; + +std::vector scriptModules; +std::vector moduleInst; +std::vector moduleInstFork; +std::vector moduleRepExecAddr; +int numScriptModules = 0; + +std::vector characterScriptObjNames; +String objectScriptObjNames[MAX_ROOM_OBJECTS]; +std::vector guiScriptObjNames; + + +int run_dialog_request (int parmtr) { + play.stop_dialog_at_end = DIALOG_RUNNING; + RunTextScriptIParam(gameinst, "dialog_request", RuntimeScriptValue().SetInt32(parmtr)); + + if (play.stop_dialog_at_end == DIALOG_STOP) { + play.stop_dialog_at_end = DIALOG_NONE; + return -2; + } + if (play.stop_dialog_at_end >= DIALOG_NEWTOPIC) { + int tval = play.stop_dialog_at_end - DIALOG_NEWTOPIC; + play.stop_dialog_at_end = DIALOG_NONE; + return tval; + } + if (play.stop_dialog_at_end >= DIALOG_NEWROOM) { + int roomnum = play.stop_dialog_at_end - DIALOG_NEWROOM; + play.stop_dialog_at_end = DIALOG_NONE; + NewRoom(roomnum); + return -2; + } + play.stop_dialog_at_end = DIALOG_NONE; + return -1; +} + +void run_function_on_non_blocking_thread(NonBlockingScriptFunction* funcToRun) { + + update_script_mouse_coords(); + + int room_changes_was = play.room_changes; + funcToRun->atLeastOneImplementationExists = false; + + // run modules + // modules need a forkedinst for this to work + for (int kk = 0; kk < numScriptModules; kk++) { + funcToRun->moduleHasFunction[kk] = DoRunScriptFuncCantBlock(moduleInstFork[kk], funcToRun, funcToRun->moduleHasFunction[kk]); + + if (room_changes_was != play.room_changes) + return; + } + + funcToRun->globalScriptHasFunction = DoRunScriptFuncCantBlock(gameinstFork, funcToRun, funcToRun->globalScriptHasFunction); + + if (room_changes_was != play.room_changes) + return; + + funcToRun->roomHasFunction = DoRunScriptFuncCantBlock(roominstFork, funcToRun, funcToRun->roomHasFunction); +} + +//----------------------------------------------------------- +// [IKM] 2012-06-22 +// +// run_interaction_event() and run_interaction_script() +// are *almost* identical, except for the first parameter +// type. +// May these types be made children of the same base? +//----------------------------------------------------------- + + +// Returns 0 normally, or -1 to indicate that the NewInteraction has +// become invalid and don't run another interaction on it +// (eg. a room change occured) +int run_interaction_event (Interaction *nint, int evnt, int chkAny, int isInv) { + + if (evnt < 0 || (size_t)evnt >= nint->Events.size() || + (nint->Events[evnt].Response.get() == nullptr) || (nint->Events[evnt].Response->Cmds.size() == 0)) { + // no response defined for this event + // If there is a response for "Any Click", then abort now so as to + // run that instead + if (chkAny < 0) ; + else if ((size_t)chkAny < nint->Events.size() && + (nint->Events[chkAny].Response.get() != nullptr) && (nint->Events[chkAny].Response->Cmds.size() > 0)) + return 0; + + // Otherwise, run unhandled_event + run_unhandled_event(evnt); + + return 0; + } + + if (play.check_interaction_only) { + play.check_interaction_only = 2; + return -1; + } + + int cmdsrun = 0, retval = 0; + // Right, so there were some commands defined in response to the event. + retval = run_interaction_commandlist (nint->Events[evnt].Response.get(), &nint->Events[evnt].TimesRun, &cmdsrun); + + // An inventory interaction, but the wrong item was used + if ((isInv) && (cmdsrun == 0)) + run_unhandled_event (evnt); + + return retval; +} + +// Returns 0 normally, or -1 to indicate that the NewInteraction has +// become invalid and don't run another interaction on it +// (eg. a room change occured) +int run_interaction_script(InteractionScripts *nint, int evnt, int chkAny, int isInv) { + + if ((nint->ScriptFuncNames[evnt] == nullptr) || (nint->ScriptFuncNames[evnt][0u] == 0)) { + // no response defined for this event + // If there is a response for "Any Click", then abort now so as to + // run that instead + if (chkAny < 0) ; + else if ((nint->ScriptFuncNames[chkAny] != nullptr) && (nint->ScriptFuncNames[chkAny][0u] != 0)) + return 0; + + // Otherwise, run unhandled_event + run_unhandled_event(evnt); + + return 0; + } + + if (play.check_interaction_only) { + play.check_interaction_only = 2; + return -1; + } + + int room_was = play.room_changes; + + RuntimeScriptValue rval_null; + + update_polled_mp3(); + if ((strstr(evblockbasename,"character")!=nullptr) || (strstr(evblockbasename,"inventory")!=nullptr)) { + // Character or Inventory (global script) + QueueScriptFunction(kScInstGame, nint->ScriptFuncNames[evnt]); + } + else { + // Other (room script) + QueueScriptFunction(kScInstRoom, nint->ScriptFuncNames[evnt]); + } + update_polled_mp3(); + + int retval = 0; + // if the room changed within the action + if (room_was != play.room_changes) + retval = -1; + + return retval; +} + +int create_global_script() { + ccSetOption(SCOPT_AUTOIMPORT, 1); + for (int kk = 0; kk < numScriptModules; kk++) { + moduleInst[kk] = ccInstance::CreateFromScript(scriptModules[kk]); + if (moduleInst[kk] == nullptr) + return -3; + // create a forked instance for rep_exec_always + moduleInstFork[kk] = moduleInst[kk]->Fork(); + if (moduleInstFork[kk] == nullptr) + return -3; + + moduleRepExecAddr[kk] = moduleInst[kk]->GetSymbolAddress(REP_EXEC_NAME); + } + gameinst = ccInstance::CreateFromScript(gamescript); + if (gameinst == nullptr) + return -3; + // create a forked instance for rep_exec_always + gameinstFork = gameinst->Fork(); + if (gameinstFork == nullptr) + return -3; + + if (dialogScriptsScript != nullptr) + { + dialogScriptsInst = ccInstance::CreateFromScript(dialogScriptsScript); + if (dialogScriptsInst == nullptr) + return -3; + } + + ccSetOption(SCOPT_AUTOIMPORT, 0); + return 0; +} + +void cancel_all_scripts() { + int aa; + + for (aa = 0; aa < num_scripts; aa++) { + if (scripts[aa].forked) + scripts[aa].inst->AbortAndDestroy(); + else + scripts[aa].inst->Abort(); + scripts[aa].numanother = 0; + } + num_scripts = 0; + /* if (gameinst!=NULL) ->Abort(gameinst); + if (roominst!=NULL) ->Abort(roominst);*/ +} + +ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst) +{ + if (sc_inst == kScInstGame) + return gameinst; + else if (sc_inst == kScInstRoom) + return roominst; + return nullptr; +} + +void QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count, const RuntimeScriptValue &p1, const RuntimeScriptValue &p2) +{ + if (inside_script) + // queue the script for the run after current script is finished + curscript->run_another (fn_name, sc_inst, param_count, p1, p2); + else + // if no script is currently running, run the requested script right away + RunScriptFunction(sc_inst, fn_name, param_count, p1, p2); +} + +void RunScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count, const RuntimeScriptValue &p1, const RuntimeScriptValue &p2) +{ + ccInstance *sci = GetScriptInstanceByType(sc_inst); + if (sci) + { + if (param_count == 2) + RunTextScript2IParam(sci, fn_name, p1, p2); + else if (param_count == 1) + RunTextScriptIParam(sci, fn_name, p1); + else if (param_count == 0) + RunTextScript(sci, fn_name); + } +} + +bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction* funcToRun, bool hasTheFunc) +{ + if (!hasTheFunc) + return(false); + + no_blocking_functions++; + int result = 0; + + if (funcToRun->numParameters < 3) + { + result = sci->CallScriptFunction((char*)funcToRun->functionName, funcToRun->numParameters, funcToRun->params); + } + else + quit("DoRunScriptFuncCantBlock called with too many parameters"); + + if (result == -2) { + // the function doens't exist, so don't try and run it again + hasTheFunc = false; + } + else if ((result != 0) && (result != 100)) { + quit_with_script_error(funcToRun->functionName); + } + else + { + funcToRun->atLeastOneImplementationExists = true; + } + // this might be nested, so don't disrupt blocked scripts + ccErrorString = ""; + ccError = 0; + no_blocking_functions--; + return(hasTheFunc); +} + +char scfunctionname[MAX_FUNCTION_NAME_LEN + 1]; +int PrepareTextScript(ccInstance *sci, const char**tsname) +{ + ccError = 0; + // FIXME: try to make it so this function is not called with NULL sci + if (sci == nullptr) return -1; + if (sci->GetSymbolAddress(tsname[0]).IsNull()) { + ccErrorString = "no such function in script"; + return -2; + } + if (sci->IsBeingRun()) { + ccErrorString = "script is already in execution"; + return -3; + } + scripts[num_scripts].init(); + scripts[num_scripts].inst = sci; + // CHECKME: this conditional block will never run, because + // function would have quit earlier (deprecated functionality?) + if (sci->IsBeingRun()) { + scripts[num_scripts].inst = sci->Fork(); + if (scripts[num_scripts].inst == nullptr) + quit("unable to fork instance for secondary script"); + scripts[num_scripts].forked = 1; + } + curscript = &scripts[num_scripts]; + num_scripts++; + if (num_scripts >= MAX_SCRIPT_AT_ONCE) + quit("too many nested text script instances created"); + // in case script_run_another is the function name, take a backup + strncpy(scfunctionname, tsname[0], MAX_FUNCTION_NAME_LEN); + tsname[0] = &scfunctionname[0]; + update_script_mouse_coords(); + inside_script++; + // aborted_ip=0; + // abort_executor=0; + return 0; +} + +int RunScriptFunctionIfExists(ccInstance *sci, const char*tsname, int numParam, const RuntimeScriptValue *params) +{ + int oldRestoreCount = gameHasBeenRestored; + // First, save the current ccError state + // This is necessary because we might be attempting + // to run Script B, while Script A is still running in the + // background. + // If CallInstance here has an error, it would otherwise + // also abort Script A because ccError is a global variable. + int cachedCcError = ccError; + ccError = 0; + + int toret = PrepareTextScript(sci, &tsname); + if (toret) { + ccError = cachedCcError; + return -18; + } + + // Clear the error message + ccErrorString = ""; + + if (numParam < 3) + { + toret = curscript->inst->CallScriptFunction(tsname, numParam, params); + } + else + quit("Too many parameters to RunScriptFunctionIfExists"); + + // 100 is if Aborted (eg. because we are LoadAGSGame'ing) + if ((toret != 0) && (toret != -2) && (toret != 100)) { + quit_with_script_error(tsname); + } + + post_script_cleanup_stack++; + + if (post_script_cleanup_stack > 50) + quitprintf("!post_script_cleanup call stack exceeded: possible recursive function call? running %s", tsname); + + post_script_cleanup(); + + post_script_cleanup_stack--; + + // restore cached error state + ccError = cachedCcError; + + // if the game has been restored, ensure that any further scripts are not run + if ((oldRestoreCount != gameHasBeenRestored) && (eventClaimed == EVENT_INPROGRESS)) + eventClaimed = EVENT_CLAIMED; + + return toret; +} + +int RunTextScript(ccInstance *sci, const char *tsname) +{ + if (strcmp(tsname, REP_EXEC_NAME) == 0) { + // run module rep_execs + // FIXME: in theory the function may be already called for moduleInst[i], + // in which case this should not be executed; need to rearrange the code somehow + int room_changes_was = play.room_changes; + int restore_game_count_was = gameHasBeenRestored; + + for (int kk = 0; kk < numScriptModules; kk++) { + if (!moduleRepExecAddr[kk].IsNull()) + RunScriptFunctionIfExists(moduleInst[kk], tsname, 0, nullptr); + + if ((room_changes_was != play.room_changes) || + (restore_game_count_was != gameHasBeenRestored)) + return 0; + } + } + + int toret = RunScriptFunctionIfExists(sci, tsname, 0, nullptr); + if ((toret == -18) && (sci == roominst)) { + // functions in room script must exist + quitprintf("prepare_script: error %d (%s) trying to run '%s' (Room %d)", toret, ccErrorString.GetCStr(), tsname, displayed_room); + } + return toret; +} + +int RunTextScriptIParam(ccInstance *sci, const char *tsname, const RuntimeScriptValue &iparam) +{ + if ((strcmp(tsname, "on_key_press") == 0) || (strcmp(tsname, "on_mouse_click") == 0)) { + bool eventWasClaimed; + int toret = run_claimable_event(tsname, true, 1, &iparam, &eventWasClaimed); + + if (eventWasClaimed) + return toret; + } + + return RunScriptFunctionIfExists(sci, tsname, 1, &iparam); +} + +int RunTextScript2IParam(ccInstance *sci, const char*tsname, const RuntimeScriptValue &iparam, const RuntimeScriptValue ¶m2) +{ + RuntimeScriptValue params[2]; + params[0] = iparam; + params[1] = param2; + + if (strcmp(tsname, "on_event") == 0) { + bool eventWasClaimed; + int toret = run_claimable_event(tsname, true, 2, params, &eventWasClaimed); + + if (eventWasClaimed) + return toret; + } + + // response to a button click, better update guis + if (ags_strnicmp(tsname, "interface_click", 15) == 0) + guis_need_update = 1; + + return RunScriptFunctionIfExists(sci, tsname, 2, params); +} + +String GetScriptName(ccInstance *sci) +{ + // TODO: have script name a ccScript's member? + // TODO: check script modules too? + if (!sci) + return "Not in a script"; + else if (sci->instanceof == gamescript) + return "Global script"; + else if (sci->instanceof == thisroom.CompiledScript) + return String::FromFormat("Room %d script", displayed_room); + return "Unknown script"; +} + +//============================================================================= + + +char bname[MAX_FUNCTION_NAME_LEN+1],bne[MAX_FUNCTION_NAME_LEN+1]; +char* make_ts_func_name(const char*base,int iii,int subd) { + int err = snprintf(bname,MAX_FUNCTION_NAME_LEN,base,iii); + if (err >= sizeof(bname)) + debug_script_warn("Function name length limit exceeded: %s (%d)", base, iii); + err = snprintf(bne,MAX_FUNCTION_NAME_LEN,"%s_%c",bname,subd+'a'); + if (err >= sizeof(bne)) + debug_script_warn("Function name length limit exceeded: %s", bname); + return &bne[0]; +} + +void post_script_cleanup() { + // should do any post-script stuff here, like go to new room + if (ccError) quit(ccErrorString); + ExecutingScript copyof = scripts[num_scripts-1]; + if (scripts[num_scripts-1].forked) + delete scripts[num_scripts-1].inst; + num_scripts--; + inside_script--; + + if (num_scripts > 0) + curscript = &scripts[num_scripts-1]; + else { + curscript = nullptr; + } + // if (abort_executor) user_disabled_data2=aborted_ip; + + int old_room_number = displayed_room; + + // run the queued post-script actions + for (int ii = 0; ii < copyof.numPostScriptActions; ii++) { + int thisData = copyof.postScriptActionData[ii]; + + switch (copyof.postScriptActions[ii]) { + case ePSANewRoom: + // only change rooms when all scripts are done + if (num_scripts == 0) { + new_room(thisData, playerchar); + // don't allow any pending room scripts from the old room + // in run_another to be executed + return; + } + else + curscript->queue_action(ePSANewRoom, thisData, "NewRoom"); + break; + case ePSAInvScreen: + invscreen(); + break; + case ePSARestoreGame: + cancel_all_scripts(); + try_restore_save(thisData); + return; + case ePSARestoreGameDialog: + restore_game_dialog(); + return; + case ePSARunAGSGame: + cancel_all_scripts(); + load_new_game = thisData; + return; + case ePSARunDialog: + do_conversation(thisData); + break; + case ePSARestartGame: + cancel_all_scripts(); + restart_game(); + return; + case ePSASaveGame: + save_game(thisData, copyof.postScriptSaveSlotDescription[ii]); + break; + case ePSASaveGameDialog: + save_game_dialog(); + break; + default: + quitprintf("undefined post script action found: %d", copyof.postScriptActions[ii]); + } + // if the room changed in a conversation, for example, abort + if (old_room_number != displayed_room) { + return; + } + } + + + int jj; + for (jj = 0; jj < copyof.numanother; jj++) { + old_room_number = displayed_room; + QueuedScript &script = copyof.ScFnQueue[jj]; + RunScriptFunction(script.Instance, script.FnName, script.ParamCount, script.Param1, script.Param2); + if (script.Instance == kScInstRoom && script.ParamCount == 1) + { + // some bogus hack for "on_call" event handler + play.roomscript_finished = 1; + } + + // if they've changed rooms, cancel any further pending scripts + if ((displayed_room != old_room_number) || (load_new_game)) + break; + } + copyof.numanother = 0; + +} + +void quit_with_script_error(const char *functionName) +{ + // TODO: clean up the error reporting logic. Now engine will append call + // stack info in quit_check_for_error_state() but only in case of explicit + // script error ("!" type), and not in other case. + if (ccErrorIsUserError) + quitprintf("!Error running function '%s':\n%s", functionName, ccErrorString.GetCStr()); + else + quitprintf("Error running function '%s':\n%s\n\n%s", functionName, ccErrorString.GetCStr(), get_cur_script(5).GetCStr()); +} + +int get_nivalue (InteractionCommandList *nic, int idx, int parm) { + if (nic->Cmds[idx].Data[parm].Type == AGS::Common::kInterValVariable) { + // return the value of the variable + return get_interaction_variable(nic->Cmds[idx].Data[parm].Value)->Value; + } + return nic->Cmds[idx].Data[parm].Value; +} + +InteractionVariable *get_interaction_variable (int varindx) { + + if ((varindx >= LOCAL_VARIABLE_OFFSET) && ((size_t)varindx < LOCAL_VARIABLE_OFFSET + thisroom.LocalVariables.size())) + return &thisroom.LocalVariables[varindx - LOCAL_VARIABLE_OFFSET]; + + if ((varindx < 0) || (varindx >= numGlobalVars)) + quit("!invalid interaction variable specified"); + + return &globalvars[varindx]; +} + +InteractionVariable *FindGraphicalVariable(const char *varName) { + int ii; + for (ii = 0; ii < numGlobalVars; ii++) { + if (ags_stricmp (globalvars[ii].Name, varName) == 0) + return &globalvars[ii]; + } + for (size_t i = 0; i < thisroom.LocalVariables.size(); ++i) { + if (ags_stricmp (thisroom.LocalVariables[i].Name, varName) == 0) + return &thisroom.LocalVariables[i]; + } + return nullptr; +} + +#define IPARAM1 get_nivalue(nicl, i, 0) +#define IPARAM2 get_nivalue(nicl, i, 1) +#define IPARAM3 get_nivalue(nicl, i, 2) +#define IPARAM4 get_nivalue(nicl, i, 3) +#define IPARAM5 get_nivalue(nicl, i, 4) + +struct TempEip { + int oldval; + TempEip (int newval) { + oldval = our_eip; + our_eip = newval; + } + ~TempEip () { our_eip = oldval; } +}; + +// the 'cmdsrun' parameter counts how many commands are run. +// if a 'Inv Item Was Used' check does not pass, it doesn't count +// so cmdsrun remains 0 if no inventory items matched +int run_interaction_commandlist (InteractionCommandList *nicl, int *timesrun, int*cmdsrun) { + size_t i; + + if (nicl == nullptr) + return -1; + + for (i = 0; i < nicl->Cmds.size(); i++) { + cmdsrun[0] ++; + int room_was = play.room_changes; + + switch (nicl->Cmds[i].Type) { + case 0: // Do nothing + break; + case 1: // Run script + { + TempEip tempip(4001); + RuntimeScriptValue rval_null; + update_polled_mp3(); + if ((strstr(evblockbasename,"character")!=nullptr) || (strstr(evblockbasename,"inventory")!=nullptr)) { + // Character or Inventory (global script) + const char *torun = make_ts_func_name(evblockbasename,evblocknum,nicl->Cmds[i].Data[0].Value); + // we are already inside the mouseclick event of the script, can't nest calls + QueueScriptFunction(kScInstGame, torun); + } + else { + // Other (room script) + const char *torun = make_ts_func_name(evblockbasename,evblocknum,nicl->Cmds[i].Data[0].Value); + QueueScriptFunction(kScInstRoom, torun); + } + update_polled_mp3(); + break; + } + case 2: // Add score (first time) + if (timesrun[0] > 0) + break; + timesrun[0] ++; + case 3: // Add score + GiveScore (IPARAM1); + break; + case 4: // Display Message + /* if (comprdata<0) + display_message_aschar=evb->data[ss];*/ + DisplayMessage(IPARAM1); + break; + case 5: // Play Music + PlayMusicResetQueue(IPARAM1); + break; + case 6: // Stop Music + stopmusic (); + break; + case 7: // Play Sound + play_sound (IPARAM1); + break; + case 8: // Play Flic + play_flc_file(IPARAM1, IPARAM2); + break; + case 9: // Run Dialog + { int room_was = play.room_changes; + RunDialog(IPARAM1); + // if they changed room within the dialog script, + // the interaction command list is no longer valid + if (room_was != play.room_changes) + return -1; + } + break; + case 10: // Enable Dialog Option + SetDialogOption (IPARAM1, IPARAM2, 1); + break; + case 11: // Disable Dialog Option + SetDialogOption (IPARAM1, IPARAM2, 0); + break; + case 12: // Go To Screen + Character_ChangeRoomAutoPosition(playerchar, IPARAM1, IPARAM2); + return -1; + case 13: // Add Inventory + add_inventory (IPARAM1); + break; + case 14: // Move Object + MoveObject (IPARAM1, IPARAM2, IPARAM3, IPARAM4); + // if they want to wait until finished, do so + if (IPARAM5) + GameLoopUntilNotMoving(&objs[IPARAM1].moving); + break; + case 15: // Object Off + ObjectOff (IPARAM1); + break; + case 16: // Object On + ObjectOn (IPARAM1); + break; + case 17: // Set Object View + SetObjectView (IPARAM1, IPARAM2); + break; + case 18: // Animate Object + AnimateObject (IPARAM1, IPARAM2, IPARAM3, IPARAM4); + break; + case 19: // Move Character + if (IPARAM4) + MoveCharacterBlocking (IPARAM1, IPARAM2, IPARAM3, 0); + else + MoveCharacter (IPARAM1, IPARAM2, IPARAM3); + break; + case 20: // If Inventory Item was used + if (play.usedinv == IPARAM1) { + if (game.options[OPT_NOLOSEINV] == 0) + lose_inventory (play.usedinv); + if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) + return -1; + } + else + cmdsrun[0] --; + break; + case 21: // if player has inventory item + if (playerchar->inv[IPARAM1] > 0) + if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) + return -1; + break; + case 22: // if a character is moving + if (game.chars[IPARAM1].walking) + if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) + return -1; + break; + case 23: // if two variables are equal + if (IPARAM1 == IPARAM2) + if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) + return -1; + break; + case 24: // Stop character walking + StopMoving (IPARAM1); + break; + case 25: // Go to screen at specific co-ordinates + NewRoomEx (IPARAM1, IPARAM2, IPARAM3); + return -1; + case 26: // Move NPC to different room + if (!is_valid_character(IPARAM1)) + quit("!Move NPC to different room: invalid character specified"); + game.chars[IPARAM1].room = IPARAM2; + break; + case 27: // Set character view + SetCharacterView (IPARAM1, IPARAM2); + break; + case 28: // Release character view + ReleaseCharacterView (IPARAM1); + break; + case 29: // Follow character + FollowCharacter (IPARAM1, IPARAM2); + break; + case 30: // Stop following + FollowCharacter (IPARAM1, -1); + break; + case 31: // Disable hotspot + DisableHotspot (IPARAM1); + break; + case 32: // Enable hotspot + EnableHotspot (IPARAM1); + break; + case 33: // Set variable value + get_interaction_variable(nicl->Cmds[i].Data[0].Value)->Value = IPARAM2; + break; + case 34: // Run animation + scAnimateCharacter(IPARAM1, IPARAM2, IPARAM3, 0); + GameLoopUntilValueIsZero(&game.chars[IPARAM1].animating); + break; + case 35: // Quick animation + SetCharacterView (IPARAM1, IPARAM2); + scAnimateCharacter(IPARAM1, IPARAM3, IPARAM4, 0); + GameLoopUntilValueIsZero(&game.chars[IPARAM1].animating); + ReleaseCharacterView (IPARAM1); + break; + case 36: // Set idle animation + SetCharacterIdle (IPARAM1, IPARAM2, IPARAM3); + break; + case 37: // Disable idle animation + SetCharacterIdle (IPARAM1, -1, -1); + break; + case 38: // Lose inventory item + lose_inventory (IPARAM1); + break; + case 39: // Show GUI + InterfaceOn (IPARAM1); + break; + case 40: // Hide GUI + InterfaceOff (IPARAM1); + break; + case 41: // Stop running more commands + return -1; + case 42: // Face location + FaceLocation (IPARAM1, IPARAM2, IPARAM3); + break; + case 43: // Pause command processor + scrWait (IPARAM1); + break; + case 44: // Change character view + ChangeCharacterView (IPARAM1, IPARAM2); + break; + case 45: // If player character is + if (GetPlayerCharacter() == IPARAM1) + if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) + return -1; + break; + case 46: // if cursor mode is + if (GetCursorMode() == IPARAM1) + if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) + return -1; + break; + case 47: // if player has been to room + if (HasBeenToRoom(IPARAM1)) + if (run_interaction_commandlist (nicl->Cmds[i].Children.get(), timesrun, cmdsrun)) + return -1; + break; + default: + quit("unknown new interaction command"); + break; + } + + // if the room changed within the action, nicl is no longer valid + if (room_was != play.room_changes) + return -1; + } + return 0; + +} + +// check and abort game if the script is currently +// inside the rep_exec_always function +void can_run_delayed_command() { + if (no_blocking_functions) + quit("!This command cannot be used within non-blocking events such as " REP_EXEC_ALWAYS_NAME); +} + +void run_unhandled_event (int evnt) { + + if (play.check_interaction_only) + return; + + int evtype=0; + if (ags_strnicmp(evblockbasename,"hotspot",7)==0) evtype=1; + else if (ags_strnicmp(evblockbasename,"object",6)==0) evtype=2; + else if (ags_strnicmp(evblockbasename,"character",9)==0) evtype=3; + else if (ags_strnicmp(evblockbasename,"inventory",9)==0) evtype=5; + else if (ags_strnicmp(evblockbasename,"region",6)==0) + return; // no unhandled_events for regions + + // clicked Hotspot 0, so change the type code + if ((evtype == 1) & (evblocknum == 0) & (evnt != 0) & (evnt != 5) & (evnt != 6)) + evtype = 4; + if ((evtype==1) & ((evnt==0) | (evnt==5) | (evnt==6))) + ; // character stands on hotspot, mouse moves over hotspot, any click + else if ((evtype==2) & (evnt==4)) ; // any click on object + else if ((evtype==3) & (evnt==4)) ; // any click on character + else if (evtype > 0) { + can_run_delayed_command(); + + QueueScriptFunction(kScInstGame, "unhandled_event", 2, RuntimeScriptValue().SetInt32(evtype), RuntimeScriptValue().SetInt32(evnt)); + } +} diff --git a/engines/ags/engine/script/script.h b/engines/ags/engine/script/script.h new file mode 100644 index 00000000000..521bf78298f --- /dev/null +++ b/engines/ags/engine/script/script.h @@ -0,0 +1,119 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_EE_SCRIPT__SCRIPT_H +#define __AGS_EE_SCRIPT__SCRIPT_H + +#include + +#include "game/roomstruct.h" // MAX_ROOM_OBJECTS +#include "script/cc_instance.h" +#include "script/executingscript.h" +#include "script/nonblockingscriptfunction.h" +#include "ac/dynobj/scriptsystem.h" +#include "game/interactions.h" +#include "util/string.h" + +using AGS::Common::Interaction; +using AGS::Common::InteractionCommandList; +using AGS::Common::InteractionScripts; +using AGS::Common::InteractionVariable; + +#define LATE_REP_EXEC_ALWAYS_NAME "late_repeatedly_execute_always" +#define REP_EXEC_ALWAYS_NAME "repeatedly_execute_always" +#define REP_EXEC_NAME "repeatedly_execute" + +int run_dialog_request (int parmtr); +void run_function_on_non_blocking_thread(NonBlockingScriptFunction* funcToRun); +int run_interaction_event (Interaction *nint, int evnt, int chkAny = -1, int isInv = 0); +int run_interaction_script(InteractionScripts *nint, int evnt, int chkAny = -1, int isInv = 0); +int create_global_script(); +void cancel_all_scripts(); + +ccInstance *GetScriptInstanceByType(ScriptInstType sc_inst); +// Queues a script function to be run either called by the engine or from another script +void QueueScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0, + const RuntimeScriptValue &p1 = RuntimeScriptValue(), const RuntimeScriptValue &p2 = RuntimeScriptValue()); +// Try to run a script function right away +void RunScriptFunction(ScriptInstType sc_inst, const char *fn_name, size_t param_count = 0, + const RuntimeScriptValue &p1 = RuntimeScriptValue(), const RuntimeScriptValue &p2 = RuntimeScriptValue()); + +int RunScriptFunctionIfExists(ccInstance *sci, const char *tsname, int numParam, const RuntimeScriptValue *params); +int RunTextScript(ccInstance *sci, const char *tsname); +int RunTextScriptIParam(ccInstance *sci, const char *tsname, const RuntimeScriptValue &iparam); +int RunTextScript2IParam(ccInstance *sci, const char *tsname, const RuntimeScriptValue &iparam, const RuntimeScriptValue ¶m2); + +int PrepareTextScript(ccInstance *sci, const char **tsname); +bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction* funcToRun, bool hasTheFunc); + +AGS::Common::String GetScriptName(ccInstance *sci); + +//============================================================================= + +char* make_ts_func_name(const char*base,int iii,int subd); +// Performs various updates to the game after script interpreter returns control to the engine. +// Executes actions and does changes that are not executed immediately at script command, for +// optimisation and other reasons. +void post_script_cleanup(); +void quit_with_script_error(const char *functionName); +int get_nivalue (InteractionCommandList *nic, int idx, int parm); +int run_interaction_commandlist (InteractionCommandList *nicl, int *timesrun, int*cmdsrun); +InteractionVariable *get_interaction_variable (int varindx); +InteractionVariable *FindGraphicalVariable(const char *varName); +void run_unhandled_event (int evnt); +void can_run_delayed_command(); + + +extern ExecutingScript scripts[MAX_SCRIPT_AT_ONCE]; +extern ExecutingScript*curscript; + +extern PScript gamescript; +extern PScript dialogScriptsScript; +extern ccInstance *gameinst, *roominst; +extern ccInstance *dialogScriptsInst; +extern ccInstance *gameinstFork, *roominstFork; + +extern int num_scripts; +extern int post_script_cleanup_stack; + +extern int inside_script,in_graph_script; +extern int no_blocking_functions; // set to 1 while in rep_Exec_always + +extern NonBlockingScriptFunction repExecAlways; +extern NonBlockingScriptFunction lateRepExecAlways; +extern NonBlockingScriptFunction getDialogOptionsDimensionsFunc; +extern NonBlockingScriptFunction renderDialogOptionsFunc; +extern NonBlockingScriptFunction getDialogOptionUnderCursorFunc; +extern NonBlockingScriptFunction runDialogOptionMouseClickHandlerFunc; +extern NonBlockingScriptFunction runDialogOptionKeyPressHandlerFunc; +extern NonBlockingScriptFunction runDialogOptionRepExecFunc; + +extern ScriptSystem scsystem; + +extern std::vector scriptModules; +extern std::vector moduleInst; +extern std::vector moduleInstFork; +extern std::vector moduleRepExecAddr; +extern int numScriptModules; + +// TODO: find out if these extra arrays are really necessary. This may be remains from the +// time when the symbol import table was holding raw pointers to char array. +extern std::vector characterScriptObjNames; +extern AGS::Common::String objectScriptObjNames[MAX_ROOM_OBJECTS]; +extern std::vector guiScriptObjNames; + +#endif // __AGS_EE_SCRIPT__SCRIPT_H diff --git a/engines/ags/engine/script/script_api.cpp b/engines/ags/engine/script/script_api.cpp new file mode 100644 index 00000000000..efc6ee92590 --- /dev/null +++ b/engines/ags/engine/script/script_api.cpp @@ -0,0 +1,243 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "ac/game_version.h" +#include "script/cc_error.h" +#include "script/runtimescriptvalue.h" +#include "script/script_api.h" +#include "util/math.h" + +namespace Math = AGS::Common::Math; + +enum FormatParseResult +{ + kFormatParseNone, + kFormatParseInvalid, + kFormatParseLiteralPercent, + kFormatParseArgInteger, + kFormatParseArgFloat, + kFormatParseArgString, + kFormatParseArgPointer, + + kFormatParseArgFirst = kFormatParseArgInteger, + kFormatParseArgLast = kFormatParseArgPointer +}; + +// Helper functions for getting parameter value either from script val array or va_list +inline int GetArgInt(const RuntimeScriptValue *sc_args, va_list *varg_ptr, int arg_idx) +{ + if (varg_ptr) + return va_arg(*varg_ptr, int); + else + return sc_args[arg_idx].IValue; +} + +inline float GetArgFloat(const RuntimeScriptValue *sc_args, va_list *varg_ptr, int arg_idx) +{ + // note that script variables store only floats, but va_list has floats promoted to double + if (varg_ptr) + return (float)va_arg(*varg_ptr, double); + else + return sc_args[arg_idx].FValue; +} + +inline const char *GetArgPtr(const RuntimeScriptValue *sc_args, va_list *varg_ptr, int arg_idx) +{ + if (varg_ptr) + return va_arg(*varg_ptr, const char*); + else + return sc_args[arg_idx].Ptr; +} + + +// TODO: this implementation can be further optimised by either not calling +// snprintf but formatting values ourselves, or by using some library method +// that supports customizing, such as getting arguments in a custom way. +const char *ScriptSprintf(char *buffer, size_t buf_length, const char *format, + const RuntimeScriptValue *sc_args, int32_t sc_argc, va_list *varg_ptr) +{ + if (!buffer || buf_length == 0) + { + cc_error("Internal error in ScriptSprintf: buffer is null"); + return ""; + } + if (!format) + {// NOTE: interpreter (usually) catches null-pointer sent as format at some stage earlier + cc_error("Internal error in ScriptSprintf: format string is null"); + return ""; + } + if (!varg_ptr && sc_argc > 0 && !sc_args) + { + cc_error("Internal error in ScriptSprintf: args pointer is null"); + return ""; + } + + // Expected format character count: + // percent sign: 1 + // flag: 1 + // field width 10 (an uint32 number) + // precision sign 1 + // precision 10 (an uint32 number) + // length modifier 2 + // type 1 + // NOTE: although width and precision will + // not likely be defined by a 10-digit + // number, such case is theoretically valid. + const size_t fmtbuf_size = 27; + char fmtbuf[fmtbuf_size]; + char *fmt_bufptr; + char *fmt_bufendptr = &fmtbuf[fmtbuf_size - 1]; + + char *out_ptr = buffer; + // save 1 character for null terminator + const char *out_endptr = buffer + buf_length - 1; + const char *fmt_ptr = format; + int32_t arg_idx = 0; + + ptrdiff_t avail_outbuf; + int snprintf_res; + FormatParseResult fmt_done; + + // Parse the format string, looking for argument placeholders + while (*fmt_ptr && out_ptr != out_endptr) + { + // Try to put argument into placeholder + if (*fmt_ptr == '%') + { + avail_outbuf = out_endptr - out_ptr; + fmt_bufptr = fmtbuf; + *(fmt_bufptr++) = '%'; + snprintf_res = 0; + fmt_done = kFormatParseNone; + + // Parse placeholder + while (*(++fmt_ptr) && fmt_done == kFormatParseNone && fmt_bufptr != fmt_bufendptr) + { + *(fmt_bufptr++) = *fmt_ptr; + switch (*fmt_ptr) + { + case 'd': + case 'i': + case 'o': + case 'u': + case 'x': + case 'X': + case 'c': + fmt_done = kFormatParseArgInteger; + break; + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + case 'a': + case 'A': + fmt_done = kFormatParseArgFloat; + break; + case 'p': + fmt_done = kFormatParseArgPointer; + break; + case 's': + fmt_done = kFormatParseArgString; + break; + case '%': + // This may be a literal percent sign ('%%') + if (fmt_bufptr - fmtbuf == 2) + { + fmt_done = kFormatParseLiteralPercent; + } + // ...Otherwise we reached the next placeholder + else + { + fmt_ptr--; + fmt_bufptr--; + fmt_done = kFormatParseInvalid; + } + break; + } + } + + // Deal with the placeholder parsing results + if (fmt_done == kFormatParseLiteralPercent) + { + // literal percent sign + *(out_ptr++) = '%'; + continue; + } + else if (fmt_done >= kFormatParseArgFirst && fmt_done <= kFormatParseArgLast && + (varg_ptr || arg_idx < sc_argc)) + { + // Print the actual value + // NOTE: snprintf is called with avail_outbuf + 1 here, because we let it use our reserved + // character for null-terminator, in case we are at the end of the buffer + *fmt_bufptr = 0; // terminate the format buffer, we are going to use it + if (fmt_done == kFormatParseArgInteger) + snprintf_res = snprintf(out_ptr, avail_outbuf + 1, fmtbuf, GetArgInt(sc_args, varg_ptr, arg_idx)); + else if (fmt_done == kFormatParseArgFloat) + snprintf_res = snprintf(out_ptr, avail_outbuf + 1, fmtbuf, GetArgFloat(sc_args, varg_ptr, arg_idx)); + else + { + const char *p = GetArgPtr(sc_args, varg_ptr, arg_idx); + // Do extra checks for %s placeholder + if (fmt_done == kFormatParseArgString && !p) + { + if (loaded_game_file_version < kGameVersion_320) + { + // explicitly put "(null)" into the placeholder + p = "(null)"; + } + else + { + cc_error("!ScriptSprintf: formatting argument %d is expected to be a string, but it is a null pointer", arg_idx + 1); + return ""; + } + } + else if (fmt_done == kFormatParseArgString && p == buffer) + { + cc_error("!ScriptSprintf: formatting argument %d is a pointer to output buffer", arg_idx + 1); + return ""; + } + snprintf_res = snprintf(out_ptr, avail_outbuf + 1, fmtbuf, p); + } + + arg_idx++; + if (snprintf_res >= 0) + { + // snprintf returns maximal number of characters, so limit it with buffer size + out_ptr += Math::Min(snprintf_res, avail_outbuf); + continue; + } + // -- pass further to invalid format case + } + + // If format was not valid, or there are no available + // parameters, just copy stored format buffer as it is + size_t copy_len = Math::Min(Math::Min(fmt_bufptr - fmtbuf, fmtbuf_size - 1), avail_outbuf); + memcpy(out_ptr, fmtbuf, copy_len); + out_ptr += copy_len; + } + // If there's no placeholder, simply copy the character to output buffer + else + { + *(out_ptr++) = *(fmt_ptr++); + } + } + + // Terminate the string + *out_ptr = 0; + return buffer; +} diff --git a/engines/ags/engine/script/script_api.h b/engines/ags/engine/script/script_api.h new file mode 100644 index 00000000000..f17183850e2 --- /dev/null +++ b/engines/ags/engine/script/script_api.h @@ -0,0 +1,550 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Script API function type and helper macros for forwarding runtime script +// values to real engine functions. +// +//============================================================================= +#ifndef __AGS_EE_SCRIPT__SCRIPTAPI_H +#define __AGS_EE_SCRIPT__SCRIPTAPI_H + +#include +#include "core/types.h" +#include "ac/runtime_defines.h" +#include "ac/statobj/agsstaticobject.h" +#include "debug/out.h" + +struct RuntimeScriptValue; + +// TODO: replace void* with base object class when possible; also put array class for parameters +typedef RuntimeScriptValue ScriptAPIFunction(const RuntimeScriptValue *params, int32_t param_count); +typedef RuntimeScriptValue ScriptAPIObjectFunction(void *self, const RuntimeScriptValue *params, int32_t param_count); + +// Sprintf that takes either script values or common argument list from plugin. +// Uses EITHER sc_args/sc_argc or varg_ptr as parameter list, whichever is not +// NULL, with varg_ptr having HIGHER priority. +const char *ScriptSprintf(char *buffer, size_t buf_length, const char *format, + const RuntimeScriptValue *sc_args, int32_t sc_argc, va_list *varg_ptr); +// Sprintf that takes script values as arguments +inline const char *ScriptSprintf(char *buffer, size_t buf_length, const char *format, const RuntimeScriptValue *args, int32_t argc) +{ + return ScriptSprintf(buffer, buf_length, format, args, argc, nullptr); +} +// Variadic sprintf (needed, because all arguments are pushed as pointer-sized values). Currently used only when plugin calls +// exported engine function. Should be removed when this plugin issue is resolved. +inline const char *ScriptVSprintf(char *buffer, size_t buf_length, const char *format, va_list &arg_ptr) +{ + return ScriptSprintf(buffer, buf_length, format, nullptr, 0, &arg_ptr); +} + +// Helper macros for script functions +#define ASSERT_SELF(METHOD) \ + assert((self != NULL) && "Object pointer is null in call to API function") +#define ASSERT_PARAM_COUNT(FUNCTION, X) \ + assert((params != NULL && param_count >= X) && "Not enough parameters in call to API function") +#define ASSERT_VARIABLE_VALUE(VARIABLE) \ + assert((params != NULL && param_count >= 1) && "Not enough parameters to set API property") +#define ASSERT_OBJ_PARAM_COUNT(METHOD, X) \ + ASSERT_SELF(METHOD); \ + ASSERT_PARAM_COUNT(METHOD, X) + +//----------------------------------------------------------------------------- +// Get/set variables + +#define API_VARGET_INT(VARIABLE) \ + return RuntimeScriptValue().SetInt32(VARIABLE) + +#define API_VARSET_PINT(VARIABLE) \ + ASSERT_VARIABLE_VALUE(VARIABLE); \ + VARIABLE = params[0].IValue; \ + return RuntimeScriptValue() + +//----------------------------------------------------------------------------- +// Calls to ScriptSprintf + +#define API_SCALL_SCRIPT_SPRINTF(FUNCTION, PARAM_COUNT) \ + ASSERT_PARAM_COUNT(FUNCTION, PARAM_COUNT); \ + char ScSfBuffer[STD_BUFFER_SIZE]; \ + const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, get_translation(params[PARAM_COUNT - 1].Ptr), params + PARAM_COUNT, param_count - PARAM_COUNT) + +#define API_OBJCALL_SCRIPT_SPRINTF(METHOD, PARAM_COUNT) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, PARAM_COUNT); \ + char ScSfBuffer[STD_BUFFER_SIZE]; \ + const char *scsf_buffer = ScriptSprintf(ScSfBuffer, STD_BUFFER_SIZE, get_translation(params[PARAM_COUNT - 1].Ptr), params + PARAM_COUNT, param_count - PARAM_COUNT) + +//----------------------------------------------------------------------------- +// Calls to ScriptSprintfV (unsafe plugin variant) + +#define API_PLUGIN_SCRIPT_SPRINTF(FORMAT_STR) \ + va_list args; \ + va_start(args, FORMAT_STR); \ + char ScSfBuffer[STD_BUFFER_SIZE]; \ + const char *scsf_buffer = ScriptVSprintf(ScSfBuffer, STD_BUFFER_SIZE, get_translation(FORMAT_STR), args); \ + va_end(args) + +//----------------------------------------------------------------------------- +// Calls to static functions +// +// IMPORTANT: please note following: historically AGS compiler did not have +// proper "void" type and allowed to store the return value of "void" API +// functions as an integer (although that value did not have any practical +// meaning). For backwards compatibility we actually return integer value +// of '0' in all the VOID script API functions! +// + +#define API_SCALL_VOID(FUNCTION) \ + FUNCTION(); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PBOOL(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + FUNCTION(params[0].GetAsBool()); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + FUNCTION(params[0].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT2(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + FUNCTION(params[0].IValue, params[1].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT3(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 3); \ + FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT4(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 4); \ + FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT5(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 5); \ + FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT6(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 6); \ + FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, params[5].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT_POBJ(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + FUNCTION(params[0].IValue, (P1CLASS*)params[1].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT_POBJ2(FUNCTION, P1CLASS, P2CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 3); \ + FUNCTION(params[0].IValue, (P1CLASS*)params[1].Ptr, (P2CLASS*)params[2].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT2_POBJ(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 3); \ + FUNCTION(params[0].IValue, params[1].IValue, (P1CLASS*)params[2].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT3_POBJ_PINT(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 5); \ + FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, (P1CLASS*)params[3].Ptr, params[4].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PINT4_POBJ(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 5); \ + FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, (P1CLASS*)params[4].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_PFLOAT2(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + FUNCTION(params[0].FValue, params[1].FValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_POBJ(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + FUNCTION((P1CLASS*)params[0].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_POBJ_PINT(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_POBJ_PINT2(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 3); \ + FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue, params[2].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_VOID_POBJ2(FUNCTION, P1CLASS, P2CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + FUNCTION((P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_SCALL_INT(FUNCTION) \ + return RuntimeScriptValue().SetInt32(FUNCTION()) + +#define API_SCALL_INT_PINT(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + return RuntimeScriptValue().SetInt32(FUNCTION(params[0].IValue)) + +#define API_SCALL_INT_PINT2(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + return RuntimeScriptValue().SetInt32(FUNCTION(params[0].IValue, params[1].IValue)) + +#define API_SCALL_INT_PINT3(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 3); \ + return RuntimeScriptValue().SetInt32(FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue)) + +#define API_SCALL_INT_PINT4(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 4); \ + return RuntimeScriptValue().SetInt32(FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue)) + +#define API_SCALL_INT_PINT4_PFLOAT(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 5) \ + return RuntimeScriptValue().SetInt32(FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[3].FValue)) + +#define API_SCALL_INT_PINT5(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 5); \ + return RuntimeScriptValue().SetInt32(FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue)) + +#define API_SCALL_INT_PFLOAT_PINT(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + return RuntimeScriptValue().SetInt32(FUNCTION(params[0].FValue, params[1].IValue)) + +#define API_SCALL_INT_POBJ(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + return RuntimeScriptValue().SetInt32(FUNCTION((P1CLASS*)params[0].Ptr)) + +#define API_SCALL_INT_POBJ_PINT(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + return RuntimeScriptValue().SetInt32(FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue)) + +#define API_SCALL_INT_POBJ_PINT2(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 3); \ + return RuntimeScriptValue().SetInt32(FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue, params[2].IValue)) + +#define API_SCALL_INT_POBJ2(FUNCTION, P1CLASS, P2CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + return RuntimeScriptValue().SetInt32(FUNCTION((P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr)) + +#define API_SCALL_INT_PINT_POBJ(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + return RuntimeScriptValue().SetInt32(FUNCTION(params[0].IValue, (P1CLASS*)params[1].Ptr)) + +#define API_SCALL_FLOAT(FUNCTION) \ + return RuntimeScriptValue().SetFloat(FUNCTION()) + +#define API_SCALL_FLOAT_PINT(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + return RuntimeScriptValue().SetFloat(FUNCTION(params[0].IValue)) + +#define API_SCALL_FLOAT_PFLOAT(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + return RuntimeScriptValue().SetFloat(FUNCTION(params[0].FValue)) + +#define API_SCALL_FLOAT_PFLOAT2(FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + return RuntimeScriptValue().SetFloat(FUNCTION(params[0].FValue, params[1].FValue)) + +#define API_SCALL_BOOL(FUNCTION) \ + return RuntimeScriptValue().SetInt32AsBool(FUNCTION()) + +#define API_SCALL_BOOL_OBJ(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + return RuntimeScriptValue().SetInt32AsBool(FUNCTION((P1CLASS*)params[0].Ptr)) + +#define API_SCALL_BOOL_POBJ_PINT(FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + return RuntimeScriptValue().SetInt32AsBool(FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue)) + +#define API_SCALL_BOOL_POBJ2(FUNCTION, P1CLASS, P2CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + return RuntimeScriptValue().SetInt32AsBool(FUNCTION((P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr)) + +#define API_SCALL_OBJ(RET_CLASS, RET_MGR, FUNCTION) \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION(), &RET_MGR) + +#define API_SCALL_OBJ_PINT(RET_CLASS, RET_MGR, FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue), &RET_MGR) + +#define API_SCALL_OBJ_POBJ_PINT_PBOOL(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 3); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue, params[2].GetAsBool()), &RET_MGR) + +#define API_SCALL_OBJ_PINT2(RET_CLASS, RET_MGR, FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue), &RET_MGR) + +#define API_SCALL_OBJ_PINT3_POBJ(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 4); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, (P1CLASS*)params[3].Ptr), &RET_MGR) + +#define API_SCALL_OBJ_POBJ(RET_CLASS, RET_MGR, FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr), &RET_MGR) + +#define API_SCALL_OBJAUTO(RET_CLASS, FUNCTION) \ + RET_CLASS* ret_obj = FUNCTION(); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_SCALL_OBJAUTO_PINT(RET_CLASS, FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + RET_CLASS* ret_obj = FUNCTION(params[0].IValue); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_SCALL_OBJAUTO_PINT2(RET_CLASS, FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + RET_CLASS* ret_obj = FUNCTION(params[0].IValue, params[1].IValue); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_SCALL_OBJAUTO_PINT3(RET_CLASS, FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 3); \ + RET_CLASS* ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_SCALL_OBJAUTO_PINT4(RET_CLASS, FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 4); \ + RET_CLASS* ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_SCALL_OBJAUTO_PINT5(RET_CLASS, FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 5); \ + RET_CLASS* ret_obj = FUNCTION(params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_SCALL_OBJAUTO_PBOOL2(RET_CLASS, FUNCTION) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + RET_CLASS* ret_obj = FUNCTION(params[0].GetAsBool(), params[1].GetAsBool()); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_SCALL_OBJAUTO_POBJ(RET_CLASS, FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 1); \ + RET_CLASS* ret_obj = FUNCTION((P1CLASS*)params[0].Ptr); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_SCALL_OBJAUTO_POBJ_PINT(RET_CLASS, FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + RET_CLASS* ret_obj = (RET_CLASS*)FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_SCALL_OBJAUTO_POBJ_PINT4(RET_CLASS, FUNCTION, P1CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 5); \ + RET_CLASS* ret_obj = FUNCTION((P1CLASS*)params[0].Ptr, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + + +#define API_SCALL_STOBJ_POBJ2(RET_CLASS, FUNCTION, P1CLASS, P2CLASS) \ + ASSERT_PARAM_COUNT(FUNCTION, 2); \ + RET_CLASS* ret_obj = FUNCTION((P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr); \ + return RuntimeScriptValue().SetStaticObject(ret_obj, &GlobalStaticManager) + +//----------------------------------------------------------------------------- +// Calls to object functions + +#define API_OBJCALL_VOID(CLASS, METHOD) \ + ASSERT_SELF(METHOD); \ + METHOD((CLASS*)self); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + METHOD((CLASS*)self, params[0].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT2(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + METHOD((CLASS*)self, params[0].IValue, params[1].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT3(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \ + METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT4(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 4); \ + METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT5(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 5); \ + METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT6(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 6); \ + METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, params[5].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PFLOAT(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + METHOD((CLASS*)self, params[0].FValue); \ + return RuntimeScriptValue() + +#define API_OBJCALL_VOID_PFLOAT2(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + METHOD((CLASS*)self, params[0].FValue, params[1].FValue); \ + return RuntimeScriptValue() + +#define API_OBJCALL_VOID_PBOOL(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + METHOD((CLASS*)self, params[0].GetAsBool()); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT_PBOOL(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + METHOD((CLASS*)self, params[0].IValue, params[1].GetAsBool()); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT_POBJ(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + METHOD((CLASS*)self, params[0].IValue, (P1CLASS*)params[1].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT3_POBJ(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 4); \ + METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue, (P1CLASS*)params[3].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_PINT5_POBJ(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 6); \ + METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue, params[3].IValue, params[4].IValue, (P1CLASS*)params[5].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_POBJ(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_POBJ_PINT(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, params[1].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_POBJ_PINT2(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \ + METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, params[1].IValue, params[2].IValue); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_VOID_POBJ2(CLASS, METHOD, P1CLASS, P2CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr); \ + return RuntimeScriptValue((int32_t)0) + +#define API_OBJCALL_INT(CLASS, METHOD) \ + ASSERT_SELF(METHOD); \ + return RuntimeScriptValue().SetInt32(METHOD((CLASS*)self)) + +#define API_OBJCALL_INT_PINT(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + return RuntimeScriptValue().SetInt32(METHOD((CLASS*)self, params[0].IValue)) + +#define API_OBJCALL_INT_PINT_POBJ(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + return RuntimeScriptValue().SetInt32(METHOD((CLASS*)self, params[0].IValue, params[1].Ptr)) + +#define API_OBJCALL_INT_PINT2(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + return RuntimeScriptValue().SetInt32(METHOD((CLASS*)self, params[0].IValue, params[1].IValue)) + +#define API_OBJCALL_INT_POBJ(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + return RuntimeScriptValue().SetInt32(METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr)) + +#define API_OBJCALL_INT_POBJ_PINT(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + return RuntimeScriptValue().SetInt32(METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, params[1].IValue)) + +#define API_OBJCALL_INT_POBJ_PBOOL(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + return RuntimeScriptValue().SetInt32(METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, params[1].GetAsBool())) + +#define API_OBJCALL_FLOAT(CLASS, METHOD) \ + ASSERT_SELF(METHOD); \ + return RuntimeScriptValue().SetFloat(METHOD((CLASS*)self)) + +#define API_OBJCALL_BOOL(CLASS, METHOD) \ + ASSERT_SELF(METHOD); \ + return RuntimeScriptValue().SetInt32AsBool(METHOD((CLASS*)self)) + +#define API_OBJCALL_BOOL_PINT(CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + return RuntimeScriptValue().SetInt32AsBool(METHOD((CLASS*)self, params[0].IValue)) + +#define API_OBJCALL_BOOL_POBJ(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + return RuntimeScriptValue().SetInt32AsBool(METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr)) + +#define API_OBJCALL_BOOL_POBJ_PINT(CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + return RuntimeScriptValue().SetInt32AsBool(METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, params[1].IValue)) + +#define API_OBJCALL_BOOL_POBJ2(CLASS, METHOD, P1CLASS, P2CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + return RuntimeScriptValue().SetInt32AsBool(METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr)) + +#define API_OBJCALL_BOOL(CLASS, METHOD) \ + ASSERT_SELF(METHOD); \ + return RuntimeScriptValue().SetInt32AsBool(METHOD((CLASS*)self)) + +#define API_OBJCALL_OBJ_PINT_POBJ(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + return RuntimeScriptValue().SetDynamicObject((void*)METHOD((CLASS*)self, params[0].IValue, (P1CLASS*)params[1].Ptr), &RET_MGR) + +#define API_OBJCALL_OBJ_POBJ2_PINT(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS, P2CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \ + return RuntimeScriptValue().SetDynamicObject((void*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].IValue), &RET_MGR) + +#define API_OBJCALL_OBJ_POBJ2_PBOOL(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS, P2CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \ + return RuntimeScriptValue().SetDynamicObject((void*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr, (P2CLASS*)params[1].Ptr, params[2].GetAsBool()), &RET_MGR) + +#define API_OBJCALL_OBJ(CLASS, RET_CLASS, RET_MGR, METHOD) \ + ASSERT_SELF(METHOD); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self), &RET_MGR) + +#define API_OBJCALL_OBJ_PINT(CLASS, RET_CLASS, RET_MGR, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue), &RET_MGR) + +#define API_OBJCALL_OBJ_PINT2(CLASS, RET_CLASS, RET_MGR, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 2); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue), &RET_MGR) + +#define API_OBJCALL_OBJ_PINT3(CLASS, RET_CLASS, RET_MGR, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].IValue), &RET_MGR) + +#define API_OBJCALL_OBJ_POBJ(CLASS, RET_CLASS, RET_MGR, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + return RuntimeScriptValue().SetDynamicObject((void*)(RET_CLASS*)METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr), &RET_MGR) + +#define API_OBJCALL_OBJAUTO(CLASS, RET_CLASS, METHOD) \ + ASSERT_SELF(METHOD); \ + RET_CLASS* ret_obj = METHOD((CLASS*)self); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_OBJCALL_OBJAUTO_PINT2_PBOOL(CLASS, RET_CLASS, METHOD) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 3); \ + RET_CLASS* ret_obj = METHOD((CLASS*)self, params[0].IValue, params[1].IValue, params[2].GetAsBool()); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#define API_OBJCALL_OBJAUTO_POBJ(CLASS, RET_CLASS, METHOD, P1CLASS) \ + ASSERT_OBJ_PARAM_COUNT(METHOD, 1); \ + RET_CLASS* ret_obj = METHOD((CLASS*)self, (P1CLASS*)params[0].Ptr); \ + return RuntimeScriptValue().SetDynamicObject(ret_obj, ret_obj) + +#endif // __AGS_EE_SCRIPT__SCRIPTAPI_H diff --git a/engines/ags/engine/script/script_engine.cpp b/engines/ags/engine/script/script_engine.cpp new file mode 100644 index 00000000000..eefd707fe06 --- /dev/null +++ b/engines/ags/engine/script/script_engine.cpp @@ -0,0 +1,52 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Script Editor run-time engine component (c) 1998 Chris Jones +// script chunk format: +// 00h 1 dword version - should be 2 +// 04h 1 dword sizeof(scriptblock) +// 08h 1 dword number of ScriptBlocks +// 0Ch n STRUCTs ScriptBlocks +// +//============================================================================= + +#include +#include "script/cc_instance.h" +#include "script/cc_error.h" +#include "util/file.h" +#include "util/stream.h" + +namespace AGS { namespace Common { class RoomStruct; } } +using namespace AGS::Common; + +extern void quit(const char *); +extern int currentline; // in script/script_common + +std::pair cc_error_at_line(const char *error_msg) +{ + ccInstance *sci = ccInstance::GetCurrentInstance(); + if (!sci) + { + return std::make_pair(String::FromFormat("Error (line %d): %s", currentline, error_msg), String()); + } + else + { + return std::make_pair(String::FromFormat("Error: %s\n", error_msg), ccInstance::GetCurrentInstance()->GetCallStack(5)); + } +} + +String cc_error_without_line(const char *error_msg) +{ + return String::FromFormat("Runtime error: %s", error_msg); +} diff --git a/engines/ags/engine/script/script_runtime.cpp b/engines/ags/engine/script/script_runtime.cpp new file mode 100644 index 00000000000..81a6d881bc5 --- /dev/null +++ b/engines/ags/engine/script/script_runtime.cpp @@ -0,0 +1,291 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// C-Script run-time interpreter (c) 2001 Chris Jones +// +// You must DISABLE OPTIMIZATIONS AND REGISTER VARIABLES in your compiler +// when compiling this, or strange results can happen. +// +// There is a problem with importing functions on 16-bit compilers: the +// script system assumes that all parameters are passed as 4 bytes, which +// ints are not on 16-bit systems. Be sure to define all parameters as longs, +// or join the 21st century and switch to DJGPP or Visual C++. +// +//============================================================================= + +#include +#include +#include +#include "script/script_runtime.h" +#include "script/script_common.h" +#include "script/cc_error.h" +#include "script/cc_options.h" +#include "ac/dynobj/cc_dynamicarray.h" +#include "script/systemimports.h" +#include "ac/statobj/staticobject.h" + +extern ccInstance *current_instance; // in script/cc_instance + +bool ccAddExternalStaticFunction(const String &name, ScriptAPIFunction *pfn) +{ + return simp.add(name, RuntimeScriptValue().SetStaticFunction(pfn), nullptr) == 0; +} + +bool ccAddExternalPluginFunction(const String &name, void *pfn) +{ + return simp.add(name, RuntimeScriptValue().SetPluginFunction(pfn), nullptr) == 0; +} + +bool ccAddExternalStaticObject(const String &name, void *ptr, ICCStaticObject *manager) +{ + return simp.add(name, RuntimeScriptValue().SetStaticObject(ptr, manager), nullptr) == 0; +} + +bool ccAddExternalStaticArray(const String &name, void *ptr, StaticArray *array_mgr) +{ + return simp.add(name, RuntimeScriptValue().SetStaticArray(ptr, array_mgr), nullptr) == 0; +} + +bool ccAddExternalDynamicObject(const String &name, void *ptr, ICCDynamicObject *manager) +{ + return simp.add(name, RuntimeScriptValue().SetDynamicObject(ptr, manager), nullptr) == 0; +} + +bool ccAddExternalObjectFunction(const String &name, ScriptAPIObjectFunction *pfn) +{ + return simp.add(name, RuntimeScriptValue().SetObjectFunction(pfn), nullptr) == 0; +} + +bool ccAddExternalScriptSymbol(const String &name, const RuntimeScriptValue &prval, ccInstance *inst) +{ + return simp.add(name, prval, inst) == 0; +} + +void ccRemoveExternalSymbol(const String &name) +{ + simp.remove(name); +} + +void ccRemoveAllSymbols() +{ + simp.clear(); +} + +ccInstance *loadedInstances[MAX_LOADED_INSTANCES] = {nullptr, +nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, +nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; + +void nullfree(void *data) +{ + if (data != nullptr) + free(data); +} + +void *ccGetSymbolAddress(const String &name) +{ + const ScriptImport *import = simp.getByName(name); + if (import) + { + return import->Value.Ptr; + } + return nullptr; +} + +bool ccAddExternalFunctionForPlugin(const String &name, void *pfn) +{ + return simp_for_plugin.add(name, RuntimeScriptValue().SetPluginFunction(pfn), nullptr) == 0; +} + +void *ccGetSymbolAddressForPlugin(const String &name) +{ + const ScriptImport *import = simp_for_plugin.getByName(name); + if (import) + { + return import->Value.Ptr; + } + else + { + // Also search the internal symbol table for non-function symbols + import = simp.getByName(name); + if (import) + { + return import->Value.Ptr; + } + } + return nullptr; +} + +new_line_hook_type new_line_hook = nullptr; + +char ccRunnerCopyright[] = "ScriptExecuter32 v" SCOM_VERSIONSTR " (c) 2001 Chris Jones"; +int maxWhileLoops = 0; + +// If a while loop does this many iterations without the +// NofityScriptAlive function getting called, the script +// aborts. Set to 0 to disable. +void ccSetScriptAliveTimer (int numloop) { + maxWhileLoops = numloop; +} + +void ccNotifyScriptStillAlive () { + if (current_instance != nullptr) + current_instance->flags |= INSTF_RUNNING; +} + +void ccSetDebugHook(new_line_hook_type jibble) +{ + new_line_hook = jibble; +} + +int call_function(intptr_t addr, const RuntimeScriptValue *object, int numparm, const RuntimeScriptValue *parms) +{ + if (!addr) + { + cc_error("null function pointer in call_function"); + return -1; + } + if (numparm > 0 && !parms) + { + cc_error("invalid parameters array in call_function"); + return -1; + } + + intptr_t parm_value[9]; + if (object) + { + parm_value[0] = (intptr_t)object->GetPtrWithOffset(); + numparm++; + } + + for (int ival = object ? 1 : 0, iparm = 0; ival < numparm; ++ival, ++iparm) + { + switch (parms[iparm].Type) + { + case kScValInteger: + case kScValFloat: // AGS passes floats, copying their values into long variable + case kScValPluginArg: + parm_value[ival] = (intptr_t)parms[iparm].IValue; + break; + break; + default: + parm_value[ival] = (intptr_t)parms[iparm].GetPtrWithOffset(); + break; + } + } + + // + // AN IMPORTANT NOTE ON PARAM TYPE + // of 2012-11-10 + // + //// NOTE of 2012-12-20: + //// Everything said below is applicable only for calling + //// exported plugin functions. + // + // Here we are sending parameters of type intptr_t to registered + // function of unknown kind. Intptr_t is 32-bit for x32 build and + // 64-bit for x64 build. + // The exported functions usually have two types of parameters: + // pointer and 'int' (32-bit). For x32 build those two have the + // same size, but for x64 build first has 64-bit size while the + // second remains 32-bit. + // In formal case that would cause 'overflow' - function will + // receive more data than needed (written to stack), with some + // values shifted further by 32 bits. + // + // Upon testing, however, it was revealed that AMD64 processor, + // the only platform we support x64 Linux AGS build on right now, + // treats all the function parameters pushed to stack as 64-bit + // values (few first parameters are sent via registers, and hence + // are least concern anyway). Therefore, no 'overflow' occurs, + // and 64-bit values are being effectively truncated to 32-bit + // integers in the callee. + // + // Since this is still quite unreliable, this should be + // reimplemented when there's enough free time available for + // developers both for coding & testing. + // + // Most basic idea is to pass array of RuntimeScriptValue + // objects (that hold type description) and get same RSV as a + // return result. Keep in mind, though, that this solution will + // require fixing ALL exported functions, so a good amount of + // time and energy should be allocated for this task. + // + + switch (numparm) + { + case 0: + { + int (*fparam) (); + fparam = (int (*)())addr; + return fparam(); + } + case 1: + { + int (*fparam) (intptr_t); + fparam = (int (*)(intptr_t))addr; + return fparam(parm_value[0]); + } + case 2: + { + int (*fparam) (intptr_t, intptr_t); + fparam = (int (*)(intptr_t, intptr_t))addr; + return fparam(parm_value[0], parm_value[1]); + } + case 3: + { + int (*fparam) (intptr_t, intptr_t, intptr_t); + fparam = (int (*)(intptr_t, intptr_t, intptr_t))addr; + return fparam(parm_value[0], parm_value[1], parm_value[2]); + } + case 4: + { + int (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t); + fparam = (int (*)(intptr_t, intptr_t, intptr_t, intptr_t))addr; + return fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3]); + } + case 5: + { + int (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t); + fparam = (int (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))addr; + return fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4]); + } + case 6: + { + int (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t); + fparam = (int (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))addr; + return fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4], parm_value[5]); + } + case 7: + { + int (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t); + fparam = (int (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))addr; + return fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4], parm_value[5], parm_value[6]); + } + case 8: + { + int (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t); + fparam = (int (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))addr; + return fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4], parm_value[5], parm_value[6], parm_value[7]); + } + case 9: + { + int (*fparam) (intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t); + fparam = (int (*)(intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t))addr; + return fparam(parm_value[0], parm_value[1], parm_value[2], parm_value[3], parm_value[4], parm_value[5], parm_value[6], parm_value[7], parm_value[8]); + } + } + + cc_error("too many arguments in call to function"); + return -1; +} diff --git a/engines/ags/engine/script/script_runtime.h b/engines/ags/engine/script/script_runtime.h new file mode 100644 index 00000000000..5a7e69c142d --- /dev/null +++ b/engines/ags/engine/script/script_runtime.h @@ -0,0 +1,75 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// C-Script run-time interpreter (c) 2001 Chris Jones +// +// You must DISABLE OPTIMIZATIONS AND REGISTER VARIABLES in your compiler +// when compiling this, or strange results can happen. +// +// There is a problem with importing functions on 16-bit compilers: the +// script system assumes that all parameters are passed as 4 bytes, which +// ints are not on 16-bit systems. Be sure to define all parameters as longs, +// or join the 21st century and switch to DJGPP or Visual C++. +// +//============================================================================= + +#ifndef __CS_RUNTIME_H +#define __CS_RUNTIME_H + +#include "script/cc_script.h" // ccScript +#include "script/cc_instance.h" // ccInstance + +struct ICCStaticObject; +struct ICCDynamicObject; +struct StaticArray; + +using AGS::Common::String; +using AGS::Common::String; + +// ************ SCRIPT LOADING AND RUNNING FUNCTIONS ************ + +// give the script access to a variable or function in your program +extern bool ccAddExternalStaticFunction(const String &name, ScriptAPIFunction *pfn); +// temporary workaround for plugins +extern bool ccAddExternalPluginFunction(const String &name, void *pfn); +extern bool ccAddExternalStaticObject(const String &name, void *ptr, ICCStaticObject *manager); +extern bool ccAddExternalStaticArray(const String &name, void *ptr, StaticArray *array_mgr); +extern bool ccAddExternalDynamicObject(const String &name, void *ptr, ICCDynamicObject *manager); +extern bool ccAddExternalObjectFunction(const String &name, ScriptAPIObjectFunction *pfn); +extern bool ccAddExternalScriptSymbol(const String &name, const RuntimeScriptValue &prval, ccInstance *inst); +// remove the script access to a variable or function in your program +extern void ccRemoveExternalSymbol(const String &name); +// removes all external symbols, allowing you to start from scratch +extern void ccRemoveAllSymbols(); + +// get the address of an exported variable in the script +extern void *ccGetSymbolAddress(const String &name); + +// registering functions, compatible with old unsafe call style; +// this is to be used solely by plugins until plugin inteface is redone +extern bool ccAddExternalFunctionForPlugin(const String &name, void *pfn); +extern void *ccGetSymbolAddressForPlugin(const String &name); + +// DEBUG HOOK +typedef void (*new_line_hook_type) (ccInstance *, int); +extern void ccSetDebugHook(new_line_hook_type jibble); +#endif + +// Set the number of while loop iterations that aborts the script +extern void ccSetScriptAliveTimer (int); +// reset the current while loop counter +extern void ccNotifyScriptStillAlive (); +// for calling exported plugin functions old-style +extern int call_function(intptr_t addr, const RuntimeScriptValue *object, int numparm, const RuntimeScriptValue *parms); +extern void nullfree(void *data); // in script/script_runtime diff --git a/engines/ags/engine/script/systemimports.cpp b/engines/ags/engine/script/systemimports.cpp new file mode 100644 index 00000000000..41a53766d92 --- /dev/null +++ b/engines/ags/engine/script/systemimports.cpp @@ -0,0 +1,136 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "script/systemimports.h" + + +extern void quit(const char *); + +SystemImports simp; +SystemImports simp_for_plugin; + +int SystemImports::add(const String &name, const RuntimeScriptValue &value, ccInstance *anotherscr) +{ + int ixof; + + if ((ixof = get_index_of(name)) >= 0) { + // Only allow override if not a script-exported function + if (anotherscr == nullptr) { + imports[ixof].Value = value; + imports[ixof].InstancePtr = anotherscr; + } + return 0; + } + + ixof = imports.size(); + for (size_t i = 0; i < imports.size(); ++i) + { + if (imports[i].Name == nullptr) + { + ixof = i; + break; + } + } + + btree[name] = ixof; + if (ixof == imports.size()) + imports.push_back(ScriptImport()); + imports[ixof].Name = name; // TODO: rather make a string copy here for safety reasons + imports[ixof].Value = value; + imports[ixof].InstancePtr = anotherscr; + return 0; +} + +void SystemImports::remove(const String &name) { + int idx = get_index_of(name); + if (idx < 0) + return; + btree.erase(imports[idx].Name); + imports[idx].Name = nullptr; + imports[idx].Value.Invalidate(); + imports[idx].InstancePtr = nullptr; +} + +const ScriptImport *SystemImports::getByName(const String &name) +{ + int o = get_index_of(name); + if (o < 0) + return nullptr; + + return &imports[o]; +} + +const ScriptImport *SystemImports::getByIndex(int index) +{ + if ((size_t)index >= imports.size()) + return nullptr; + + return &imports[index]; +} + +int SystemImports::get_index_of(const String &name) +{ + IndexMap::const_iterator it = btree.find(name); + if (it != btree.end()) + return it->second; + + // CHECKME: what are "mangled names" and where do they come from? + String mangled_name = String::FromFormat("%s$", name.GetCStr()); + // if it's a function with a mangled name, allow it + it = btree.lower_bound(mangled_name); + if (it != btree.end() && it->first.CompareLeft(mangled_name) == 0) + return it->second; + + if (name.GetLength() > 3) + { + size_t c = name.FindCharReverse('^'); + if (c != -1 && (c == name.GetLength() - 2 || c == name.GetLength() - 3)) + { + // Function with number of prametrs on the end + // attempt to find it without the param count + return get_index_of(name.Left(c)); + } + } + return -1; +} + +void SystemImports::RemoveScriptExports(ccInstance *inst) +{ + if (!inst) + { + return; + } + + for (size_t i = 0; i < imports.size(); ++i) + { + if (imports[i].Name == nullptr) + continue; + + if (imports[i].InstancePtr == inst) + { + btree.erase(imports[i].Name); + imports[i].Name = nullptr; + imports[i].Value.Invalidate(); + imports[i].InstancePtr = nullptr; + } + } +} + +void SystemImports::clear() +{ + btree.clear(); + imports.clear(); +} diff --git a/engines/ags/engine/script/systemimports.h b/engines/ags/engine/script/systemimports.h new file mode 100644 index 00000000000..b6a0873864f --- /dev/null +++ b/engines/ags/engine/script/systemimports.h @@ -0,0 +1,63 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __CC_SYSTEMIMPORTS_H +#define __CC_SYSTEMIMPORTS_H + +#include +#include "script/cc_instance.h" // ccInstance + +struct ICCDynamicObject; +struct ICCStaticObject; + +using AGS::Common::String; + +struct ScriptImport +{ + ScriptImport() + { + InstancePtr = nullptr; + } + + String Name; // import's uid + RuntimeScriptValue Value; + ccInstance *InstancePtr; // script instance +}; + +struct SystemImports +{ +private: + // Note we can't use a hash-map here, because we sometimes need to search + // by partial keys. + typedef std::map IndexMap; + + std::vector imports; + IndexMap btree; + +public: + int add(const String &name, const RuntimeScriptValue &value, ccInstance *inst); + void remove(const String &name); + const ScriptImport *getByName(const String &name); + int get_index_of(const String &name); + const ScriptImport *getByIndex(int index); + void RemoveScriptExports(ccInstance *inst); + void clear(); +}; + +extern SystemImports simp; +// This is to register symbols exclusively for plugins, to allow them +// perform old style unsafe function calls +extern SystemImports simp_for_plugin; + +#endif // __CC_SYSTEMIMPORTS_H \ No newline at end of file diff --git a/engines/ags/engine/util/library.h b/engines/ags/engine/util/library.h new file mode 100644 index 00000000000..07c037a5508 --- /dev/null +++ b/engines/ags/engine/util/library.h @@ -0,0 +1,62 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__LIBRARY_H +#define __AGS_EE_UTIL__LIBRARY_H + +#include "core/platform.h" +#include "util/string.h" + +namespace AGS +{ +namespace Engine +{ + + +class BaseLibrary +{ +public: + BaseLibrary() = default; + + virtual ~BaseLibrary() = default; + + virtual AGS::Common::String GetFilenameForLib(AGS::Common::String libraryName) = 0; + + virtual bool Load(AGS::Common::String libraryName) = 0; + + virtual bool Unload() = 0; + + virtual void *GetFunctionAddress(AGS::Common::String functionName) = 0; +}; + + +} // namespace Engine +} // namespace AGS + + +#if AGS_PLATFORM_OS_WINDOWS +#include "library_windows.h" + +#elif AGS_PLATFORM_OS_LINUX \ + || AGS_PLATFORM_OS_MACOS \ + || AGS_PLATFORM_OS_ANDROID +#include "library_posix.h" + +#elif AGS_PLATFORM_OS_IOS +#include "library_dummy.h" + +#endif + + +#endif // __AGS_EE_UTIL__MUTEX_H diff --git a/engines/ags/engine/util/library_dummy.h b/engines/ags/engine/util/library_dummy.h new file mode 100644 index 00000000000..d91d06d3465 --- /dev/null +++ b/engines/ags/engine/util/library_dummy.h @@ -0,0 +1,67 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__LIBRARY_DUMMY_H +#define __AGS_EE_UTIL__LIBRARY_DUMMY_H + + +namespace AGS +{ +namespace Engine +{ + + +class DummyLibrary : BaseLibrary +{ +public: + DummyLibrary() + { + }; + + ~DummyLibrary() override + { + }; + + AGS::Common::String GetFilenameForLib(AGS::Common::String libraryName) override + { + return libraryName; + } + + bool Load(AGS::Common::String libraryName) override + { + return false; + } + + bool Unload() override + { + return true; + } + + void *GetFunctionAddress(AGS::Common::String functionName) override + { + return NULL; + } +}; + + +typedef DummyLibrary Library; + + + +} // namespace Engine +} // namespace AGS + + + +#endif // __AGS_EE_UTIL__LIBRARY_DUMMY_H diff --git a/engines/ags/engine/util/library_posix.h b/engines/ags/engine/util/library_posix.h new file mode 100644 index 00000000000..a11cdb2cecb --- /dev/null +++ b/engines/ags/engine/util/library_posix.h @@ -0,0 +1,153 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__LIBRARY_POSIX_H +#define __AGS_EE_UTIL__LIBRARY_POSIX_H + +#include +#include "core/platform.h" +#include "util/string.h" +#include "debug/out.h" + +// FIXME: Replace with a unified way to get the directory which contains the engine binary +#if AGS_PLATFORM_OS_ANDROID +extern char android_app_directory[256]; +#else +extern AGS::Common::String appDirectory; +#endif + + +namespace AGS +{ +namespace Engine +{ + + +class PosixLibrary : BaseLibrary +{ +public: + PosixLibrary() + : _library(nullptr) + { + }; + + ~PosixLibrary() override + { + Unload(); + }; + + AGS::Common::String BuildFilename(AGS::Common::String libraryName) + { + return String::FromFormat( +#if AGS_PLATFORM_OS_MACOS + "lib%s.dylib" +#else + "lib%s.so" +#endif + , libraryName.GetCStr()); + } + + AGS::Common::String BuildPath(const char *path, AGS::Common::String libraryName) + { + AGS::Common::String platformLibraryName = ""; + if (path) + { + platformLibraryName = path; + platformLibraryName.Append("/"); + } + platformLibraryName.Append(BuildFilename(libraryName)); + + AGS::Common::Debug::Printf("Built library path: %s", platformLibraryName.GetCStr()); + return platformLibraryName; + } + + AGS::Common::String GetFilenameForLib(AGS::Common::String libraryName) override + { + return BuildFilename(libraryName); + } + + bool Load(AGS::Common::String libraryName) override + { + Unload(); + + // Try rpath first + _library = dlopen(BuildPath(nullptr, libraryName).GetCStr(), RTLD_LAZY); + AGS::Common::Debug::Printf("dlopen returned: %s", dlerror()); + if (_library != nullptr) + { + return true; + } + + // Try current path + _library = dlopen(BuildPath(".", libraryName).GetCStr(), RTLD_LAZY); + + AGS::Common::Debug::Printf("dlopen returned: %s", dlerror()); + + if (_library == nullptr) + { + // Try the engine directory + +#if AGS_PLATFORM_OS_ANDROID + char buffer[200]; + sprintf(buffer, "%s%s", android_app_directory, "/lib"); + _library = dlopen(BuildPath(buffer, libraryName).GetCStr(), RTLD_LAZY); +#else + _library = dlopen(BuildPath(appDirectory, libraryName).GetCStr(), RTLD_LAZY); +#endif + + AGS::Common::Debug::Printf("dlopen returned: %s", dlerror()); + } + + return (_library != nullptr); + } + + bool Unload() override + { + if (_library) + { + return (dlclose(_library) == 0); + } + else + { + return true; + } + } + + void *GetFunctionAddress(AGS::Common::String functionName) override + { + if (_library) + { + return dlsym(_library, functionName.GetCStr()); + } + else + { + return nullptr; + } + } + +private: + void *_library; +}; + + +typedef PosixLibrary Library; + + + +} // namespace Engine +} // namespace AGS + + + +#endif // __AGS_EE_UTIL__LIBRARY_POSIX_H diff --git a/engines/ags/engine/util/library_psp.h b/engines/ags/engine/util/library_psp.h new file mode 100644 index 00000000000..54f52098b46 --- /dev/null +++ b/engines/ags/engine/util/library_psp.h @@ -0,0 +1,158 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + + +#ifndef __AGS_EE_UTIL__LIBRARY_PSP_H +#define __AGS_EE_UTIL__LIBRARY_PSP_H + +#include +#include "util/string.h" +#include "debug/out.h" + +namespace AGS +{ +namespace Engine +{ + +class PSPLibrary : BaseLibrary +{ +public: + PSPLibrary() + : _library(-1) + { + }; + + virtual ~PSPLibrary() + { + Unload(); + }; + + AGS::Common::String BuildPath(char *path, AGS::Common::String libraryName) + { + AGS::Common::String platformLibraryName = path; + platformLibraryName.Append(libraryName); + platformLibraryName.Append(".prx"); + + AGS::Common::Debug::Printf("Built library path: %s", platformLibraryName.GetCStr()); + + return platformLibraryName; + } + + bool Load(AGS::Common::String libraryName) + { + Unload(); + + // Try current path + _library = pspSdkLoadStartModule(BuildPath("./", libraryName).GetCStr(), PSP_MEMORY_PARTITION_USER); + + if (_library < 0) + { + // Try one directory higher, usually the AGS base directory + _library = pspSdkLoadStartModule(BuildPath("../", libraryName).GetCStr(), PSP_MEMORY_PARTITION_USER); + } + + // The PSP module and PSP library name are assumed to be the same as the file name + _moduleName = libraryName; + _moduleName.MakeLower(); + + AGS::Common::Debug::Printf("Result is %s %d", _moduleName.GetCStr(), _library); + + return (_library > -1); + } + + bool Unload() + { + if (_library > -1) + { + return (sceKernelUnloadModule(_library) > -1); + } + else + { + return true; + } + } + + void *GetFunctionAddress(AGS::Common::String functionName) + { + if (_library > -1) + { + // On the PSP functions are identified by an ID that is the first 4 byte of the SHA1 of the name. + int functionId; + +#if 1 + // Hardcoded values for plugin loading. + if (functionName == "AGS_PluginV2") + { + functionId = 0x960C49BD; + } + else if (functionName == "AGS_EngineStartup") + { + functionId = 0x0F13D9E8; + } + else if (functionName == "AGS_EngineShutdown") + { + functionId = 0x2F131C76; + } + else if (functionName == "AGS_EngineOnEvent") + { + functionId = 0xE3DFFC5A; + } + else if (functionName == "AGS_EngineDebugHook") + { + functionId = 0xC37D6879; + } + else if (functionName == "AGS_EngineInitGfx") + { + functionId = 0xA428D254; + } + else + { + AGS::Common::Debug::Printf("Function ID not found: %s", functionName.GetCStr()); + functionId = -1; + } +#else + // This is a possible SHA1 implementation. + SceKernelUtilsSha1Context ctx; + uint8_t digest[20]; + sceKernelUtilsSha1BlockInit(&ctx); + sceKernelUtilsSha1BlockUpdate(&ctx, (uint8_t *)functionName.GetCStr(), functionName.GetLength()); + sceKernelUtilsSha1BlockResult(&ctx, digest); + + functionId = strtol(digest, NULL, 8); +#endif + + return (void *)kernel_sctrlHENFindFunction((char *)_moduleName.GetCStr(), (char *)_moduleName.GetCStr(), functionId); + } + else + { + return NULL; + } + } + +private: + SceUID _library; + AGS::Common::String _moduleName; +}; + + +typedef PSPLibrary Library; + + + +} // namespace Engine +} // namespace AGS + + + +#endif // __AGS_EE_UTIL__LIBRARY_PSP_H diff --git a/engines/ags/engine/util/library_windows.h b/engines/ags/engine/util/library_windows.h new file mode 100644 index 00000000000..7d4e5a7454e --- /dev/null +++ b/engines/ags/engine/util/library_windows.h @@ -0,0 +1,114 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __AGS_EE_UTIL__LIBRARY_WINDOWS_H +#define __AGS_EE_UTIL__LIBRARY_WINDOWS_H + +#include "debug/out.h" +#include "platform/windows/winapi_exclusive.h" +#include "util/string.h" + +// Because this class may be exposed to generic code in sake of inlining, +// we should avoid including full of macros with common names. +#ifdef __cplusplus +extern "C" { +#endif + + WINBASEAPI BOOL WINAPI FreeLibrary(HMODULE hLibModule); + WINBASEAPI FARPROC WINAPI GetProcAddress(HMODULE hModule, LPCSTR lpProcName); + WINBASEAPI HMODULE WINAPI LoadLibraryA(LPCSTR lpLibFileName); + +#ifdef __cplusplus +} // extern "C" +#endif + + +namespace AGS +{ +namespace Engine +{ + + +class WindowsLibrary : BaseLibrary +{ +public: + WindowsLibrary() + : _library(NULL) + { + }; + + virtual ~WindowsLibrary() + { + Unload(); + }; + + AGS::Common::String BuildFilename(AGS::Common::String libraryName) + { + return String::FromFormat("%s.dll", libraryName.GetCStr()); + } + + AGS::Common::String BuildPath(AGS::Common::String libraryName) + { + AGS::Common::String platformLibraryName = BuildFilename(libraryName); + + AGS::Common::Debug::Printf("Built library path: %s", platformLibraryName.GetCStr()); + + return platformLibraryName; + } + + AGS::Common::String GetFilenameForLib(AGS::Common::String libraryName) override + { + return BuildFilename(libraryName); + } + + bool Load(AGS::Common::String libraryName) + { + Unload(); + + _library = LoadLibraryA(BuildPath(libraryName).GetCStr()); + + return (_library != NULL); + } + + bool Unload() + { + if (_library) + { + return (FreeLibrary(_library) != 0); + } + else + { + return true; + } + } + + void *GetFunctionAddress(AGS::Common::String functionName) + { + return GetProcAddress(_library, functionName.GetCStr()); + } + +private: + HANDLE _library; +}; + + +typedef WindowsLibrary Library; + + + +} // namespace Engine +} // namespace AGS + + + +#endif // __AGS_EE_UTIL__LIBRARY_WINDOWS_H diff --git a/engines/ags/engine/util/mutex.h b/engines/ags/engine/util/mutex.h new file mode 100644 index 00000000000..f7e8535f4b9 --- /dev/null +++ b/engines/ags/engine/util/mutex.h @@ -0,0 +1,50 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__MUTEX_H +#define __AGS_EE_UTIL__MUTEX_H + +namespace AGS +{ +namespace Engine +{ + + +class BaseMutex +{ +public: + BaseMutex() = default; + + virtual ~BaseMutex() = default; + + BaseMutex &operator=(const BaseMutex &) = delete; + BaseMutex(const BaseMutex &) = delete; + + virtual void Lock() = 0; + + virtual void Unlock() = 0; +}; + + +} // namespace Engine +} // namespace AGS + + +#if 0 + // insert platforms here +#else +#include "mutex_std.h" +#endif + +#endif // __AGS_EE_UTIL__MUTEX_H diff --git a/engines/ags/engine/util/mutex_base.h b/engines/ags/engine/util/mutex_base.h new file mode 100644 index 00000000000..51684f4b3ce --- /dev/null +++ b/engines/ags/engine/util/mutex_base.h @@ -0,0 +1,38 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_PLATFORM__MUTEX_BASE_H +#define __AGS_EE_PLATFORM__MUTEX_BASE_H + + +namespace AGS +{ +namespace Common +{ + + +class BaseMutex +{ +public: + BaseMutex() = 0; + virtual ~BaseMutex() = 0; + virtual void Lock() = 0; + virtual void Unlock() = 0; +}; + + +} // namespace Common +} // namespace AGS + +#endif // __AGS_EE_PLATFORM__MUTEX_BASE_H diff --git a/engines/ags/engine/util/mutex_lock.h b/engines/ags/engine/util/mutex_lock.h new file mode 100644 index 00000000000..6164abbf909 --- /dev/null +++ b/engines/ags/engine/util/mutex_lock.h @@ -0,0 +1,66 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__MUTEX_LOCK_H +#define __AGS_EE_UTIL__MUTEX_LOCK_H + +#include "util/mutex.h" + +namespace AGS +{ +namespace Engine +{ + + +class MutexLock +{ +private: + BaseMutex *_m; + MutexLock(MutexLock const &); // non-copyable + MutexLock& operator=(MutexLock const &); // not copy-assignable + +public: + void Release() + { + if (_m != nullptr) _m->Unlock(); + _m = nullptr; + } + + void Acquire(BaseMutex &mutex) + { + Release(); + _m = &mutex; + _m->Lock(); + } + + MutexLock() : _m(nullptr) + { + } + + explicit MutexLock(BaseMutex &mutex) : _m(nullptr) + { + Acquire(mutex); + } + + ~MutexLock() + { + Release(); + } +}; // class MutexLock + + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_UTIL__MUTEX_LOCK_H diff --git a/engines/ags/engine/util/mutex_psp.h b/engines/ags/engine/util/mutex_psp.h new file mode 100644 index 00000000000..72ab2838451 --- /dev/null +++ b/engines/ags/engine/util/mutex_psp.h @@ -0,0 +1,62 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__PSP_MUTEX_H +#define __AGS_EE_UTIL__PSP_MUTEX_H + +#include +#include +#include + +namespace AGS +{ +namespace Engine +{ + + +class PSPMutex : public BaseMutex +{ +public: + PSPMutex() + { + _mutex = sceKernelCreateSema("", 0, 1, 1, 0); + } + + ~PSPMutex() + { + sceKernelDeleteSema(_mutex); + } + + inline void Lock() + { + sceKernelWaitSema(_mutex, 1, 0); + } + + inline void Unlock() + { + sceKernelSignalSema(_mutex, 1); + } + +private: + SceUID _mutex; +}; + + +typedef PSPMutex Mutex; + + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_UTIL__PSP_MUTEX_H diff --git a/engines/ags/engine/util/mutex_pthread.h b/engines/ags/engine/util/mutex_pthread.h new file mode 100644 index 00000000000..cb3183fd13f --- /dev/null +++ b/engines/ags/engine/util/mutex_pthread.h @@ -0,0 +1,59 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__MUTEX_PTHREAD_H +#define __AGS_EE_UTIL__MUTEX_PTHREAD_H + +#include + +namespace AGS +{ +namespace Engine +{ + + +class PThreadMutex : public BaseMutex +{ +public: + inline PThreadMutex() + { + pthread_mutex_init(&_mutex, NULL); + } + + inline ~PThreadMutex() + { + pthread_mutex_destroy(&_mutex); + } + + inline void Lock() + { + pthread_mutex_lock(&_mutex); + } + + inline void Unlock() + { + pthread_mutex_unlock(&_mutex); + } + +private: + pthread_mutex_t _mutex; +}; + +typedef PThreadMutex Mutex; + + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_UTIL__MUTEX_PTHREAD_H diff --git a/engines/ags/engine/util/mutex_std.h b/engines/ags/engine/util/mutex_std.h new file mode 100644 index 00000000000..d86607956cf --- /dev/null +++ b/engines/ags/engine/util/mutex_std.h @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__MUTEX_STD_H +#define __AGS_EE_UTIL__MUTEX_STD_H + +#include + +namespace AGS +{ +namespace Engine +{ + +class StdMutex : public BaseMutex +{ + public: + inline StdMutex() : mutex_() {} + inline ~StdMutex() override = default; + + StdMutex &operator=(const StdMutex &) = delete; + StdMutex(const StdMutex &) = delete; + + inline void Lock() override { mutex_.lock(); } + inline void Unlock() override { mutex_.unlock(); } + + private: + std::recursive_mutex mutex_; +}; + +typedef StdMutex Mutex; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_UTIL__MUTEX_STD_H diff --git a/engines/ags/engine/util/mutex_wii.h b/engines/ags/engine/util/mutex_wii.h new file mode 100644 index 00000000000..6aa376b2f47 --- /dev/null +++ b/engines/ags/engine/util/mutex_wii.h @@ -0,0 +1,60 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__WII_MUTEX_H +#define __AGS_EE_UTIL__WII_MUTEX_H + +#include + +namespace AGS +{ +namespace Engine +{ + + +class WiiMutex : public BaseMutex +{ +public: + inline WiiMutex() + { + LWP_MutexInit(&_mutex, 0); + } + + inline ~WiiMutex() + { + LWP_MutexDestroy(_mutex); + } + + inline void Lock() + { + LWP_MutexLock(_mutex); + } + + inline void Unlock() + { + LWP_MutexUnlock(_mutex); + } + +private: + mutex_t _mutex; +}; + + +typedef WiiMutex Mutex; + + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_UTIL__WII_MUTEX_H diff --git a/engines/ags/engine/util/mutex_windows.h b/engines/ags/engine/util/mutex_windows.h new file mode 100644 index 00000000000..cf4c9157657 --- /dev/null +++ b/engines/ags/engine/util/mutex_windows.h @@ -0,0 +1,65 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __AGS_EE_UTIL__WINDOWS_MUTEX_H +#define __AGS_EE_UTIL__WINDOWS_MUTEX_H + +namespace AGS +{ +namespace Engine +{ + + +class WindowsMutex : public BaseMutex +{ +public: + WindowsMutex() + { + _mutex = CreateMutex(NULL, FALSE, NULL); + + _ASSERT(_mutex != NULL); + } + + ~WindowsMutex() + { + _ASSERT(_mutex != NULL); + + CloseHandle(_mutex); + } + + inline void Lock() + { + _ASSERT(_mutex != NULL); + + WaitForSingleObject(_mutex, INFINITE); + } + + inline void Unlock() + { + _ASSERT(_mutex != NULL); + + ReleaseMutex(_mutex); + } + +private: + HANDLE _mutex; +}; + + +typedef WindowsMutex Mutex; + + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_UTIL__WINDOWS_MUTEX_H diff --git a/engines/ags/engine/util/scaling.h b/engines/ags/engine/util/scaling.h new file mode 100644 index 00000000000..a8f78475469 --- /dev/null +++ b/engines/ags/engine/util/scaling.h @@ -0,0 +1,173 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Helper struct for scaling coordinates +// +// TODO: rewrite into proper Transform class. +// Maybe implement as real matrix and/or floats if that is better/runs faster. +// +//============================================================================= +#ifndef __AGS_EE_UTIL__SCALING_H +#define __AGS_EE_UTIL__SCALING_H + +#include "core/types.h" +#include "util/geometry.h" + +namespace AGS +{ +namespace Engine +{ + +class AxisScaling +{ +public: + AxisScaling() + : _scale(kUnit) + , _unscale(kUnit) + , _srcOffset(0) + , _dstOffset(0) + { + } + + bool IsIdentity() const + { + return _scale == kUnit && _srcOffset == 0 && _dstOffset == 0; + } + + bool IsTranslateOnly() const + { + return _scale == kUnit; + } + + void Init(const int32_t src_length, const int32_t dst_length, const int32_t src_offset = 0, const int32_t dst_offset = 0) + { + _scale = kUnit; + _unscale = kUnit; + _srcOffset = src_offset; + _dstOffset = dst_offset; + + if (src_length != 0) + { + int32_t scale = (dst_length << kShift) / src_length; + if (scale != 0) + { + _scale = scale; + _unscale = scale; + int32_t scaled_val = ScaleDistance(src_length); + if (scaled_val < dst_length) + _scale++; + } + } + } + + void SetSrcOffset(int32_t x) + { + _srcOffset = x; + } + + void SetDstOffset(int32_t x) + { + _dstOffset = x; + } + + inline int32_t GetSrcOffset() const { return _srcOffset; } + + inline int32_t ScalePt(int32_t x) const + { + return (((x - _srcOffset) * _scale) >> kShift) + _dstOffset; + } + inline int32_t ScaleDistance(int32_t x) const + { + return ((x * _scale) >> kShift); + } + inline int32_t UnScalePt(int32_t x) const + { + return ((x - _dstOffset) << kShift) / _unscale + _srcOffset; + } + inline int32_t UnScaleDistance(int32_t x) const + { + return (x << kShift) / _unscale; + } + +private: + int32_t _scale; + int32_t _unscale; + int32_t _srcOffset; + int32_t _dstOffset; +}; + +struct PlaneScaling +{ + AxisScaling X; + AxisScaling Y; + + bool IsIdentity() const + { + return X.IsIdentity() && Y.IsIdentity(); + } + + bool IsTranslateOnly() const + { + return X.IsTranslateOnly() && Y.IsTranslateOnly(); + } + + void Init(const Size &src_size, const Rect &dst_rect) + { + X.Init(src_size.Width, dst_rect.GetWidth(), 0, dst_rect.Left); + Y.Init(src_size.Height, dst_rect.GetHeight(), 0, dst_rect.Top); + } + + void Init(const Rect &src_rect, const Rect &dst_rect) + { + X.Init(src_rect.GetWidth(), dst_rect.GetWidth(), src_rect.Left, dst_rect.Left); + Y.Init(src_rect.GetHeight(), dst_rect.GetHeight(), src_rect.Top, dst_rect.Top); + } + + void SetSrcOffsets(int x, int y) + { + X.SetSrcOffset(x); + Y.SetSrcOffset(y); + } + + void SetDestOffsets(int x, int y) + { + X.SetDstOffset(x); + Y.SetDstOffset(y); + } + + inline Point Scale(const Point p) const + { + return Point(X.ScalePt(p.X), Y.ScalePt(p.Y)); + } + + inline Rect ScaleRange(const Rect r) const + { + return RectWH(X.ScalePt(r.Left), Y.ScalePt(r.Top), X.ScaleDistance(r.GetWidth()), Y.ScaleDistance(r.GetHeight())); + } + + inline Point UnScale(const Point p) const + { + return Point(X.UnScalePt(p.X), Y.UnScalePt(p.Y)); + } + + inline Rect UnScaleRange(const Rect r) const + { + return RectWH(X.UnScalePt(r.Left), Y.UnScalePt(r.Top), X.UnScaleDistance(r.GetWidth()), Y.UnScaleDistance(r.GetHeight())); + } +}; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_UTIL__SCALING_H diff --git a/engines/ags/engine/util/thread.h b/engines/ags/engine/util/thread.h new file mode 100644 index 00000000000..0f2bc1c1611 --- /dev/null +++ b/engines/ags/engine/util/thread.h @@ -0,0 +1,56 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__THREAD_H +#define __AGS_EE_UTIL__THREAD_H + +namespace AGS +{ +namespace Engine +{ + + +class BaseThread +{ +public: + typedef void(* AGSThreadEntry)(); + + BaseThread() = default; + virtual ~BaseThread() = default; + + BaseThread &operator=(const BaseThread &) = delete; + BaseThread(const BaseThread &) = delete; + + virtual bool Create(AGSThreadEntry entryPoint, bool looping) = 0; + virtual bool Start() = 0; + virtual bool Stop() = 0; + + inline bool CreateAndStart(AGSThreadEntry entryPoint, bool looping) + { + if (!Create(entryPoint, looping)) { return false; } + return Start(); + } +}; + +} // namespace Engine +} // namespace AGS + +#if 0 + // insert platforms here +#else +#include "thread_std.h" +#endif + + +#endif // __AGS_EE_UTIL__THREAD_H diff --git a/engines/ags/engine/util/thread_psp.h b/engines/ags/engine/util/thread_psp.h new file mode 100644 index 00000000000..eb4890425f0 --- /dev/null +++ b/engines/ags/engine/util/thread_psp.h @@ -0,0 +1,115 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_UTIL__PSP_THREAD_H +#define __AGS_EE_UTIL__PSP_THREAD_H + +#include +#include +#include + +namespace AGS +{ +namespace Engine +{ + + +class PSPThread : public BaseThread +{ +public: + PSPThread() + { + _thread = -1; + _entry = NULL; + _running = false; + _looping = false; + } + + ~PSPThread() + { + Stop(); + } + + inline bool Create(AGSThreadEntry entryPoint, bool looping) + { + _looping = looping; + _entry = entryPoint; + _thread = sceKernelCreateThread("ags", _thread_start, 0x20, 0x10000, THREAD_ATTR_USER, 0); + + return (_thread > -1); + } + + inline bool Start() + { + if ((_thread > -1) && (!_running)) + { + PSPThread* thisPointer = this; + SceUID result = sceKernelStartThread(_thread, sizeof(this), &thisPointer); + + _running = (result > -1); + return _running; + } + else + { + return false; + } + } + + bool Stop() + { + if ((_thread > -1) && (_running)) + { + if (_looping) + { + _looping = false; + sceKernelWaitThreadEnd(_thread, 0); + } + + _running = false; + return (sceKernelTerminateDeleteThread(_thread) > -1); + } + else + { + return false; + } + } + +private: + SceUID _thread; + bool _running; + bool _looping; + + AGSThreadEntry _entry; + + static int _thread_start(SceSize args, void *argp) + { + AGSThreadEntry entry = (*(PSPThread **)argp)->_entry; + bool *looping = &(*(PSPThread **)argp)->_looping; + + do + { + entry(); + } + while (*looping); + } +}; + + +typedef PSPThread Thread; + + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_UTIL__PSP_THREAD_H diff --git a/engines/ags/engine/util/thread_pthread.h b/engines/ags/engine/util/thread_pthread.h new file mode 100644 index 00000000000..c1b1a95b7fe --- /dev/null +++ b/engines/ags/engine/util/thread_pthread.h @@ -0,0 +1,114 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_PLATFORM__THREAD_PTHREAD_H +#define __AGS_EE_PLATFORM__THREAD_PTHREAD_H + +#include + +namespace AGS +{ +namespace Engine +{ + + +class PThreadThread : public BaseThread +{ +public: + PThreadThread() + { + _thread = 0; + _entry = NULL; + _running = false; + _looping = false; + } + + ~PThreadThread() + { + Stop(); + } + + inline bool Create(AGSThreadEntry entryPoint, bool looping) + { + _looping = looping; + _entry = entryPoint; + + // Thread creation is delayed till the thread is started + return true; + } + + inline bool Start() + { + if (!_running) + { + _running = (pthread_create(&_thread, NULL, _thread_start, this) == 0); + + return _running; + } + else + { + return false; + } + } + + bool Stop() + { + if (_running) + { + if (_looping) + { + _looping = false; + } + + pthread_join(_thread, NULL); + + _running = false; + return true; + } + else + { + return false; + } + } + +private: + pthread_t _thread; + bool _running; + bool _looping; + + AGSThreadEntry _entry; + + static void *_thread_start(void *arg) + { + AGSThreadEntry entry = ((PThreadThread *)arg)->_entry; + bool *looping = &((PThreadThread *)arg)->_looping; + + do + { + entry(); + } + while (*looping); + + return NULL; + } +}; + + +typedef PThreadThread Thread; + + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_PLATFORM__THREAD_PTHREAD_H diff --git a/engines/ags/engine/util/thread_std.h b/engines/ags/engine/util/thread_std.h new file mode 100644 index 00000000000..1868ff92263 --- /dev/null +++ b/engines/ags/engine/util/thread_std.h @@ -0,0 +1,97 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_PLATFORM__THREAD_STD_H +#define __AGS_EE_PLATFORM__THREAD_STD_H + +#include +#include + +namespace AGS +{ +namespace Engine +{ + +class StdThread : public BaseThread +{ +public: + StdThread() : thread_(), entry_(nullptr), looping_(false) + { + } + + ~StdThread() override + { + Stop(); + } + + StdThread &operator=(const StdThread &) = delete; + StdThread(const StdThread &) = delete; + + bool Create(AGSThreadEntry entryPoint, bool looping) override + { + if (!entryPoint) { return false; } + + entry_ = entryPoint; + looping_ = looping; + return true; + } + + bool Start() override + { + if (thread_.joinable()) { return true; } + if (!entry_) { return false; } + + try { + thread_ = std::thread(thread_start_, this); + } catch (std::system_error) { + return false; + } + return thread_.joinable(); + } + + bool Stop() override + { + if (!thread_.joinable()) { return true; } + + looping_ = false; // signal thread to stop + thread_.join(); + return true; + } + +private: + std::thread thread_; + AGSThreadEntry entry_; + bool looping_; + + static void thread_start_(StdThread *self) + { + auto entry = self->entry_; + for (;;) + { + entry(); + if (!self->looping_) + { + break; + } + std::this_thread::yield(); + } + } +}; + +typedef StdThread Thread; + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_PLATFORM__THREAD_STD_H diff --git a/engines/ags/engine/util/thread_wii.h b/engines/ags/engine/util/thread_wii.h new file mode 100644 index 00000000000..713a5e1ae47 --- /dev/null +++ b/engines/ags/engine/util/thread_wii.h @@ -0,0 +1,114 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_EE_PLATFORM__THREAD_WII_H +#define __AGS_EE_PLATFORM__THREAD_WII_H + +#include + +namespace AGS +{ +namespace Engine +{ + + +class WiiThread : public BaseThread +{ +public: + WiiThread() + { + _thread = (lwp_t)NULL; + _entry = NULL; + _running = false; + _looping = false; + } + + ~WiiThread() + { + Stop(); + } + + inline bool Create(AGSThreadEntry entryPoint, bool looping) + { + _looping = looping; + _entry = entryPoint; + + // Thread creation is delayed till the thread is started + return true; + } + + inline bool Start() + { + if (!_running) + { + _running = (LWP_CreateThread(&_thread, _thread_start, this, 0, 8 * 1024, 64) != 0); + + return _running; + } + else + { + return false; + } + } + + bool Stop() + { + if (_running) + { + if (_looping) + { + _looping = false; + } + + LWP_JoinThread(_thread, NULL); + + _running = false; + return true; + } + else + { + return false; + } + } + +private: + lwp_t _thread; + bool _running; + bool _looping; + + AGSThreadEntry _entry; + + static void *_thread_start(void *arg) + { + AGSThreadEntry entry = ((WiiThread *)arg)->_entry; + bool *looping = &((WiiThread *)arg)->_looping; + + do + { + entry(); + } + while (*looping); + + return NULL; + } +}; + + +typedef WiiThread Thread; + + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_PLATFORM__THREAD_WII_H diff --git a/engines/ags/engine/util/thread_windows.h b/engines/ags/engine/util/thread_windows.h new file mode 100644 index 00000000000..c3938f12b67 --- /dev/null +++ b/engines/ags/engine/util/thread_windows.h @@ -0,0 +1,114 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __AGS_EE_PLATFORM__THREAD_WINDOWS_H +#define __AGS_EE_PLATFORM__THREAD_WINDOWS_H + +namespace AGS +{ +namespace Engine +{ + + +class WindowsThread : public BaseThread +{ +public: + WindowsThread() + { + _thread = NULL; + _entry = NULL; + _running = false; + _looping = false; + } + + ~WindowsThread() + { + Stop(); + } + + inline bool Create(AGSThreadEntry entryPoint, bool looping) + { + _looping = looping; + _entry = entryPoint; + _thread = CreateThread(NULL, 0, _thread_start, this, CREATE_SUSPENDED, NULL); + + return (_thread != NULL); + } + + inline bool Start() + { + if ((_thread != NULL) && (!_running)) + { + DWORD result = ResumeThread(_thread); + + _running = (result != (DWORD) - 1); + return _running; + } + else + { + return false; + } + } + + bool Stop() + { + if ((_thread != NULL) && (_running)) + { + if (_looping) + { + _looping = false; + WaitForSingleObject(_thread, INFINITE); + } + + CloseHandle(_thread); + + _running = false; + _thread = NULL; + return true; + } + else + { + return false; + } + } + +private: + HANDLE _thread; + bool _running; + bool _looping; + + AGSThreadEntry _entry; + + static DWORD __stdcall _thread_start(LPVOID lpParam) + { + AGSThreadEntry entry = ((WindowsThread *)lpParam)->_entry; + bool *looping = &((WindowsThread *)lpParam)->_looping; + + do + { + entry(); + } + while (*looping); + + return 0; + } +}; + + +typedef WindowsThread Thread; + + +} // namespace Engine +} // namespace AGS + +#endif // __AGS_EE_PLATFORM__THREAD_WINDOWS_H diff --git a/engines/ags/shared/ac/audiocliptype.cpp b/engines/ags/shared/ac/audiocliptype.cpp new file mode 100644 index 00000000000..b2b6df92879 --- /dev/null +++ b/engines/ags/shared/ac/audiocliptype.cpp @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/audiocliptype.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +void AudioClipType::ReadFromFile(Stream *in) +{ + id = in->ReadInt32(); + reservedChannels = in->ReadInt32(); + volume_reduction_while_speech_playing = in->ReadInt32(); + crossfadeSpeed = in->ReadInt32(); + reservedForFuture = in->ReadInt32(); +} + +void AudioClipType::WriteToFile(Stream *out) +{ + out->WriteInt32(id); + out->WriteInt32(reservedChannels); + out->WriteInt32(volume_reduction_while_speech_playing); + out->WriteInt32(crossfadeSpeed); + out->WriteInt32(reservedForFuture); +} + +void AudioClipType::ReadFromSavegame(Common::Stream *in) +{ + volume_reduction_while_speech_playing = in->ReadInt32(); + crossfadeSpeed = in->ReadInt32(); +} + +void AudioClipType::WriteToSavegame(Common::Stream *out) const +{ + out->WriteInt32(volume_reduction_while_speech_playing); + out->WriteInt32(crossfadeSpeed); +} diff --git a/engines/ags/shared/ac/audiocliptype.h b/engines/ags/shared/ac/audiocliptype.h new file mode 100644 index 00000000000..66185a6fc01 --- /dev/null +++ b/engines/ags/shared/ac/audiocliptype.h @@ -0,0 +1,36 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_AUDIOCLIPTYPE_H +#define __AC_AUDIOCLIPTYPE_H + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +#define AUDIO_CLIP_TYPE_SOUND 1 +struct AudioClipType { + int id; + int reservedChannels; + int volume_reduction_while_speech_playing; + int crossfadeSpeed; + int reservedForFuture; + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); + void ReadFromSavegame(Common::Stream *in); + void WriteToSavegame(Common::Stream *out) const; +}; + +#endif // __AC_AUDIOCLIPTYPE_H \ No newline at end of file diff --git a/engines/ags/shared/ac/characterinfo.cpp b/engines/ags/shared/ac/characterinfo.cpp new file mode 100644 index 00000000000..dbcbcf39347 --- /dev/null +++ b/engines/ags/shared/ac/characterinfo.cpp @@ -0,0 +1,155 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/characterinfo.h" +#include "util/stream.h" + +using AGS::Common::Stream; + + +void CharacterInfo::ReadFromFile(Stream *in) +{ + defview = in->ReadInt32(); + talkview = in->ReadInt32(); + view = in->ReadInt32(); + room = in->ReadInt32(); + prevroom = in->ReadInt32(); + x = in->ReadInt32(); + y = in->ReadInt32(); + wait = in->ReadInt32(); + flags = in->ReadInt32(); + following = in->ReadInt16(); + followinfo = in->ReadInt16(); + idleview = in->ReadInt32(); + idletime = in->ReadInt16(); + idleleft = in->ReadInt16(); + transparency = in->ReadInt16(); + baseline = in->ReadInt16(); + activeinv = in->ReadInt32(); + talkcolor = in->ReadInt32(); + thinkview = in->ReadInt32(); + blinkview = in->ReadInt16(); + blinkinterval = in->ReadInt16(); + blinktimer = in->ReadInt16(); + blinkframe = in->ReadInt16(); + walkspeed_y = in->ReadInt16(); + pic_yoffs = in->ReadInt16(); + z = in->ReadInt32(); + walkwait = in->ReadInt32(); + speech_anim_speed = in->ReadInt16(); + reserved1 = in->ReadInt16(); + blocking_width = in->ReadInt16(); + blocking_height = in->ReadInt16();; + index_id = in->ReadInt32(); + pic_xoffs = in->ReadInt16(); + walkwaitcounter = in->ReadInt16(); + loop = in->ReadInt16(); + frame = in->ReadInt16(); + walking = in->ReadInt16(); + animating = in->ReadInt16(); + walkspeed = in->ReadInt16(); + animspeed = in->ReadInt16(); + in->ReadArrayOfInt16(inv, MAX_INV); + actx = in->ReadInt16(); + acty = in->ReadInt16(); + in->Read(name, 40); + in->Read(scrname, MAX_SCRIPT_NAME_LEN); + on = in->ReadInt8(); +} + +void CharacterInfo::WriteToFile(Stream *out) +{ + out->WriteInt32(defview); + out->WriteInt32(talkview); + out->WriteInt32(view); + out->WriteInt32(room); + out->WriteInt32(prevroom); + out->WriteInt32(x); + out->WriteInt32(y); + out->WriteInt32(wait); + out->WriteInt32(flags); + out->WriteInt16(following); + out->WriteInt16(followinfo); + out->WriteInt32(idleview); + out->WriteInt16(idletime); + out->WriteInt16(idleleft); + out->WriteInt16(transparency); + out->WriteInt16(baseline); + out->WriteInt32(activeinv); + out->WriteInt32(talkcolor); + out->WriteInt32(thinkview); + out->WriteInt16(blinkview); + out->WriteInt16(blinkinterval); + out->WriteInt16(blinktimer); + out->WriteInt16(blinkframe); + out->WriteInt16(walkspeed_y); + out->WriteInt16(pic_yoffs); + out->WriteInt32(z); + out->WriteInt32(walkwait); + out->WriteInt16(speech_anim_speed); + out->WriteInt16(reserved1); + out->WriteInt16(blocking_width); + out->WriteInt16(blocking_height);; + out->WriteInt32(index_id); + out->WriteInt16(pic_xoffs); + out->WriteInt16(walkwaitcounter); + out->WriteInt16(loop); + out->WriteInt16(frame); + out->WriteInt16(walking); + out->WriteInt16(animating); + out->WriteInt16(walkspeed); + out->WriteInt16(animspeed); + out->WriteArrayOfInt16(inv, MAX_INV); + out->WriteInt16(actx); + out->WriteInt16(acty); + out->Write(name, 40); + out->Write(scrname, MAX_SCRIPT_NAME_LEN); + out->WriteInt8(on); +} + +void ConvertOldCharacterToNew (OldCharacterInfo *oci, CharacterInfo *ci) { + COPY_CHAR_VAR (defview); + COPY_CHAR_VAR (talkview); + COPY_CHAR_VAR (view); + COPY_CHAR_VAR (room); + COPY_CHAR_VAR (prevroom); + COPY_CHAR_VAR (x); + COPY_CHAR_VAR (y); + COPY_CHAR_VAR (wait); + COPY_CHAR_VAR (flags); + COPY_CHAR_VAR (following); + COPY_CHAR_VAR (followinfo); + COPY_CHAR_VAR (idleview); + COPY_CHAR_VAR (idletime); + COPY_CHAR_VAR (idleleft); + COPY_CHAR_VAR (transparency); + COPY_CHAR_VAR (baseline); + COPY_CHAR_VAR (activeinv); + COPY_CHAR_VAR (loop); + COPY_CHAR_VAR (frame); + COPY_CHAR_VAR (walking); + COPY_CHAR_VAR (animating); + COPY_CHAR_VAR (walkspeed); + COPY_CHAR_VAR (animspeed); + COPY_CHAR_VAR (actx); + COPY_CHAR_VAR (acty); + COPY_CHAR_VAR (on); + strcpy (ci->name, oci->name); + strcpy (ci->scrname, oci->scrname); + memcpy (&ci->inv[0], &oci->inv[0], sizeof(short) * 100); + // move the talking colour into the struct and remove from flags + ci->talkcolor = (oci->flags & OCHF_SPEECHCOL) >> OCHF_SPEECHCOLSHIFT; + ci->flags = ci->flags & (~OCHF_SPEECHCOL); +} diff --git a/engines/ags/shared/ac/characterinfo.h b/engines/ags/shared/ac/characterinfo.h new file mode 100644 index 00000000000..f3140d15639 --- /dev/null +++ b/engines/ags/shared/ac/characterinfo.h @@ -0,0 +1,144 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_CHARACTERINFO_H +#define __AC_CHARACTERINFO_H + +#include "ac/common_defines.h" // constants + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +#define MAX_INV 301 +#define CHF_MANUALSCALING 1 +#define CHF_FIXVIEW 2 // between SetCharView and ReleaseCharView +#define CHF_NOINTERACT 4 +#define CHF_NODIAGONAL 8 +#define CHF_ALWAYSIDLE 0x10 +#define CHF_NOLIGHTING 0x20 +#define CHF_NOTURNING 0x40 +#define CHF_NOWALKBEHINDS 0x80 +#define CHF_FLIPSPRITE 0x100 // ?? Is this used?? +#define CHF_NOBLOCKING 0x200 +#define CHF_SCALEMOVESPEED 0x400 +#define CHF_NOBLINKANDTHINK 0x800 +#define CHF_SCALEVOLUME 0x1000 +#define CHF_HASTINT 0x2000 // engine only +#define CHF_BEHINDSHEPHERD 0x4000 // engine only +#define CHF_AWAITINGMOVE 0x8000 // engine only +#define CHF_MOVENOTWALK 0x10000 // engine only - do not do walk anim +#define CHF_ANTIGLIDE 0x20000 +#define CHF_HASLIGHT 0x40000 +// Speechcol is no longer part of the flags as of v2.5 +#define OCHF_SPEECHCOL 0xff000000 +#define OCHF_SPEECHCOLSHIFT 24 +#define UNIFORM_WALK_SPEED 0 +#define FOLLOW_ALWAYSONTOP 0x7ffe + +struct CharacterExtras; // forward declaration +// remember - if change this struct, also change AGSDEFNS.SH and +// plugin header file struct +struct CharacterInfo { + int defview; + int talkview; + int view; + int room, prevroom; + int x, y, wait; + int flags; + short following; + short followinfo; + int idleview; // the loop will be randomly picked + short idletime, idleleft; // num seconds idle before playing anim + short transparency; // if character is transparent + short baseline; + int activeinv; + int talkcolor; + int thinkview; + short blinkview, blinkinterval; // design time + short blinktimer, blinkframe; // run time + short walkspeed_y; + short pic_yoffs; // this is fixed in screen coordinates + int z; // z-location, for flying etc + int walkwait; + short speech_anim_speed, reserved1; // only 1 reserved left!! + short blocking_width, blocking_height; + int index_id; // used for object functions to know the id + short pic_xoffs; // this is fixed in screen coordinates + short walkwaitcounter; + short loop, frame; + short walking, animating; + short walkspeed, animspeed; + short inv[MAX_INV]; + short actx, acty; + char name[40]; + char scrname[MAX_SCRIPT_NAME_LEN]; + char on; + + int get_effective_y(); // return Y - Z + int get_baseline(); // return baseline, or Y if not set + int get_blocking_top(); // return Y - BlockingHeight/2 + int get_blocking_bottom(); // return Y + BlockingHeight/2 + + inline bool has_explicit_light() const { return (flags & CHF_HASLIGHT) != 0; } + inline bool has_explicit_tint() const { return (flags & CHF_HASTINT) != 0; } + + // [IKM] 2012-06-28: I still have to pass char_index to some of those functions + // either because they use it to set some variables with it, + // or because they pass it further to other functions, that are called from various places + // and it would be too much to change them all simultaneously + // + // [IKM] 2016-08-26: these methods should NOT be in CharacterInfo class, + // bit in distinct runtime character class! + void UpdateMoveAndAnim(int &char_index, CharacterExtras *chex, int &numSheep, int *followingAsSheep); + void UpdateFollowingExactlyCharacter(); + + int update_character_walking(CharacterExtras *chex); + void update_character_moving(int &char_index, CharacterExtras *chex, int &doing_nothing); + int update_character_animating(int &char_index, int &doing_nothing); + void update_character_idle(CharacterExtras *chex, int &doing_nothing); + void update_character_follower(int &char_index, int &numSheep, int *followingAsSheep, int &doing_nothing); + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); +}; + + +struct OldCharacterInfo { + int defview; + int talkview; + int view; + int room, prevroom; + int x, y, wait; + int flags; + short following; + short followinfo; + int idleview; // the loop will be randomly picked + short idletime, idleleft; // num seconds idle before playing anim + short transparency; // if character is transparent + short baseline; + int activeinv; // this is an INT to support SeeR (no signed shorts) + short loop, frame; + short walking, animating; + short walkspeed, animspeed; + short inv[100]; + short actx, acty; + char name[30]; + char scrname[16]; + char on; +}; + +#define COPY_CHAR_VAR(name) ci->name = oci->name +void ConvertOldCharacterToNew (OldCharacterInfo *oci, CharacterInfo *ci); + +#endif // __AC_CHARACTERINFO_H \ No newline at end of file diff --git a/engines/ags/shared/ac/common.cpp b/engines/ags/shared/ac/common.cpp new file mode 100644 index 00000000000..5db922c15a0 --- /dev/null +++ b/engines/ags/shared/ac/common.cpp @@ -0,0 +1,29 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" +#include "util/string.h" + +using namespace AGS::Common; + +const char *game_file_sig = "Adventure Creator Game File v2"; + +void quitprintf(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + String text = String::FromFormatV(fmt, ap); + va_end(ap); + quit(text); +} diff --git a/engines/ags/shared/ac/common.h b/engines/ags/shared/ac/common.h new file mode 100644 index 00000000000..469605c3205 --- /dev/null +++ b/engines/ags/shared/ac/common.h @@ -0,0 +1,27 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_COMMON_H +#define __AC_COMMON_H + +// These are the project-dependent functions, they are defined both in Engine.App and AGS.Native. +void quit(const char *); +void quitprintf(const char *fmt, ...); +void update_polled_stuff_if_runtime(); +void set_our_eip(int eip); +int get_our_eip(); + +extern const char *game_file_sig; + +#endif // __AC_COMMON_H diff --git a/engines/ags/shared/ac/common_defines.h b/engines/ags/shared/ac/common_defines.h new file mode 100644 index 00000000000..dd70970dbb6 --- /dev/null +++ b/engines/ags/shared/ac/common_defines.h @@ -0,0 +1,123 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_DEFINES_H +#define __AC_DEFINES_H + +#include "core/platform.h" + +#define EXIT_NORMAL 0 +#define EXIT_CRASH 92 +#define EXIT_ERROR 93 + + +#if defined (OBSOLETE) +#define NUM_MISC 20 +#define NUMOTCON 7 // number of conditions before standing on +#define NUM_CONDIT (120 + NUMOTCON) +#endif + + + + +#define MAX_SCRIPT_NAME_LEN 20 + +//const int MISC_COND = MAX_WALK_BEHINDS * 4 + NUMOTCON + MAX_ROOM_OBJECTS * 4; + +// NUMCONDIT : whataction[0]: Char walks off left +// [1]: Char walks off right +// [2]: Char walks off bottom +// [3]: Char walks off top +// [4]: First enters screen +// [5]: Every time enters screen +// [6]: execute every loop +// [5]...[19]: Char stands on lookat type +// [20]...[35]: Look at type +// [36]...[49]: Action on type +// [50]...[65]: Use inv on type +// [66]...[75]: Look at object +// [76]...[85]: Action on object +// [86]...[95]: Speak to object +// [96]...[105]: Use inv on object +// [106]...[124]: Misc conditions 1-20 + +// game ver whataction[]= +// v1.00 0 : Go to screen +// 1 : Don't do anything +// 2 : Can't walk +// 3 : Man dies +// 4 : Run animation +// 5 : Display message +// 6 : Remove an object (set object.on=0) +// 7 : Remove object & add Val2 to inventory +// 8 : Add Val1 to inventory (Val2=num times) +// 9 : Run a script +// v1.00 SR-1 10 : Run graphical script +// v1.1 11 : Play sound effect SOUND%d.WAV +// v1.12 12 : Play FLI/FLC animation FLIC%d.FLC or FLIC%d.FLI +// 13 : Turn object on +// v2.00 14 : Run conversation +#define NUMRESPONSE 14 +#define NUMCOMMANDS 15 +#define GO_TO_SCREEN 0 +#define NO_ACTION 1 +#define NO_WALK 2 +#define MAN_DIES 3 +#define RUN_ANIMATE 4 +#define SHOW_MESSAGE 5 +#define OBJECT_OFF 6 +#define OBJECT_INV 7 +#define ADD_INV 8 +#define RUNSCRIPT 9 +#define GRAPHSCRIPT 10 +#define PLAY_SOUND 11 +#define PLAY_FLI 12 +#define OBJECT_ON 13 +#define RUN_DIALOG 14 + + +#define MAX_ROOMS 300 + +#define MAX_FLAGS 15 +#define LEGACY_MAXOBJNAMELEN 30 + +#define LEGACY_MAX_SPRITES_V25 6000 +#define LEGACY_MAX_SPRITES 30000 +#define MAX_CURSOR 20 + +#ifndef int32 +#define int32 int +#endif + +#if AGS_PLATFORM_OS_WINDOWS +#define AGS_INLINE inline +#else +// the linux compiler won't allow extern inline +#define AGS_INLINE +#endif + +// The game to screen coordinate conversion multiplier in hi-res type games +#define HIRES_COORD_MULTIPLIER 2 + +// object flags (currently only a char) +#define OBJF_NOINTERACT 1 // not clickable +#define OBJF_NOWALKBEHINDS 2 // ignore walk-behinds +#define OBJF_HASTINT 4 // the tint_* members are valid +#define OBJF_USEREGIONTINTS 8 // obey region tints/light areas +#define OBJF_USEROOMSCALING 0x10 // obey room scaling areas +#define OBJF_SOLID 0x20 // blocks characters from moving +#define OBJF_LEGACY_LOCKED 0x40 // object position is locked in the editor (OBSOLETE since 3.5.0) +#define OBJF_HASLIGHT 0x80 // the tint_light is valid and treated as brightness + +#endif // __AC_DEFINES_H diff --git a/engines/ags/shared/ac/dialogtopic.cpp b/engines/ags/shared/ac/dialogtopic.cpp new file mode 100644 index 00000000000..86d5dcd1836 --- /dev/null +++ b/engines/ags/shared/ac/dialogtopic.cpp @@ -0,0 +1,42 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dialogtopic.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +void DialogTopic::ReadFromFile(Stream *in) +{ + in->ReadArray(optionnames, 150*sizeof(char), MAXTOPICOPTIONS); + in->ReadArrayOfInt32(optionflags, MAXTOPICOPTIONS); + // optionscripts pointer is not used anywhere in the engine + optionscripts = nullptr; + in->ReadInt32(); // optionscripts 32-bit pointer + in->ReadArrayOfInt16(entrypoints, MAXTOPICOPTIONS); + startupentrypoint = in->ReadInt16(); + codesize = in->ReadInt16(); + numoptions = in->ReadInt32(); + topicFlags = in->ReadInt32(); +} + +void DialogTopic::ReadFromSavegame(Common::Stream *in) +{ + in->ReadArrayOfInt32(optionflags, MAXTOPICOPTIONS); +} + +void DialogTopic::WriteToSavegame(Common::Stream *out) const +{ + out->WriteArrayOfInt32(optionflags, MAXTOPICOPTIONS); +} diff --git a/engines/ags/shared/ac/dialogtopic.h b/engines/ags/shared/ac/dialogtopic.h new file mode 100644 index 00000000000..df259811bf4 --- /dev/null +++ b/engines/ags/shared/ac/dialogtopic.h @@ -0,0 +1,68 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_AC__DIALOGTOPIC_H +#define __AGS_CN_AC__DIALOGTOPIC_H + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +// [IKM] This is *conversation* dialog, not *gui* dialog, mind you! + +#define MAXTOPICOPTIONS 30 +#define DFLG_ON 1 // currently enabled +#define DFLG_OFFPERM 2 // off forever (can't be trurned on) +#define DFLG_NOREPEAT 4 // character doesn't repeat it when clicked +#define DFLG_HASBEENCHOSEN 8 // dialog option is 'read' +#define DTFLG_SHOWPARSER 1 // show parser in this topic +#define DCMD_SAY 1 +#define DCMD_OPTOFF 2 +#define DCMD_OPTON 3 +#define DCMD_RETURN 4 +#define DCMD_STOPDIALOG 5 +#define DCMD_OPTOFFFOREVER 6 +#define DCMD_RUNTEXTSCRIPT 7 +#define DCMD_GOTODIALOG 8 +#define DCMD_PLAYSOUND 9 +#define DCMD_ADDINV 10 +#define DCMD_SETSPCHVIEW 11 +#define DCMD_NEWROOM 12 +#define DCMD_SETGLOBALINT 13 +#define DCMD_GIVESCORE 14 +#define DCMD_GOTOPREVIOUS 15 +#define DCMD_LOSEINV 16 +#define DCMD_ENDSCRIPT 0xff +#define DCHAR_NARRATOR 999 +#define DCHAR_PLAYER 998 +struct DialogTopic { + char optionnames[MAXTOPICOPTIONS][150]; + int optionflags[MAXTOPICOPTIONS]; + unsigned char *optionscripts; + short entrypoints[MAXTOPICOPTIONS]; + short startupentrypoint; + short codesize; + int numoptions; + int topicFlags; + + void ReadFromFile(Common::Stream *in); + + void ReadFromSavegame(Common::Stream *in); + void WriteToSavegame(Common::Stream *out) const; +}; + + +#endif // __AGS_CN_AC__DIALOGTOPIC_H \ No newline at end of file diff --git a/engines/ags/shared/ac/dynobj/scriptaudioclip.cpp b/engines/ags/shared/ac/dynobj/scriptaudioclip.cpp new file mode 100644 index 00000000000..0f1115d29e3 --- /dev/null +++ b/engines/ags/shared/ac/dynobj/scriptaudioclip.cpp @@ -0,0 +1,32 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/dynobj/scriptaudioclip.h" +#include "util/stream.h" + +using namespace AGS::Common; + +void ScriptAudioClip::ReadFromFile(Stream *in) +{ + id = in->ReadInt32(); + scriptName.ReadCount(in, SCRIPTAUDIOCLIP_SCRIPTNAMELENGTH); + fileName.ReadCount(in, SCRIPTAUDIOCLIP_FILENAMELENGTH); + bundlingType = in->ReadInt8(); + type = in->ReadInt8(); + fileType = in->ReadInt8(); + defaultRepeat = in->ReadInt8(); + defaultPriority = in->ReadInt16(); + defaultVolume = in->ReadInt16(); + in->ReadInt32(); // reserved +} diff --git a/engines/ags/shared/ac/dynobj/scriptaudioclip.h b/engines/ags/shared/ac/dynobj/scriptaudioclip.h new file mode 100644 index 00000000000..1349054ab70 --- /dev/null +++ b/engines/ags/shared/ac/dynobj/scriptaudioclip.h @@ -0,0 +1,54 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_DYNOBJ__SCRIPTAUDIOCLIP_H +#define __AGS_CN_DYNOBJ__SCRIPTAUDIOCLIP_H + +#include "util/string.h" + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +enum AudioFileType { + eAudioFileOGG = 1, + eAudioFileMP3 = 2, + eAudioFileWAV = 3, + eAudioFileVOC = 4, + eAudioFileMIDI = 5, + eAudioFileMOD = 6 +}; + +#define AUCL_BUNDLE_EXE 1 +#define AUCL_BUNDLE_VOX 2 + +#define SCRIPTAUDIOCLIP_SCRIPTNAMELENGTH 30 +#define SCRIPTAUDIOCLIP_FILENAMELENGTH 15 +struct ScriptAudioClip { + int id = 0; + Common::String scriptName; + Common::String fileName; + char bundlingType = AUCL_BUNDLE_EXE; + char type = 0; + char fileType = eAudioFileOGG; + char defaultRepeat = 0; + short defaultPriority = 50; + short defaultVolume = 100; + + void ReadFromFile(Common::Stream *in); +}; + +#endif // __AGS_CN_DYNOBJ__SCRIPTAUDIOCLIP_H diff --git a/engines/ags/shared/ac/game_version.h b/engines/ags/shared/ac/game_version.h new file mode 100644 index 00000000000..1c7ddeab52b --- /dev/null +++ b/engines/ags/shared/ac/game_version.h @@ -0,0 +1,146 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Game version constants and information +// +//============================================================================= + +#ifndef __AGS_CN_AC__GAMEVERSION_H +#define __AGS_CN_AC__GAMEVERSION_H + +/* + +Game data versions and changes: +------------------------------- + +12 : 2.3 + 2.4 + +Versions above are incompatible at the moment. + +18 : 2.5.0 +19 : 2.5.1 + 2.52 +20 : 2.5.3 + +Lip sync data added. +21 : 2.5.4 +22 : 2.5.5 + +Variable number of sprites. +24 : 2.5.6 +25 : 2.6.0 + +Encrypted global messages and dialogs. +26 : 2.6.1 + +Wait() must be called with parameter > 0 +GetRegionAt() clips the input values to the screen size +Color 0 now means transparent instead of black for text windows +SetPlayerCharacter() does nothing if the new character is already the player character. +27 : 2.6.2 + +Script modules. Fixes bug in the inventory display. +Clickable GUI is selected with regard for the drawing order. +Pointer to the "player" variable is now accessed via a dynamic object. +31 : 2.7.0 +32 : 2.7.2 + +35 : 3.0.0 + +Room names are serialized when game is compiled in "debug" mode. +36 : 3.0.1 + +Interactions are now scripts. The number for "not set" changed from 0 to -1 for +a lot of variables (views, sounds). +37 : 3.1.0 + +Dialogs are now scripts. New character animation speed. +39 : 3.1.1 + +Individual character speech animation speed. +40 : 3.1.2 + +Audio clips +41 : 3.2.0 +42 : 3.2.1 + +43 : 3.3.0 +Added few more game options. + +44 : 3.3.1 +Added custom dialog option highlight colour. + +45 : 3.4.0.1 +Support for custom game resolution. + +46 : 3.4.0.2-.3 +Audio playback speed. +Custom dialog option rendering extension. + +47 : 3.4.0.4 +Custom properties changed at runtime. +Ambient lighting + +48 : 3.4.1 +OPT_RENDERATSCREENRES, extended engine caps check, font vertical offset. + +49 : 3.4.1.2 +Font custom line spacing. + +50 : 3.5.0.8 +Sprites have "real" resolution. Expanded FontInfo data format. +Option to allow legacy relative asset resolutions. + +51 : +Fonts have adjustable outline + +*/ + +enum GameDataVersion +{ + kGameVersion_Undefined = 0, + kGameVersion_230 = 12, + kGameVersion_240 = 12, + kGameVersion_250 = 18, + kGameVersion_251 = 19, // same as 2.52 + kGameVersion_253 = 20, + kGameVersion_254 = 21, + kGameVersion_255 = 22, + kGameVersion_256 = 24, + kGameVersion_260 = 25, + kGameVersion_261 = 26, + kGameVersion_262 = 27, + kGameVersion_270 = 31, + kGameVersion_272 = 32, + kGameVersion_300 = 35, + kGameVersion_301 = 36, + kGameVersion_310 = 37, + kGameVersion_311 = 39, + kGameVersion_312 = 40, + kGameVersion_320 = 41, + kGameVersion_321 = 42, + kGameVersion_330 = 43, + kGameVersion_331 = 44, + kGameVersion_340_1 = 45, + kGameVersion_340_2 = 46, + kGameVersion_340_4 = 47, + kGameVersion_341 = 48, + kGameVersion_341_2 = 49, + kGameVersion_350 = 50, + kGameVersion_351 = 51, + kGameVersion_Current = kGameVersion_351 +}; + +extern GameDataVersion loaded_game_file_version; + +#endif // __AGS_CN_AC__GAMEVERSION_H diff --git a/engines/ags/shared/ac/gamesetupstruct.cpp b/engines/ags/shared/ac/gamesetupstruct.cpp new file mode 100644 index 00000000000..1c8a2cbae3f --- /dev/null +++ b/engines/ags/shared/ac/gamesetupstruct.cpp @@ -0,0 +1,498 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/audiocliptype.h" +#include "ac/gamesetupstruct.h" +#include "ac/oldgamesetupstruct.h" +#include "ac/wordsdictionary.h" +#include "ac/dynobj/scriptaudioclip.h" +#include "game/interactions.h" +#include "util/alignedstream.h" + +using namespace AGS::Common; + +GameSetupStruct::GameSetupStruct() + : filever(0) + , roomCount(0) + , roomNumbers(nullptr) + , roomNames(nullptr) + , scoreClipID(0) +{ + memset(invinfo, 0, sizeof(invinfo)); + memset(mcurs, 0, sizeof(mcurs)); + memset(lipSyncFrameLetters, 0, sizeof(lipSyncFrameLetters)); + memset(guid, 0, sizeof(guid)); + memset(saveGameFileExtension, 0, sizeof(saveGameFileExtension)); + memset(saveGameFolderName, 0, sizeof(saveGameFolderName)); +} + +GameSetupStruct::~GameSetupStruct() +{ + Free(); +} + +void GameSetupStruct::Free() +{ + GameSetupStructBase::Free(); + + intrChar.clear(); + charScripts.clear(); + numcharacters = 0; + + // TODO: find out if it really needs to begin with 1 here? + for (size_t i = 1; i < (size_t)MAX_INV; i++) + intrInv[i].reset(); + invScripts.clear(); + numinvitems = 0; + + for (int i = 0; i < roomCount; i++) + delete roomNames[i]; + delete[] roomNames; + delete[] roomNumbers; + roomCount = 0; + + audioClips.clear(); + audioClipTypes.clear(); + + charProps.clear(); + viewNames.clear(); +} + +// Assigns font info parameters using legacy flags value read from the game data +void SetFontInfoFromLegacyFlags(FontInfo &finfo, const uint8_t data) +{ + finfo.Flags = (data >> 6) & 0xFF; + finfo.SizePt = data & FFLG_LEGACY_SIZEMASK; +} + +void AdjustFontInfoUsingFlags(FontInfo &finfo, const uint32_t flags) +{ + finfo.Flags = flags; + if ((flags & FFLG_SIZEMULTIPLIER) != 0) + { + finfo.SizeMultiplier = finfo.SizePt; + finfo.SizePt = 0; + } +} + +ScriptAudioClip* GetAudioClipForOldStyleNumber(GameSetupStruct &game, bool is_music, int num) +{ + String clip_name; + if (is_music) + clip_name.Format("aMusic%d", num); + else + clip_name.Format("aSound%d", num); + + for (size_t i = 0; i < game.audioClips.size(); ++i) + { + if (clip_name.Compare(game.audioClips[i].scriptName) == 0) + return &game.audioClips[i]; + } + return nullptr; +} + +//----------------------------------------------------------------------------- +// Reading Part 1 + +void GameSetupStruct::read_savegame_info(Common::Stream *in, GameDataVersion data_ver) +{ + if (data_ver > kGameVersion_272) // only 3.x + { + in->Read(&guid[0], MAX_GUID_LENGTH); + in->Read(&saveGameFileExtension[0], MAX_SG_EXT_LENGTH); + in->Read(&saveGameFolderName[0], MAX_SG_FOLDER_LEN); + } +} + +void GameSetupStruct::read_font_infos(Common::Stream *in, GameDataVersion data_ver) +{ + fonts.resize(numfonts); + if (data_ver < kGameVersion_350) + { + for (int i = 0; i < numfonts; ++i) + SetFontInfoFromLegacyFlags(fonts[i], in->ReadInt8()); + for (int i = 0; i < numfonts; ++i) + fonts[i].Outline = in->ReadInt8(); // size of char + if (data_ver < kGameVersion_341) + return; + for (int i = 0; i < numfonts; ++i) + { + fonts[i].YOffset = in->ReadInt32(); + if (data_ver >= kGameVersion_341_2) + fonts[i].LineSpacing = Math::Max(0, in->ReadInt32()); + } + } + else + { + for (int i = 0; i < numfonts; ++i) + { + uint32_t flags = in->ReadInt32(); + fonts[i].SizePt = in->ReadInt32(); + fonts[i].Outline = in->ReadInt32(); + fonts[i].YOffset = in->ReadInt32(); + fonts[i].LineSpacing = Math::Max(0, in->ReadInt32()); + AdjustFontInfoUsingFlags(fonts[i], flags); + if (data_ver >= kGameVersion_351) + { + fonts[i].AutoOutlineThickness = in->ReadInt32(); + fonts[i].AutoOutlineStyle = + static_cast(in->ReadInt32()); + } + } + } +} + +void GameSetupStruct::ReadInvInfo_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int iteratorCount = 0; iteratorCount < numinvitems; ++iteratorCount) + { + invinfo[iteratorCount].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +void GameSetupStruct::WriteInvInfo_Aligned(Stream *out) +{ + AlignedStream align_s(out, Common::kAligned_Write); + for (int iteratorCount = 0; iteratorCount < numinvitems; ++iteratorCount) + { + invinfo[iteratorCount].WriteToFile(&align_s); + align_s.Reset(); + } +} + +HGameFileError GameSetupStruct::read_cursors(Common::Stream *in, GameDataVersion data_ver) +{ + if (numcursors > MAX_CURSOR) + return new MainGameFileError(kMGFErr_TooManyCursors, String::FromFormat("Count: %d, max: %d", numcursors, MAX_CURSOR)); + + ReadMouseCursors_Aligned(in); + return HGameFileError::None(); +} + +void GameSetupStruct::read_interaction_scripts(Common::Stream *in, GameDataVersion data_ver) +{ + numGlobalVars = 0; + + if (data_ver > kGameVersion_272) // 3.x + { + charScripts.resize(numcharacters); + invScripts.resize(numinvitems); + for (size_t i = 0; i < (size_t)numcharacters; ++i) + charScripts[i].reset(InteractionScripts::CreateFromStream(in)); + // NOTE: new inventory items' events are loaded starting from 1 for some reason + for (size_t i = 1; i < (size_t)numinvitems; ++i) + invScripts[i].reset(InteractionScripts::CreateFromStream(in)); + } + else // 2.x + { + intrChar.resize(numcharacters); + for (size_t i = 0; i < (size_t)numcharacters; ++i) + intrChar[i].reset(Interaction::CreateFromStream(in)); + for (size_t i = 0; i < (size_t)numinvitems; ++i) + intrInv[i].reset(Interaction::CreateFromStream(in)); + + numGlobalVars = in->ReadInt32(); + for (size_t i = 0; i < (size_t)numGlobalVars; ++i) + globalvars[i].Read(in); + } +} + +void GameSetupStruct::read_words_dictionary(Common::Stream *in) +{ + if (load_dictionary) { + dict = new WordsDictionary(); + read_dictionary (dict, in); + } +} + +void GameSetupStruct::ReadMouseCursors_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int iteratorCount = 0; iteratorCount < numcursors; ++iteratorCount) + { + mcurs[iteratorCount].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +void GameSetupStruct::WriteMouseCursors_Aligned(Stream *out) +{ + AlignedStream align_s(out, Common::kAligned_Write); + for (int iteratorCount = 0; iteratorCount < numcursors; ++iteratorCount) + { + mcurs[iteratorCount].WriteToFile(&align_s); + align_s.Reset(); + } +} + +//----------------------------------------------------------------------------- +// Reading Part 2 + +void GameSetupStruct::read_characters(Common::Stream *in, GameDataVersion data_ver) +{ + chars = new CharacterInfo[numcharacters + 5]; // TODO: why +5, is this really needed? + + ReadCharacters_Aligned(in); +} + +void GameSetupStruct::read_lipsync(Common::Stream *in, GameDataVersion data_ver) +{ + if (data_ver >= kGameVersion_254) // lip syncing was introduced in 2.54 + in->ReadArray(&lipSyncFrameLetters[0][0], MAXLIPSYNCFRAMES, 50); +} + +void GameSetupStruct::read_messages(Common::Stream *in, GameDataVersion data_ver) +{ + for (int ee=0;eeReadInt8(); + if (*nextchar == 0) + break; + nextchar++; + } + } + else + read_string_decrypt(in, messages[ee], GLOBALMESLENGTH); + } + delete [] load_messages; + load_messages = nullptr; +} + +void GameSetupStruct::ReadCharacters_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int iteratorCount = 0; iteratorCount < numcharacters; ++iteratorCount) + { + chars[iteratorCount].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +void GameSetupStruct::WriteCharacters_Aligned(Stream *out) +{ + AlignedStream align_s(out, Common::kAligned_Write); + for (int iteratorCount = 0; iteratorCount < numcharacters; ++iteratorCount) + { + chars[iteratorCount].WriteToFile(&align_s); + align_s.Reset(); + } +} + +//----------------------------------------------------------------------------- +// Reading Part 3 + +HGameFileError GameSetupStruct::read_customprops(Common::Stream *in, GameDataVersion data_ver) +{ + dialogScriptNames.resize(numdialog); + viewNames.resize(numviews); + if (data_ver >= kGameVersion_260) // >= 2.60 + { + if (Properties::ReadSchema(propSchema, in) != kPropertyErr_NoError) + return new MainGameFileError(kMGFErr_InvalidPropertySchema); + + int errors = 0; + + charProps.resize(numcharacters); + for (int i = 0; i < numcharacters; ++i) + { + errors += Properties::ReadValues(charProps[i], in); + } + for (int i = 0; i < numinvitems; ++i) + { + errors += Properties::ReadValues(invProps[i], in); + } + + if (errors > 0) + return new MainGameFileError(kMGFErr_InvalidPropertyValues); + + for (int i = 0; i < numviews; ++i) + viewNames[i] = String::FromStream(in); + + for (int i = 0; i < numinvitems; ++i) + invScriptNames[i] = String::FromStream(in); + + for (int i = 0; i < numdialog; ++i) + dialogScriptNames[i] = String::FromStream(in); + } + return HGameFileError::None(); +} + +HGameFileError GameSetupStruct::read_audio(Common::Stream *in, GameDataVersion data_ver) +{ + if (data_ver >= kGameVersion_320) + { + size_t audiotype_count = in->ReadInt32(); + audioClipTypes.resize(audiotype_count); + for (size_t i = 0; i < audiotype_count; ++i) + { + audioClipTypes[i].ReadFromFile(in); + } + + size_t audioclip_count = in->ReadInt32(); + audioClips.resize(audioclip_count); + ReadAudioClips_Aligned(in, audioclip_count); + + scoreClipID = in->ReadInt32(); + } + return HGameFileError::None(); +} + +// Temporarily copied this from acruntim.h; +// it is unknown if this should be defined for all solution, or only runtime +#define STD_BUFFER_SIZE 3000 + +void GameSetupStruct::read_room_names(Stream *in, GameDataVersion data_ver) +{ + if ((data_ver >= kGameVersion_301) && (options[OPT_DEBUGMODE] != 0)) + { + roomCount = in->ReadInt32(); + roomNumbers = new int[roomCount]; + roomNames = new char*[roomCount]; + String pexbuf; + for (int bb = 0; bb < roomCount; bb++) + { + roomNumbers[bb] = in->ReadInt32(); + pexbuf.Read(in, STD_BUFFER_SIZE); + roomNames[bb] = new char[pexbuf.GetLength() + 1]; + strcpy(roomNames[bb], pexbuf); + } + } + else + { + roomCount = 0; + } +} + +void GameSetupStruct::ReadAudioClips_Aligned(Common::Stream *in, size_t count) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (size_t i = 0; i < count; ++i) + { + audioClips[i].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +void GameSetupStruct::ReadFromSaveGame_v321(Stream *in, char* gswas, ccScript* compsc, CharacterInfo* chwas, + WordsDictionary *olddict, char** mesbk) +{ + int bb; + + ReadInvInfo_Aligned(in); + ReadMouseCursors_Aligned(in); + + if (loaded_game_file_version <= kGameVersion_272) + { + for (bb = 0; bb < numinvitems; bb++) + intrInv[bb]->ReadTimesRunFromSave_v321(in); + for (bb = 0; bb < numcharacters; bb++) + intrChar[bb]->ReadTimesRunFromSave_v321(in); + } + + // restore pointer members + globalscript=gswas; + compiled_script=compsc; + chars=chwas; + dict = olddict; + for (int vv=0;vvReadArrayOfInt32(&options[0], OPT_HIGHESTOPTION_321 + 1); + options[OPT_LIPSYNCTEXT] = in->ReadByte(); + + ReadCharacters_Aligned(in); +} + +//============================================================================= + +void ConvertOldGameStruct (OldGameSetupStruct *ogss, GameSetupStruct *gss) { + strcpy (gss->gamename, ogss->gamename); + for (int i = 0; i < 20; i++) + gss->options[i] = ogss->options[i]; + memcpy (&gss->paluses[0], &ogss->paluses[0], 256); + memcpy (&gss->defpal[0], &ogss->defpal[0], 256 * sizeof(color)); + gss->numviews = ogss->numviews; + gss->numcharacters = ogss->numcharacters; + gss->playercharacter = ogss->playercharacter; + gss->totalscore = ogss->totalscore; + gss->numinvitems = ogss->numinvitems; + gss->numdialog = ogss->numdialog; + gss->numdlgmessage = ogss->numdlgmessage; + gss->numfonts = ogss->numfonts; + gss->color_depth = ogss->color_depth; + gss->target_win = ogss->target_win; + gss->dialog_bullet = ogss->dialog_bullet; + gss->hotdot = ogss->hotdot; + gss->hotdotouter = ogss->hotdotouter; + gss->uniqueid = ogss->uniqueid; + gss->numgui = ogss->numgui; + for (int i = 0; i < 10; ++i) + { + SetFontInfoFromLegacyFlags(gss->fonts[i], ogss->fontflags[i]); + gss->fonts[i].Outline = ogss->fontoutline[i]; + } + + for (int i = 0; i < LEGACY_MAX_SPRITES_V25; ++i) + { + gss->SpriteInfos[i].Flags = ogss->spriteflags[i]; + } + + memcpy (&gss->invinfo[0], &ogss->invinfo[0], 100 * sizeof(InventoryItemInfo)); + memcpy (&gss->mcurs[0], &ogss->mcurs[0], 10 * sizeof(MouseCursor)); + for (int i = 0; i < MAXGLOBALMES; i++) + gss->messages[i] = ogss->messages[i]; + gss->dict = ogss->dict; + gss->globalscript = ogss->globalscript; + gss->chars = nullptr; //ogss->chars; + gss->compiled_script = ogss->compiled_script; + gss->numcursors = 10; +} + +void GameSetupStruct::ReadFromSavegame(PStream in) +{ + // of GameSetupStruct + in->ReadArrayOfInt32(options, OPT_HIGHESTOPTION_321 + 1); + options[OPT_LIPSYNCTEXT] = in->ReadInt32(); + // of GameSetupStructBase + playercharacter = in->ReadInt32(); + dialog_bullet = in->ReadInt32(); + hotdot = in->ReadInt16(); + hotdotouter = in->ReadInt16(); + invhotdotsprite = in->ReadInt32(); + default_lipsync_frame = in->ReadInt32(); +} + +void GameSetupStruct::WriteForSavegame(PStream out) +{ + // of GameSetupStruct + out->WriteArrayOfInt32(options, OPT_HIGHESTOPTION_321 + 1); + out->WriteInt32(options[OPT_LIPSYNCTEXT]); + // of GameSetupStructBase + out->WriteInt32(playercharacter); + out->WriteInt32(dialog_bullet); + out->WriteInt16(hotdot); + out->WriteInt16(hotdotouter); + out->WriteInt32(invhotdotsprite); + out->WriteInt32(default_lipsync_frame); +} diff --git a/engines/ags/shared/ac/gamesetupstruct.h b/engines/ags/shared/ac/gamesetupstruct.h new file mode 100644 index 00000000000..322fd4f453e --- /dev/null +++ b/engines/ags/shared/ac/gamesetupstruct.h @@ -0,0 +1,165 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_AC__GAMESETUPSTRUCT_H +#define __AGS_CN_AC__GAMESETUPSTRUCT_H + +#include +#include "ac/audiocliptype.h" +#include "ac/characterinfo.h" // TODO: constants to separate header +#include "ac/gamesetupstructbase.h" +#include "ac/inventoryiteminfo.h" +#include "ac/mousecursor.h" +#include "ac/dynobj/scriptaudioclip.h" +#include "game/customproperties.h" +#include "game/main_game_file.h" // TODO: constants to separate header or split out reading functions + +namespace AGS +{ + namespace Common + { + struct AssetLibInfo; + struct Interaction; + struct InteractionScripts; + typedef std::shared_ptr PInteraction; + typedef std::shared_ptr PInteractionScripts; + } +} + +using AGS::Common::PInteraction; +using AGS::Common::PInteractionScripts; +using AGS::Common::HGameFileError; +struct OldGameSetupStruct; + + +// TODO: split GameSetupStruct into struct used to hold loaded game data, and actual runtime object +struct GameSetupStruct: public GameSetupStructBase { + // This array is used only to read data into; + // font parameters are then put and queried in the fonts module + // TODO: split into installation params (used only when reading) and runtime params + std::vector fonts; + InventoryItemInfo invinfo[MAX_INV]; + MouseCursor mcurs[MAX_CURSOR]; + std::vector intrChar; + PInteraction intrInv[MAX_INV]; + std::vector charScripts; + std::vector invScripts; + // TODO: why we do not use this in the engine instead of + // loaded_game_file_version? + int filever; // just used by editor + Common::String compiled_with; // version of AGS this data was created by + char lipSyncFrameLetters[MAXLIPSYNCFRAMES][50]; + AGS::Common::PropertySchema propSchema; + std::vector charProps; + AGS::Common::StringIMap invProps[MAX_INV]; + // NOTE: although the view names are stored in game data, they are never + // used, nor registered as script exports; numeric IDs are used to + // reference views instead. + std::vector viewNames; + Common::String invScriptNames[MAX_INV]; + std::vector dialogScriptNames; + char guid[MAX_GUID_LENGTH]; + char saveGameFileExtension[MAX_SG_EXT_LENGTH]; + char saveGameFolderName[MAX_SG_FOLDER_LEN]; + int roomCount; + int *roomNumbers; + char **roomNames; + std::vector audioClips; + std::vector audioClipTypes; + // A clip to play when player gains score in game + // TODO: find out why OPT_SCORESOUND option cannot be used to store this in >=3.2 games + int scoreClipID; + + // TODO: I converted original array of sprite infos to vector here, because + // statistically in most games sprites go in long continious sequences with minimal + // gaps, and standard hash-map will have relatively big memory overhead compared. + // Of course vector will not behave very well if user has created e.g. only + // sprite #1 and sprite #1000000. For that reason I decided to still limit static + // sprite count to some reasonable number for the time being. Dynamic sprite IDs are + // added in sequence, so there won't be any issue with these. + // There could be other collection types, more optimal for this case. For example, + // we could use a kind of hash map containing fixed-sized arrays, where size of + // array is calculated based on key spread factor. + std::vector SpriteInfos; + + // Get game's native color depth (bits per pixel) + inline int GetColorDepth() const { return color_depth * 8; } + + + GameSetupStruct(); + ~GameSetupStruct(); + + void Free(); + + // [IKM] Game struct loading code is moved here from Engine's load_game_file + // function; for now it is not supposed to be called by Editor; although it + // is possible that eventually will be. + // + // Since reading game data is made in a bit inconvenient way I had to + // a) divide process into three functions (there's some extra stuff + // being read between them; + // b) use a helper struct to pass some arguments + // + // I also had to move BuildAudioClipArray from the engine and make it + // GameSetupStruct member. + + //-------------------------------------------------------------------- + // Do not call these directly + //------------------------------ + // Part 1 + void read_savegame_info(Common::Stream *in, GameDataVersion data_ver); + void read_font_infos(Common::Stream *in, GameDataVersion data_ver); + HGameFileError read_cursors(Common::Stream *in, GameDataVersion data_ver); + void read_interaction_scripts(Common::Stream *in, GameDataVersion data_ver); + void read_words_dictionary(Common::Stream *in); + + void ReadInvInfo_Aligned(Common::Stream *in); + void WriteInvInfo_Aligned(Common::Stream *out); + void ReadMouseCursors_Aligned(Common::Stream *in); + void WriteMouseCursors_Aligned(Common::Stream *out); + //------------------------------ + // Part 2 + void read_characters(Common::Stream *in, GameDataVersion data_ver); + void read_lipsync(Common::Stream *in, GameDataVersion data_ver); + void read_messages(Common::Stream *in, GameDataVersion data_ver); + + void ReadCharacters_Aligned(Common::Stream *in); + void WriteCharacters_Aligned(Common::Stream *out); + //------------------------------ + // Part 3 + HGameFileError read_customprops(Common::Stream *in, GameDataVersion data_ver); + HGameFileError read_audio(Common::Stream *in, GameDataVersion data_ver); + void read_room_names(Common::Stream *in, GameDataVersion data_ver); + + void ReadAudioClips_Aligned(Common::Stream *in, size_t count); + //-------------------------------------------------------------------- + + // Functions for reading and writing appropriate data from/to save game + void ReadFromSaveGame_v321(Common::Stream *in, char* gswas, ccScript* compsc, CharacterInfo* chwas, + WordsDictionary *olddict, char** mesbk); + + void ReadFromSavegame(Common::PStream in); + void WriteForSavegame(Common::PStream out); +}; + +//============================================================================= +// TODO: find out how this function was supposed to be used +void ConvertOldGameStruct (OldGameSetupStruct *ogss, GameSetupStruct *gss); +// Finds an audio clip using legacy convention index +ScriptAudioClip* GetAudioClipForOldStyleNumber(GameSetupStruct &game, bool is_music, int num); + +#endif // __AGS_CN_AC__GAMESETUPSTRUCT_H diff --git a/engines/ags/shared/ac/gamesetupstructbase.cpp b/engines/ags/shared/ac/gamesetupstructbase.cpp new file mode 100644 index 00000000000..f3cbe33b803 --- /dev/null +++ b/engines/ags/shared/ac/gamesetupstructbase.cpp @@ -0,0 +1,267 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/characterinfo.h" +#include "ac/gamesetupstructbase.h" +#include "ac/game_version.h" +#include "ac/wordsdictionary.h" +#include "script/cc_script.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +GameSetupStructBase::GameSetupStructBase() + : numviews(0) + , numcharacters(0) + , playercharacter(-1) + , totalscore(0) + , numinvitems(0) + , numdialog(0) + , numdlgmessage(0) + , numfonts(0) + , color_depth(0) + , target_win(0) + , dialog_bullet(0) + , hotdot(0) + , hotdotouter(0) + , uniqueid(0) + , numgui(0) + , numcursors(0) + , default_lipsync_frame(0) + , invhotdotsprite(0) + , dict(nullptr) + , globalscript(nullptr) + , chars(nullptr) + , compiled_script(nullptr) + , load_messages(nullptr) + , load_dictionary(false) + , load_compiled_script(false) + , _resolutionType(kGameResolution_Undefined) + , _dataUpscaleMult(1) + , _screenUpscaleMult(1) +{ + memset(gamename, 0, sizeof(gamename)); + memset(options, 0, sizeof(options)); + memset(paluses, 0, sizeof(paluses)); + memset(defpal, 0, sizeof(defpal)); + memset(reserved, 0, sizeof(reserved)); + memset(messages, 0, sizeof(messages)); +} + +GameSetupStructBase::~GameSetupStructBase() +{ + Free(); +} + +void GameSetupStructBase::Free() +{ + for (int i = 0; i < MAXGLOBALMES; ++i) + { + delete[] messages[i]; + messages[i] = nullptr; + } + delete[] load_messages; + load_messages = nullptr; + delete dict; + dict = nullptr; + delete globalscript; + globalscript = nullptr; + delete compiled_script; + compiled_script = nullptr; + delete[] chars; + chars = nullptr; +} + +void GameSetupStructBase::SetDefaultResolution(GameResolutionType type) +{ + SetDefaultResolution(type, Size()); +} + +void GameSetupStructBase::SetDefaultResolution(Size size) +{ + SetDefaultResolution(kGameResolution_Custom, size); +} + +void GameSetupStructBase::SetDefaultResolution(GameResolutionType type, Size size) +{ + // Calculate native res first then remember it + SetNativeResolution(type, size); + _defGameResolution = _gameResolution; + // Setup data resolution according to legacy settings (if set) + _dataResolution = _defGameResolution; + if (IsLegacyHiRes() && options[OPT_NATIVECOORDINATES] == 0) + { + _dataResolution = _defGameResolution / HIRES_COORD_MULTIPLIER; + } + OnResolutionSet(); +} + +void GameSetupStructBase::SetNativeResolution(GameResolutionType type, Size game_res) +{ + if (type == kGameResolution_Custom) + { + _resolutionType = kGameResolution_Custom; + _gameResolution = game_res; + _letterboxSize = _gameResolution; + } + else + { + _resolutionType = type; + _gameResolution = ResolutionTypeToSize(_resolutionType, IsLegacyLetterbox()); + _letterboxSize = ResolutionTypeToSize(_resolutionType, false); + } +} + +void GameSetupStructBase::SetGameResolution(GameResolutionType type) +{ + SetNativeResolution(type, Size()); + OnResolutionSet(); +} + +void GameSetupStructBase::SetGameResolution(Size game_res) +{ + SetNativeResolution(kGameResolution_Custom, game_res); + OnResolutionSet(); +} + +void GameSetupStructBase::OnResolutionSet() +{ + // The final data-to-game multiplier is always set after actual game resolution (not default one) + if (!_dataResolution.IsNull()) + _dataUpscaleMult = _gameResolution.Width / _dataResolution.Width; + else + _dataUpscaleMult = 1; + if (!_defGameResolution.IsNull()) + _screenUpscaleMult = _gameResolution.Width / _defGameResolution.Width; + else + _screenUpscaleMult = 1; + _relativeUIMult = IsLegacyHiRes() ? HIRES_COORD_MULTIPLIER : 1; +} + +void GameSetupStructBase::ReadFromFile(Stream *in) +{ + in->Read(&gamename[0], GAME_NAME_LENGTH); + in->ReadArrayOfInt32(options, MAX_OPTIONS); + if (loaded_game_file_version < kGameVersion_340_4) + { // TODO: this should probably be possible to deduce script API level + // using game data version and other options like OPT_STRICTSCRIPTING + options[OPT_BASESCRIPTAPI] = kScriptAPI_Undefined; + options[OPT_SCRIPTCOMPATLEV] = kScriptAPI_Undefined; + } + in->Read(&paluses[0], 256); + // colors are an array of chars + in->Read(&defpal[0], sizeof(color)*256); + numviews = in->ReadInt32(); + numcharacters = in->ReadInt32(); + playercharacter = in->ReadInt32(); + totalscore = in->ReadInt32(); + numinvitems = in->ReadInt16(); + numdialog = in->ReadInt32(); + numdlgmessage = in->ReadInt32(); + numfonts = in->ReadInt32(); + color_depth = in->ReadInt32(); + target_win = in->ReadInt32(); + dialog_bullet = in->ReadInt32(); + hotdot = in->ReadInt16(); + hotdotouter = in->ReadInt16(); + uniqueid = in->ReadInt32(); + numgui = in->ReadInt32(); + numcursors = in->ReadInt32(); + GameResolutionType resolution_type = (GameResolutionType)in->ReadInt32(); + Size game_size; + if (resolution_type == kGameResolution_Custom && loaded_game_file_version >= kGameVersion_330) + { + game_size.Width = in->ReadInt32(); + game_size.Height = in->ReadInt32(); + } + SetDefaultResolution(resolution_type, game_size); + + default_lipsync_frame = in->ReadInt32(); + invhotdotsprite = in->ReadInt32(); + in->ReadArrayOfInt32(reserved, NUM_INTS_RESERVED); + load_messages = new int32_t[MAXGLOBALMES]; + in->ReadArrayOfInt32(load_messages, MAXGLOBALMES); + + // - GameSetupStruct::read_words_dictionary() checks load_dictionary + // - load_game_file() checks load_compiled_script + load_dictionary = in->ReadInt32() != 0; + in->ReadInt32(); // globalscript + in->ReadInt32(); // chars + load_compiled_script = in->ReadInt32() != 0; +} + +void GameSetupStructBase::WriteToFile(Stream *out) +{ + out->Write(&gamename[0], 50); + out->WriteArrayOfInt32(options, 100); + out->Write(&paluses[0], 256); + // colors are an array of chars + out->Write(&defpal[0], sizeof(color)*256); + out->WriteInt32(numviews); + out->WriteInt32(numcharacters); + out->WriteInt32(playercharacter); + out->WriteInt32(totalscore); + out->WriteInt16(numinvitems); + out->WriteInt32(numdialog); + out->WriteInt32(numdlgmessage); + out->WriteInt32(numfonts); + out->WriteInt32(color_depth); + out->WriteInt32(target_win); + out->WriteInt32(dialog_bullet); + out->WriteInt16(hotdot); + out->WriteInt16(hotdotouter); + out->WriteInt32(uniqueid); + out->WriteInt32(numgui); + out->WriteInt32(numcursors); + out->WriteInt32(_resolutionType); + if (_resolutionType == kGameResolution_Custom) + { + out->WriteInt32(_defGameResolution.Width); + out->WriteInt32(_defGameResolution.Height); + } + out->WriteInt32(default_lipsync_frame); + out->WriteInt32(invhotdotsprite); + out->WriteArrayOfInt32(reserved, 17); + for (int i = 0; i < MAXGLOBALMES; ++i) + { + out->WriteInt32(messages[i] ? 1 : 0); + } + out->WriteInt32(dict ? 1 : 0); + out->WriteInt32(0); // globalscript + out->WriteInt32(0); // chars + out->WriteInt32(compiled_script ? 1 : 0); +} + +Size ResolutionTypeToSize(GameResolutionType resolution, bool letterbox) +{ + switch (resolution) + { + case kGameResolution_Default: + case kGameResolution_320x200: + return letterbox ? Size(320, 240) : Size(320, 200); + case kGameResolution_320x240: + return Size(320, 240); + case kGameResolution_640x400: + return letterbox ? Size(640, 480) : Size(640, 400); + case kGameResolution_640x480: + return Size(640, 480); + case kGameResolution_800x600: + return Size(800, 600); + case kGameResolution_1024x768: + return Size(1024, 768); + case kGameResolution_1280x720: + return Size(1280,720); + } + return Size(); +} diff --git a/engines/ags/shared/ac/gamesetupstructbase.h b/engines/ags/shared/ac/gamesetupstructbase.h new file mode 100644 index 00000000000..1b075834e7f --- /dev/null +++ b/engines/ags/shared/ac/gamesetupstructbase.h @@ -0,0 +1,222 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_AC__GAMESETUPSTRUCTBASE_H +#define __AGS_CN_AC__GAMESETUPSTRUCTBASE_H + +#include "ac/game_version.h" +#include "ac/gamestructdefines.h" +#include "util/string.h" +#include "util/wgt2allg.h" // color (allegro RGB) + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +struct WordsDictionary; +struct CharacterInfo; +struct ccScript; + + +struct GameSetupStructBase { + static const int GAME_NAME_LENGTH = 50; + static const int MAX_OPTIONS = 100; + static const int NUM_INTS_RESERVED = 17; + + char gamename[GAME_NAME_LENGTH]; + int options[MAX_OPTIONS]; + unsigned char paluses[256]; + color defpal[256]; + int numviews; + int numcharacters; + int playercharacter; + int totalscore; + short numinvitems; + int numdialog, numdlgmessage; + int numfonts; + int color_depth; // in bytes per pixel (ie. 1 or 2) + int target_win; + int dialog_bullet; // 0 for none, otherwise slot num of bullet point + unsigned short hotdot, hotdotouter; // inv cursor hotspot dot color + int uniqueid; // random key identifying the game + int numgui; + int numcursors; + int default_lipsync_frame; // used for unknown chars + int invhotdotsprite; + int reserved[NUM_INTS_RESERVED]; + char *messages[MAXGLOBALMES]; + WordsDictionary *dict; + char *globalscript; + CharacterInfo *chars; + ccScript *compiled_script; + + int *load_messages; + bool load_dictionary; + bool load_compiled_script; + // [IKM] 2013-03-30 + // NOTE: it looks like nor 'globalscript', not 'compiled_script' are used + // to store actual script data anytime; 'ccScript* gamescript' global + // pointer is used for that instead. + + GameSetupStructBase(); + ~GameSetupStructBase(); + void Free(); + void SetDefaultResolution(GameResolutionType type); + void SetDefaultResolution(Size game_res); + void SetGameResolution(GameResolutionType type); + void SetGameResolution(Size game_res); + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); + + + // + // ** On game resolution. + // + // Game resolution is a size of a native game screen in pixels. + // This is the "game resolution" that developer sets up in AGS Editor. + // It is in the same units in which sprite and font sizes are defined. + // + // Graphic renderer may scale and stretch game's frame as requested by + // player or system, which will not affect native coordinates in any way. + // + // ** Legacy upscale mode. + // + // In the past engine had a separation between logical and native screen + // coordinates and supported running games "upscaled". E.g. 320x200 games + // could be run as 640x400. This was not done by simply stretching final + // game's drawn frame to the larger window, but by multiplying all data + // containing coordinates and graphics either on load or real-time. + // Games of 640x400 and above were scripted and set up in coordinate units + // that were always x2 times smaller than the one developer chose. + // For example, choosing a 640x400 resolution would make game draw itself + // as 640x400, but all the game logic (object properties, script commands) + // would work in 320x200 (this also let run 640x400 downscaled to 320x200). + // Ignoring the obvious complications, the known benefit from such approach + // was that developers could supply separate sets of fonts and sprites for + // low-res and high-res modes. + // The 3rd generation of AGS still allows to achieve same effect by using + // backwards-compatible option (although it is not recommended except when + // importing and continuing old projects). + // + // In order to support this legacy behavior we have a set of functions for + // coordinate conversion. They are required to move from "data" resolution + // to "final game" resolution and back. + // + // Some of the script commands, as well as some internal engine data use + // coordinates in "game resolution" instead (this should be documented). + // In such case there's another conversion which translates these from + // default to actual resolution; e.g. when 320x200 game is run as 640x400 + // they should be multiplied by 2. + // + // ** TODO. + // + // Truth be told, all this is still implemented incorrectly, because no one + // found time to rewrite the thing. The correct way would perhaps be: + // 1) treat old games as x2 lower resolution than they say. + // 2) support drawing particular sprites and texts in x2 higher resolution + // (assuming display resolution allows). The latter is potentially enabled + // by "sprite batches" system in the engine and will benefit new games too. + + inline GameResolutionType GetResolutionType() const + { + return _resolutionType; + } + + // Get actual game's resolution + const Size &GetGameRes() const { return _gameResolution; } + // Get default resolution the game was created for; + // this is usually equal to GetGameRes except for legacy modes. + const Size &GetDefaultRes() const { return _defGameResolution; } + // Get data & script resolution; + // this is usually equal to GetGameRes except for legacy modes. + const Size &GetDataRes() const { return _dataResolution; } + // Get game data-->final game resolution coordinate multiplier + inline int GetDataUpscaleMult() const { return _dataUpscaleMult; } + // Get multiplier for various default UI sizes, meant to keep UI looks + // more or less readable in any game resolution. + // TODO: find a better solution for UI sizes, perhaps make variables. + inline int GetRelativeUIMult() const { return _relativeUIMult; } + // Get game default res-->final game resolution coordinate multiplier; + // used to convert coordinates from original game res to actual one + inline int GetScreenUpscaleMult() const { return _screenUpscaleMult; } + // Tells if game allows assets defined in relative resolution; + // that is - have to be converted to this game resolution type + inline bool AllowRelativeRes() const { return options[OPT_RELATIVEASSETRES] != 0; } + // Legacy definition of high and low game resolution. + // Used to determine certain hardcoded coordinate conversion logic, but + // does not make much sense today when the resolution is arbitrary. + inline bool IsLegacyHiRes() const + { + if (_resolutionType == kGameResolution_Custom) + return (_gameResolution.Width * _gameResolution.Height) > (320 * 240); + return ::IsLegacyHiRes(_resolutionType); + } + // Tells if data has coordinates in default game resolution + inline bool IsDataInNativeCoordinates() const { return options[OPT_NATIVECOORDINATES] != 0; } + + // Tells if game runs in native letterbox mode (legacy option) + inline bool IsLegacyLetterbox() const { return options[OPT_LETTERBOX] != 0; } + // Get letterboxed frame size + const Size &GetLetterboxSize() const { return _letterboxSize; } + + // Room region/hotspot masks are traditionally 1:1 of the room's size in + // low-resolution games and 1:2 of the room size in high-resolution games. + // This also means that mask relation to data resolution is 1:1 if the + // game uses low-res coordinates in script and 1:2 if high-res. + + // Test if the game is built around old audio system + inline bool IsLegacyAudioSystem() const + { + return loaded_game_file_version < kGameVersion_320; + } + + // Returns the expected filename of a digital audio package + inline AGS::Common::String GetAudioVOXName() const + { + return IsLegacyAudioSystem() ? "music.vox" : "audio.vox"; + } + +private: + void SetDefaultResolution(GameResolutionType type, Size game_res); + void SetNativeResolution(GameResolutionType type, Size game_res); + void OnResolutionSet(); + + // Game's native resolution ID, used to init following values. + GameResolutionType _resolutionType; + + // Determines game's default screen resolution. Use for the reference + // when comparing with actual screen resolution, which may be modified + // by certain overriding game modes. + Size _defGameResolution; + // Determines game's actual resolution. + Size _gameResolution; + // Determines resolution in which loaded data and script define coordinates + // and sizes (with very little exception). + Size _dataResolution; + // Letterboxed frame size. Used when old game is run in native letterbox + // mode. In all other situations is equal to game's resolution. + Size _letterboxSize; + + // Game logic to game resolution coordinate factor + int _dataUpscaleMult; + // Multiplier for various UI drawin sizes, meant to keep UI elements readable + int _relativeUIMult; + // Game default resolution to actual game resolution factor + int _screenUpscaleMult; +}; + +#endif // __AGS_CN_AC__GAMESETUPSTRUCTBASE_H \ No newline at end of file diff --git a/engines/ags/shared/ac/gamestructdefines.h b/engines/ags/shared/ac/gamestructdefines.h new file mode 100644 index 00000000000..77ccee094d6 --- /dev/null +++ b/engines/ags/shared/ac/gamestructdefines.h @@ -0,0 +1,256 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_AC__GAMESTRUCTDEFINES_H +#define __AGS_CN_AC__GAMESTRUCTDEFINES_H + +#include "util/geometry.h" +#include "core/types.h" + +#define PAL_GAMEWIDE 0 +#define PAL_LOCKED 1 +#define PAL_BACKGROUND 2 +#define MAXGLOBALMES 500 +#define GLOBALMESLENGTH 500 +#define MAXLANGUAGE 5 +#define LEGACY_MAX_FONTS 30 +#define OPT_DEBUGMODE 0 +#define OPT_SCORESOUND 1 +#define OPT_WALKONLOOK 2 +#define OPT_DIALOGIFACE 3 +#define OPT_ANTIGLIDE 4 +#define OPT_TWCUSTOM 5 +#define OPT_DIALOGGAP 6 +#define OPT_NOSKIPTEXT 7 +#define OPT_DISABLEOFF 8 +#define OPT_ALWAYSSPCH 9 +#define OPT_SPEECHTYPE 10 +#define OPT_PIXPERFECT 11 +#define OPT_NOWALKMODE 12 +#define OPT_LETTERBOX 13 +#define OPT_FIXEDINVCURSOR 14 +#define OPT_NOLOSEINV 15 +#define OPT_HIRES_FONTS 16 +#define OPT_SPLITRESOURCES 17 +#define OPT_ROTATECHARS 18 +#define OPT_FADETYPE 19 +#define OPT_HANDLEINVCLICKS 20 +#define OPT_MOUSEWHEEL 21 +#define OPT_DIALOGNUMBERED 22 +#define OPT_DIALOGUPWARDS 23 +#define OPT_CROSSFADEMUSIC 24 +#define OPT_ANTIALIASFONTS 25 +#define OPT_THOUGHTGUI 26 +#define OPT_TURNTOFACELOC 27 +#define OPT_RIGHTLEFTWRITE 28 // right-to-left text writing +#define OPT_DUPLICATEINV 29 // if they have 2 of the item, draw it twice +#define OPT_SAVESCREENSHOT 30 +#define OPT_PORTRAITSIDE 31 +#define OPT_STRICTSCRIPTING 32 // don't allow MoveCharacter-style commands +#define OPT_LEFTTORIGHTEVAL 33 // left-to-right operator evaluation +#define OPT_COMPRESSSPRITES 34 +#define OPT_STRICTSTRINGS 35 // don't allow old-style strings +#define OPT_NEWGUIALPHA 36 +#define OPT_RUNGAMEDLGOPTS 37 +#define OPT_NATIVECOORDINATES 38 // defines coordinate relation between game logic and game screen +#define OPT_GLOBALTALKANIMSPD 39 +#define OPT_HIGHESTOPTION_321 39 +#define OPT_SPRITEALPHA 40 +#define OPT_HIGHESTOPTION_330 OPT_SPRITEALPHA +#define OPT_SAFEFILEPATHS 41 +#define OPT_HIGHESTOPTION_335 OPT_SAFEFILEPATHS +#define OPT_DIALOGOPTIONSAPI 42 // version of dialog options API (-1 for pre-3.4.0 API) +#define OPT_BASESCRIPTAPI 43 // version of the Script API (ScriptAPIVersion) used to compile game script +#define OPT_SCRIPTCOMPATLEV 44 // level of API compatibility (ScriptAPIVersion) used to compile game script +#define OPT_RENDERATSCREENRES 45 // scale sprites at the (final) screen resolution +#define OPT_RELATIVEASSETRES 46 // relative asset resolution mode (where sprites are resized to match game type) +#define OPT_WALKSPEEDABSOLUTE 47 // if movement speeds are independent of walkable mask resolution +#define OPT_HIGHESTOPTION OPT_WALKSPEEDABSOLUTE +#define OPT_NOMODMUSIC 98 +#define OPT_LIPSYNCTEXT 99 +#define PORTRAIT_LEFT 0 +#define PORTRAIT_RIGHT 1 +#define PORTRAIT_ALTERNATE 2 +#define PORTRAIT_XPOSITION 3 +#define FADE_NORMAL 0 +#define FADE_INSTANT 1 +#define FADE_DISSOLVE 2 +#define FADE_BOXOUT 3 +#define FADE_CROSSFADE 4 +#define FADE_LAST 4 // this should equal the last one + +// Legacy font flags +//#define FFLG_LEGACY_NOSCALE 0x01 // TODO: is this from legacy format, ever used? +#define FFLG_LEGACY_SIZEMASK 0x3f +#define MAX_LEGACY_FONT_SIZE 63 +// Contemporary font flags +#define FFLG_SIZEMULTIPLIER 0x01 // size data means multiplier +// Font outline types +#define FONT_OUTLINE_NONE -1 +#define FONT_OUTLINE_AUTO -10 + +#define DIALOG_OPTIONS_HIGHLIGHT_COLOR_DEFAULT 14 // Yellow + +#define MAXVIEWNAMELENGTH 15 +#define MAXLIPSYNCFRAMES 20 +#define MAX_GUID_LENGTH 40 +#define MAX_SG_EXT_LENGTH 20 +#define MAX_SG_FOLDER_LEN 50 + +enum GameResolutionType +{ + kGameResolution_Undefined = -1, + // definition of 320x200 in very old versions of the engine (somewhere pre-2.56) + kGameResolution_Default = 0, + kGameResolution_320x200 = 1, + kGameResolution_320x240 = 2, + kGameResolution_640x400 = 3, + kGameResolution_640x480 = 4, + kGameResolution_800x600 = 5, + kGameResolution_1024x768 = 6, + kGameResolution_1280x720 = 7, + kGameResolution_Custom = 8, + kNumGameResolutions, + + kGameResolution_LastLoRes = kGameResolution_320x240, + kGameResolution_FirstHiRes = kGameResolution_640x400 +}; + +inline bool IsLegacyHiRes(GameResolutionType resolution) +{ + return resolution > kGameResolution_LastLoRes; +} + +Size ResolutionTypeToSize(GameResolutionType resolution, bool letterbox = false); + +// Automatic numbering of dialog options (OPT_DIALOGNUMBERED) +enum DialogOptionNumbering +{ + kDlgOptNoNumbering = -1, + kDlgOptKeysOnly = 0, // implicit key shortcuts + kDlgOptNumbering = 1 // draw option indices and use key shortcuts +}; + +// Version of the script api (OPT_BASESCRIPTAPI and OPT_SCRIPTCOMPATLEV). +// If the existing script function meaning had changed, that may be +// possible to find out which implementation to use by checking one of those +// two options. +// NOTE: please remember that those values are valid only for games made with +// 3.4.0 final and above. +enum ScriptAPIVersion +{ + kScriptAPI_Undefined = INT32_MIN, + kScriptAPI_v321 = 0, + kScriptAPI_v330 = 1, + kScriptAPI_v334 = 2, + kScriptAPI_v335 = 3, + kScriptAPI_v340 = 4, + kScriptAPI_v341 = 5, + kScriptAPI_v350 = 6, + kScriptAPI_v3507= 7, + kScriptAPI_v351 = 8, + kScriptAPI_Current = kScriptAPI_v351 +}; + +// Determines whether the graphics renderer should scale sprites at the final +// screen resolution, as opposed to native resolution +enum RenderAtScreenRes +{ + kRenderAtScreenRes_UserDefined = 0, + kRenderAtScreenRes_Enabled = 1, + kRenderAtScreenRes_Disabled = 2, +}; + +// Method to use when blending two sprites with alpha channel +enum GameSpriteAlphaRenderingStyle +{ + kSpriteAlphaRender_Legacy = 0, + kSpriteAlphaRender_Proper +}; + +// Method to use when blending two GUI elements with alpha channel +enum GameGuiAlphaRenderingStyle +{ + kGuiAlphaRender_Legacy = 0, + kGuiAlphaRender_AdditiveAlpha, + kGuiAlphaRender_Proper +}; + + +// Sprite flags (serialized as 8-bit) +#define SPF_HIRES 0x01 // sized for high native resolution (legacy option) +#define SPF_HICOLOR 0x02 // is 16-bit +#define SPF_DYNAMICALLOC 0x04 // created by runtime script +#define SPF_TRUECOLOR 0x08 // is 32-bit +#define SPF_ALPHACHANNEL 0x10 // has alpha-channel +#define SPF_VAR_RESOLUTION 0x20 // variable resolution (refer to SPF_HIRES) +#define SPF_HADALPHACHANNEL 0x80 // the saved sprite on disk has one + +// General information about sprite (properties, size) +struct SpriteInfo +{ + uint32_t Flags; + int Width; + int Height; + + SpriteInfo(); + + // + // Legacy game support + // + // Gets if sprite should adjust its base size depending on game's resolution + inline bool IsRelativeRes() const { return (Flags & SPF_VAR_RESOLUTION) != 0; } + // Gets if sprite belongs to high resolution; hi-res sprites should be + // downscaled in low-res games, and low-res sprites should be upscaled + // in hi-res games + inline bool IsLegacyHiRes() const { return (Flags & SPF_HIRES) != 0; } +}; + +// Various font parameters, defining and extending font rendering behavior. +// While FontRenderer object's main goal is to render single line of text at +// the strictly determined position on canvas, FontInfo may additionally +// provide instructions on adjusting drawing position, as well as arranging +// multiple lines, and similar cases. +struct FontInfo +{ + enum AutoOutlineStyle : int + { + kRounded = 0, + kSquared = 1, + }; + + // General font's loading and rendering flags + uint32_t Flags; + // Font size, in points (basically means pixels in AGS) + int SizePt; + // Factor to multiply base font size by + int SizeMultiplier; + // Outlining font index, or auto-outline flag + char Outline; + // Custom vertical render offset, used mainly for fixing broken fonts + int YOffset; + // custom line spacing between two lines of text (0 = use font height) + int LineSpacing; + // When automatic outlining, thickness of the outline (0 = use legacy thickness) + int AutoOutlineThickness; + // When automatic outlining, style of the outline + AutoOutlineStyle AutoOutlineStyle; + + FontInfo(); +}; + +#endif // __AGS_CN_AC__GAMESTRUCTDEFINES_H diff --git a/engines/ags/shared/ac/interfacebutton.h b/engines/ags/shared/ac/interfacebutton.h new file mode 100644 index 00000000000..6a97a61d50c --- /dev/null +++ b/engines/ags/shared/ac/interfacebutton.h @@ -0,0 +1,29 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_INTERFACEBUTTON_H +#define __AC_INTERFACEBUTTON_H + +#define MAXBUTTON 20 +#define IBFLG_ENABLED 1 +#define IBFLG_INVBOX 2 +struct InterfaceButton { + int x, y, pic, overpic, pushpic, leftclick; + int rightclick; // if inv, then leftclick = wid, rightclick = hit + int reserved_for_future; + char flags; + void set(int xx, int yy, int picc, int overpicc, int actionn); +}; + +#endif // __AC_INTERFACEBUTTON_H \ No newline at end of file diff --git a/engines/ags/shared/ac/interfaceelement.h b/engines/ags/shared/ac/interfaceelement.h new file mode 100644 index 00000000000..4c3094be2d7 --- /dev/null +++ b/engines/ags/shared/ac/interfaceelement.h @@ -0,0 +1,51 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_INTERFACEELEMENT_H +#define __AC_INTERFACEELEMENT_H + +#include "ac/interfacebutton.h" // InterfaceButton + +// this struct should go in a Game struct, not the room structure. +struct InterfaceElement { + int x, y, x2, y2; + int bgcol, fgcol, bordercol; + int vtextxp, vtextyp, vtextalign; // X & Y relative to topleft of interface + char vtext[40]; + int numbuttons; + InterfaceButton button[MAXBUTTON]; + int flags; + int reserved_for_future; + int popupyp; // pops up when mousey < this + char popup; // does it pop up? (like sierra icon bar) + char on; + InterfaceElement(); +}; + +/*struct InterfaceStyle { +int playareax1,playareay1,playareax2,playareay2; // where the game takes place +int vtextxp,vtextyp; +char vtext[40]; +int numbuttons,popupbuttons; +InterfaceButton button[MAXBUTTON]; +int invx1,invy1,invx2,invy2; // set invx1=-1 for Sierra-style inventory +InterfaceStyle() { // sierra interface +playareax1=0; playareay1=13; playareax2=319; playareay2=199; +vtextxp=160; vtextyp=2; strcpy(vtext,"@SCORETEXT@$r@GAMENAME@"); +invx1=-1; numbuttons=2; popupbuttons=1; +button[0].set(0,13,3,-1,0); +} +};*/ + +#endif // __AC_INTERFACEELEMENT_H \ No newline at end of file diff --git a/engines/ags/shared/ac/inventoryiteminfo.cpp b/engines/ags/shared/ac/inventoryiteminfo.cpp new file mode 100644 index 00000000000..910392dc321 --- /dev/null +++ b/engines/ags/shared/ac/inventoryiteminfo.cpp @@ -0,0 +1,55 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/inventoryiteminfo.h" +#include "util/stream.h" +#include "util/string_utils.h" + +using namespace AGS::Common; + +void InventoryItemInfo::ReadFromFile(Stream *in) +{ + in->Read(name, 25); + pic = in->ReadInt32(); + cursorPic = in->ReadInt32(); + hotx = in->ReadInt32(); + hoty = in->ReadInt32(); + in->ReadArrayOfInt32(reserved, 5); + flags = in->ReadInt8(); +} + +void InventoryItemInfo::WriteToFile(Stream *out) +{ + out->Write(name, 25); + out->WriteInt32(pic); + out->WriteInt32(cursorPic); + out->WriteInt32(hotx); + out->WriteInt32(hoty); + out->WriteArrayOfInt32(reserved, 5); + out->WriteInt8(flags); +} + +void InventoryItemInfo::ReadFromSavegame(Stream *in) +{ + StrUtil::ReadString(name, in, 25); + pic = in->ReadInt32(); + cursorPic = in->ReadInt32(); +} + +void InventoryItemInfo::WriteToSavegame(Stream *out) const +{ + StrUtil::WriteString(name, out); + out->WriteInt32(pic); + out->WriteInt32(cursorPic); +} diff --git a/engines/ags/shared/ac/inventoryiteminfo.h b/engines/ags/shared/ac/inventoryiteminfo.h new file mode 100644 index 00000000000..3936ce6f087 --- /dev/null +++ b/engines/ags/shared/ac/inventoryiteminfo.h @@ -0,0 +1,35 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_INVENTORYITEMINFO_H +#define __AC_INVENTORYITEMINFO_H + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +#define IFLG_STARTWITH 1 +struct InventoryItemInfo { + char name[25]; + int pic; + int cursorPic, hotx, hoty; + int reserved[5]; + char flags; + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); + void ReadFromSavegame(Common::Stream *in); + void WriteToSavegame(Common::Stream *out) const; +}; + +#endif // __AC_INVENTORYITEMINFO_H \ No newline at end of file diff --git a/engines/ags/shared/ac/mousecursor.cpp b/engines/ags/shared/ac/mousecursor.cpp new file mode 100644 index 00000000000..1da9225e406 --- /dev/null +++ b/engines/ags/shared/ac/mousecursor.cpp @@ -0,0 +1,58 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/mousecursor.h" +#include "util/stream.h" + +using AGS::Common::Stream; + +MouseCursor::MouseCursor() { pic = 2054; hotx = 0; hoty = 0; name[0] = 0; flags = 0; view = -1; } + +void MouseCursor::ReadFromFile(Stream *in) +{ + pic = in->ReadInt32(); + hotx = in->ReadInt16(); + hoty = in->ReadInt16(); + view = in->ReadInt16(); + in->Read(name, 10); + flags = in->ReadInt8(); +} + +void MouseCursor::WriteToFile(Stream *out) +{ + out->WriteInt32(pic); + out->WriteInt16(hotx); + out->WriteInt16(hoty); + out->WriteInt16(view); + out->Write(name, 10); + out->WriteInt8(flags); +} + +void MouseCursor::ReadFromSavegame(Stream *in) +{ + pic = in->ReadInt32(); + hotx = in->ReadInt32(); + hoty = in->ReadInt32(); + view = in->ReadInt32(); + flags = in->ReadInt32(); +} + +void MouseCursor::WriteToSavegame(Stream *out) const +{ + out->WriteInt32(pic); + out->WriteInt32(hotx); + out->WriteInt32(hoty); + out->WriteInt32(view); + out->WriteInt32(flags); +} diff --git a/engines/ags/shared/ac/mousecursor.h b/engines/ags/shared/ac/mousecursor.h new file mode 100644 index 00000000000..4207ccf5d45 --- /dev/null +++ b/engines/ags/shared/ac/mousecursor.h @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_MOUSECURSOR_H +#define __AC_MOUSECURSOR_H + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +#define MCF_ANIMMOVE 1 +#define MCF_DISABLED 2 +#define MCF_STANDARD 4 +#define MCF_HOTSPOT 8 // only animate when over hotspot +// this struct is also in the plugin header file +struct MouseCursor { + int pic; + short hotx, hoty; + short view; + char name[10]; + char flags; + MouseCursor(); + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); + void ReadFromSavegame(Common::Stream *in); + void WriteToSavegame(Common::Stream *out) const; +}; + +#endif // __AC_MOUSECURSOR_H \ No newline at end of file diff --git a/engines/ags/shared/ac/oldgamesetupstruct.h b/engines/ags/shared/ac/oldgamesetupstruct.h new file mode 100644 index 00000000000..40dbfb7555f --- /dev/null +++ b/engines/ags/shared/ac/oldgamesetupstruct.h @@ -0,0 +1,78 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_AC__OLDGAMESETUPSTRUCT_H +#define __AGS_CN_AC__OLDGAMESETUPSTRUCT_H + +#include "ac/characterinfo.h" // OldCharacterInfo, CharacterInfo +#ifdef UNUSED_CODE +#include "ac/eventblock.h" // EventBlock +#endif +#include "ac/interfaceelement.h" // InterfaceElement +#include "ac/inventoryiteminfo.h" // InventoryItemInfo +#include "ac/mousecursor.h" // MouseCursor +#include "ac/wordsdictionary.h" // WordsDictionary +#include "script/cc_script.h" // ccScript + +struct OriGameSetupStruct { + char gamename[30]; + char options[20]; + unsigned char paluses[256]; + color defpal[256]; + InterfaceElement iface[10]; + int numiface; + int numviews; + MouseCursor mcurs[10]; + char *globalscript; + int numcharacters; + OldCharacterInfo *chars; +#ifdef UNUSED_CODE + EventBlock __charcond[50]; // [IKM] 2012-06-22: does not seem to be used anywhere + EventBlock __invcond[100]; // same +#endif + ccScript *compiled_script; + int playercharacter; + unsigned char __old_spriteflags[2100]; + int totalscore; + short numinvitems; + InventoryItemInfo invinfo[100]; + int numdialog, numdlgmessage; + int numfonts; + int color_depth; // in bytes per pixel (ie. 1 or 2) + int target_win; + int dialog_bullet; // 0 for none, otherwise slot num of bullet point + short hotdot, hotdotouter; // inv cursor hotspot dot + int uniqueid; // random key identifying the game + int reserved[2]; + short numlang; + char langcodes[MAXLANGUAGE][3]; + char *messages[MAXGLOBALMES]; +}; + +struct OriGameSetupStruct2 : public OriGameSetupStruct { + unsigned char fontflags[10]; + char fontoutline[10]; + int numgui; + WordsDictionary *dict; + int reserved2[8]; +}; + +struct OldGameSetupStruct : public OriGameSetupStruct2 { + unsigned char spriteflags[LEGACY_MAX_SPRITES_V25]; +}; + +#endif // __AGS_CN_AC__OLDGAMESETUPSTRUCT_H diff --git a/engines/ags/shared/ac/spritecache.cpp b/engines/ags/shared/ac/spritecache.cpp new file mode 100644 index 00000000000..eecf492e6d6 --- /dev/null +++ b/engines/ags/shared/ac/spritecache.cpp @@ -0,0 +1,983 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// sprite caching system +// +//============================================================================= + +#ifdef _MANAGED +// ensure this doesn't get compiled to .NET IL +#pragma unmanaged +#pragma warning (disable: 4996 4312) // disable deprecation warnings +#endif + +#include "ac/common.h" // quit +#include "ac/gamestructdefines.h" +#include "ac/spritecache.h" +#include "core/assetmanager.h" +#include "debug/out.h" +#include "gfx/bitmap.h" +#include "util/compress.h" +#include "util/file.h" +#include "util/stream.h" + +using namespace AGS::Common; + +// [IKM] We have to forward-declare these because their implementations are in the Engine +extern void initialize_sprite(int); +extern void pre_save_sprite(int); +extern void get_new_size_for_sprite(int, int, int, int &, int &); + +#define START_OF_LIST -1 +#define END_OF_LIST -1 + +const char *spindexid = "SPRINDEX"; + +// TODO: should not be part of SpriteCache, but rather some asset management class? +const String SpriteCache::DefaultSpriteFileName = "acsprset.spr"; +const String SpriteCache::DefaultSpriteIndexName = "sprindex.dat"; + + +SpriteInfo::SpriteInfo() + : Flags(0) + , Width(0) + , Height(0) +{ +} + +SpriteCache::SpriteData::SpriteData() + : Offset(0) + , Size(0) + , Flags(0) + , Image(nullptr) +{ +} + +SpriteCache::SpriteData::~SpriteData() +{ + // TODO: investigate, if it's actually safe/preferred to delete bitmap here + // (some of these bitmaps may be assigned from outside of the cache) +} + + +SpriteCache::SpriteCache(std::vector &sprInfos) + : _sprInfos(sprInfos) +{ + _compressed = false; + Init(); +} + +SpriteCache::~SpriteCache() +{ + Reset(); +} + +size_t SpriteCache::GetCacheSize() const +{ + return _cacheSize; +} + +size_t SpriteCache::GetLockedSize() const +{ + return _lockedSize; +} + +size_t SpriteCache::GetMaxCacheSize() const +{ + return _maxCacheSize; +} + +sprkey_t SpriteCache::GetSpriteSlotCount() const +{ + return _spriteData.size(); +} + +sprkey_t SpriteCache::FindTopmostSprite() const +{ + sprkey_t topmost = -1; + for (sprkey_t i = 0; i < static_cast(_spriteData.size()); ++i) + if (DoesSpriteExist(i)) + topmost = i; + return topmost; +} + +void SpriteCache::SetMaxCacheSize(size_t size) +{ + _maxCacheSize = size; +} + +void SpriteCache::Init() +{ + _cacheSize = 0; + _lockedSize = 0; + _maxCacheSize = (size_t)DEFAULTCACHESIZE_KB * 1024; + _liststart = -1; + _listend = -1; + _lastLoad = -2; +} + +void SpriteCache::Reset() +{ + _stream.reset(); + // TODO: find out if it's safe to simply always delete _spriteData.Image with array element + for (size_t i = 0; i < _spriteData.size(); ++i) + { + if (_spriteData[i].Image) + { + delete _spriteData[i].Image; + _spriteData[i].Image = nullptr; + } + } + _spriteData.clear(); + + _mrulist.clear(); + _mrubacklink.clear(); + + Init(); +} + +void SpriteCache::SetSprite(sprkey_t index, Bitmap *sprite) +{ + if (index < 0 || EnlargeTo(index) != index) + { + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: unable to use index %d", index); + return; + } + if (!sprite) + { + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetSprite: attempt to assign nullptr to index %d", index); + return; + } + _spriteData[index].Image = sprite; + _spriteData[index].Flags = SPRCACHEFLAG_LOCKED; // NOT from asset file + _spriteData[index].Offset = 0; + _spriteData[index].Size = 0; +#ifdef DEBUG_SPRITECACHE + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "SetSprite: (external) %d", index); +#endif +} + +void SpriteCache::SetEmptySprite(sprkey_t index, bool as_asset) +{ + if (index < 0 || EnlargeTo(index) != index) + { + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SetEmptySprite: unable to use index %d", index); + return; + } + if (as_asset) + _spriteData[index].Flags = SPRCACHEFLAG_ISASSET; + RemapSpriteToSprite0(index); +} + +void SpriteCache::SubstituteBitmap(sprkey_t index, Common::Bitmap *sprite) +{ + if (!DoesSpriteExist(index)) + { + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SubstituteBitmap: attempt to set for non-existing sprite %d", index); + return; + } + _spriteData[index].Image = sprite; +#ifdef DEBUG_SPRITECACHE + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "SubstituteBitmap: %d", index); +#endif +} + +void SpriteCache::RemoveSprite(sprkey_t index, bool freeMemory) +{ + if (freeMemory) + delete _spriteData[index].Image; + InitNullSpriteParams(index); +#ifdef DEBUG_SPRITECACHE + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "RemoveSprite: %d", index); +#endif +} + +sprkey_t SpriteCache::EnlargeTo(sprkey_t topmost) +{ + if (topmost < 0 || topmost > MAX_SPRITE_INDEX) + return -1; + if ((size_t)topmost < _spriteData.size()) + return topmost; + + size_t newsize = topmost + 1; + _sprInfos.resize(newsize); + _spriteData.resize(newsize); + _mrulist.resize(newsize); + _mrubacklink.resize(newsize); + return topmost; +} + +sprkey_t SpriteCache::GetFreeIndex() +{ + for (size_t i = MIN_SPRITE_INDEX; i < _spriteData.size(); ++i) + { + // slot empty + if (!DoesSpriteExist(i)) + { + _sprInfos[i] = SpriteInfo(); + _spriteData[i] = SpriteData(); + return i; + } + } + // enlarge the sprite bank to find a free slot and return the first new free slot + return EnlargeTo(_spriteData.size()); +} + +bool SpriteCache::SpriteData::DoesSpriteExist() const +{ + return (Image != nullptr) || // HAS loaded bitmap + ((Flags & SPRCACHEFLAG_ISASSET) != 0); // OR found in the game resources +} + +bool SpriteCache::SpriteData::IsAssetSprite() const +{ + return (Flags & SPRCACHEFLAG_ISASSET) != 0; // found in game resources +} + +bool SpriteCache::SpriteData::IsExternalSprite() const +{ + return (Image != nullptr) && // HAS loaded bitmap + ((Flags & SPRCACHEFLAG_ISASSET) == 0) && // AND NOT found in game resources + ((Flags & SPRCACHEFLAG_REMAPPED) == 0); // AND was NOT remapped to another sprite +} + +bool SpriteCache::SpriteData::IsLocked() const +{ + return (Flags & SPRCACHEFLAG_LOCKED) != 0; +} + +bool SpriteCache::DoesSpriteExist(sprkey_t index) const +{ + return index >= 0 && (size_t)index < _spriteData.size() && _spriteData[index].DoesSpriteExist(); +} + +Bitmap *SpriteCache::operator [] (sprkey_t index) +{ + // invalid sprite slot + if (index < 0 || (size_t)index >= _spriteData.size()) + return nullptr; + + // Externally added sprite, don't put it into MRU list + if (_spriteData[index].IsExternalSprite()) + return _spriteData[index].Image; + + // Sprite exists in file but is not in mem, load it + if ((_spriteData[index].Image == nullptr) && _spriteData[index].IsAssetSprite()) + LoadSprite(index); + + // Locked sprite that shouldn't be put into MRU list + if (_spriteData[index].IsLocked()) + return _spriteData[index].Image; + + if (_liststart < 0) + { + _liststart = index; + _listend = index; + _mrulist[index] = END_OF_LIST; + _mrubacklink[index] = START_OF_LIST; + } + else if (_listend != index) + { + // this is the oldest element being bumped to newest, so update start link + if (index == _liststart) + { + _liststart = _mrulist[index]; + _mrubacklink[_liststart] = START_OF_LIST; + } + // already in list, link previous to next + else if (_mrulist[index] > 0) + { + _mrulist[_mrubacklink[index]] = _mrulist[index]; + _mrubacklink[_mrulist[index]] = _mrubacklink[index]; + } + + // set this as the newest element in the list + _mrulist[index] = END_OF_LIST; + _mrulist[_listend] = index; + _mrubacklink[index] = _listend; + _listend = index; + } + + return _spriteData[index].Image; +} + +void SpriteCache::DisposeOldest() +{ + if (_liststart < 0) + return; + + sprkey_t sprnum = _liststart; + + if ((_spriteData[sprnum].Image != nullptr) && !_spriteData[sprnum].IsLocked()) + { + // Free the memory + // Sprites that are not from the game resources should not normally be in a MRU list; + // if such is met here there's something wrong with the internal cache logic! + if (!_spriteData[sprnum].IsAssetSprite()) + { + quitprintf("SpriteCache::DisposeOldest: attempted to remove sprite %d that was added externally or does not exist", sprnum); + } + _cacheSize -= _spriteData[sprnum].Size; + + delete _spriteData[sprnum].Image; + _spriteData[sprnum].Image = nullptr; + } + + if (_liststart == _listend) + { + // there was one huge sprite, removing it has now emptied the cache completely + if (_cacheSize > 0) + { + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "SPRITE CACHE ERROR: Sprite cache should be empty, but still has %d bytes", _cacheSize); + } + _mrulist[_liststart] = 0; + _liststart = -1; + _listend = -1; + } + else + { + sprkey_t oldstart = _liststart; + _liststart = _mrulist[_liststart]; + _mrulist[oldstart] = 0; + _mrubacklink[_liststart] = START_OF_LIST; + if (oldstart == _liststart) + { + // Somehow, we have got a recursive link to itself, so we + // the game will freeze (since it is not actually freeing any + // memory) + // There must be a bug somewhere causing this, but for now + // let's just reset the cache + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "RUNTIME CACHE ERROR: CACHE INCONSISTENT: RESETTING\n\tAt size %d (of %d), start %d end %d fwdlink=%d", + _cacheSize, _maxCacheSize, oldstart, _listend, _liststart); + DisposeAll(); + } + } + +#ifdef DEBUG_SPRITECACHE + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "DisposeOldest: disposed %d, size now %d KB", sprnum, _cacheSize / 1024); +#endif +} + +void SpriteCache::DisposeAll() +{ + _liststart = -1; + _listend = -1; + for (size_t i = 0; i < _spriteData.size(); ++i) + { + if (!_spriteData[i].IsLocked() && // not locked + _spriteData[i].IsAssetSprite()) // sprite from game resource + { + delete _spriteData[i].Image; + _spriteData[i].Image = nullptr; + } + _mrulist[i] = 0; + _mrubacklink[i] = 0; + } + _cacheSize = _lockedSize; +} + +void SpriteCache::Precache(sprkey_t index) +{ + if (index < 0 || (size_t)index >= _spriteData.size()) + return; + + soff_t sprSize = 0; + + if (_spriteData[index].Image == nullptr) + sprSize = LoadSprite(index); + else if (!_spriteData[index].IsLocked()) + sprSize = _spriteData[index].Size; + + // make sure locked sprites can't fill the cache + _maxCacheSize += sprSize; + _lockedSize += sprSize; + + _spriteData[index].Flags |= SPRCACHEFLAG_LOCKED; + +#ifdef DEBUG_SPRITECACHE + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "Precached %d", index); +#endif +} + +sprkey_t SpriteCache::GetDataIndex(sprkey_t index) +{ + return (_spriteData[index].Flags & SPRCACHEFLAG_REMAPPED) == 0 ? index : 0; +} + +void SpriteCache::SeekToSprite(sprkey_t index) +{ + // If we didn't just load the previous sprite, seek to it + if (index - 1 != _lastLoad) + _stream->Seek(_spriteData[index].Offset, kSeekBegin); +} + +size_t SpriteCache::LoadSprite(sprkey_t index) +{ + int hh = 0; + + while (_cacheSize > _maxCacheSize) + { + DisposeOldest(); + hh++; + if (hh > 1000) + { + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "RUNTIME CACHE ERROR: STUCK IN FREE_UP_MEM; RESETTING CACHE"); + DisposeAll(); + } + } + + if (index < 0 || (size_t)index >= _spriteData.size()) + quit("sprite cache array index out of bounds"); + + sprkey_t load_index = GetDataIndex(index); + SeekToSprite(load_index); + + int coldep = _stream->ReadInt16(); + + if (coldep == 0) + { + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Error, "LoadSprite: asked to load sprite %d (for slot %d) which does not exist.", load_index, index); + _lastLoad = load_index; + return 0; + } + + int wdd = _stream->ReadInt16(); + int htt = _stream->ReadInt16(); + // update the stored width/height + _sprInfos[index].Width = wdd; + _sprInfos[index].Height = htt; + + _spriteData[index].Image = BitmapHelper::CreateBitmap(wdd, htt, coldep * 8); + if (_spriteData[index].Image == nullptr) + { + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Warn, "LoadSprite: failed to init sprite %d, remapping to sprite 0.", index); + RemapSpriteToSprite0(index); + return 0; + } + + Bitmap *image = _spriteData[index].Image; + if (this->_compressed) + { + _stream->ReadInt32(); // skip data size + UnCompressSprite(image, _stream.get()); + } + else + { + if (coldep == 1) + { + for (hh = 0; hh < htt; hh++) + _stream->ReadArray(&image->GetScanLineForWriting(hh)[0], coldep, wdd); + } + else if (coldep == 2) + { + for (hh = 0; hh < htt; hh++) + _stream->ReadArrayOfInt16((int16_t*)&image->GetScanLineForWriting(hh)[0], wdd); + } + else + { + for (hh = 0; hh < htt; hh++) + _stream->ReadArrayOfInt32((int32_t*)&image->GetScanLineForWriting(hh)[0], wdd); + } + } + + _lastLoad = load_index; + + // Stop it adding the sprite to the used list just because it's loaded + // TODO: this messy hack is required, because initialize_sprite calls operator[] + // which puts the sprite to the MRU list. + _spriteData[index].Flags |= SPRCACHEFLAG_LOCKED; + + // TODO: this is ugly: asks the engine to convert the sprite using its own knowledge. + // And engine assigns new bitmap using SpriteCache::SubstituteBitmap(). + // Perhaps change to the callback function pointer? + initialize_sprite(index); + + if (index != 0) // leave sprite 0 locked + _spriteData[index].Flags &= ~SPRCACHEFLAG_LOCKED; + + // we need to store this because the main program might + // alter spritewidth/height if it resizes stuff + size_t size = _sprInfos[index].Width * _sprInfos[index].Height * coldep; + _spriteData[index].Size = size; + _cacheSize += size; + +#ifdef DEBUG_SPRITECACHE + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "Loaded %d, size now %u KB", index, _cacheSize / 1024); +#endif + + return size; +} + +void SpriteCache::RemapSpriteToSprite0(sprkey_t index) +{ + _sprInfos[index].Flags = _sprInfos[0].Flags; + _sprInfos[index].Width = _sprInfos[0].Width; + _sprInfos[index].Height = _sprInfos[0].Height; + _spriteData[index].Image = nullptr; + _spriteData[index].Offset = _spriteData[0].Offset; + _spriteData[index].Size = _spriteData[0].Size; + _spriteData[index].Flags |= SPRCACHEFLAG_REMAPPED; +#ifdef DEBUG_SPRITECACHE + Debug::Printf(kDbgGroup_SprCache, kDbgMsg_Debug, "RemapSpriteToSprite0: %d", index); +#endif +} + +const char *spriteFileSig = " Sprite File "; + +void SpriteCache::CompressSprite(Bitmap *sprite, Stream *out) +{ + const int depth = sprite->GetBPP(); + if (depth == 1) + { + for (int y = 0; y < sprite->GetHeight(); y++) + cpackbitl(&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), out); + } + else if (depth == 2) + { + for (int y = 0; y < sprite->GetHeight(); y++) + cpackbitl16((unsigned short *)&sprite->GetScanLine(y)[0], sprite->GetWidth(), out); + } + else + { + for (int y = 0; y < sprite->GetHeight(); y++) + cpackbitl32((unsigned int *)&sprite->GetScanLine(y)[0], sprite->GetWidth(), out); + } +} + +void SpriteCache::UnCompressSprite(Bitmap *sprite, Stream *in) +{ + const int depth = sprite->GetBPP(); + if (depth == 1) + { + for (int y = 0; y < sprite->GetHeight(); y++) + cunpackbitl(&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in); + } + else if (depth == 2) + { + for (int y = 0; y < sprite->GetHeight(); y++) + cunpackbitl16((unsigned short*)&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in); + } + else + { + for (int y = 0; y < sprite->GetHeight(); y++) + cunpackbitl32((unsigned int*)&sprite->GetScanLineForWriting(y)[0], sprite->GetWidth(), in); + } +} + +int SpriteCache::SaveToFile(const char *filename, bool compressOutput, SpriteFileIndex &index) +{ + Stream *output = Common::File::CreateFile(filename); + if (output == nullptr) + return -1; + + if (compressOutput) + { + // re-open the file so that it can be seeked + delete output; + output = File::OpenFile(filename, Common::kFile_Open, Common::kFile_ReadWrite); // CHECKME why mode was "r+" here? + if (output == nullptr) + return -1; + } + + int spriteFileIDCheck = (int)time(nullptr); + + // sprite file version + output->WriteInt16(kSprfVersion_Current); + + output->WriteArray(spriteFileSig, strlen(spriteFileSig), 1); + + output->WriteInt8(compressOutput ? 1 : 0); + output->WriteInt32(spriteFileIDCheck); + + sprkey_t lastslot = FindTopmostSprite(); + output->WriteInt32(lastslot); + + // allocate buffers to store the indexing info + sprkey_t numsprits = lastslot + 1; + std::vector spritewidths, spriteheights; + std::vector spriteoffs; + spritewidths.resize(numsprits); + spriteheights.resize(numsprits); + spriteoffs.resize(numsprits); + + const int memBufferSize = 100000; + char *memBuffer = new char[memBufferSize]; + + for (sprkey_t i = 0; i <= lastslot; ++i) + { + spriteoffs[i] = output->GetPosition(); + + // if compressing uncompressed sprites, load the sprite into memory + if ((_spriteData[i].Image == nullptr) && (this->_compressed != compressOutput)) + (*this)[i]; + + if (_spriteData[i].Image != nullptr) + { + // image in memory -- write it out + pre_save_sprite(i); + Bitmap *image = _spriteData[i].Image; + int bpss = image->GetColorDepth() / 8; + spritewidths[i] = image->GetWidth(); + spriteheights[i] = image->GetHeight(); + output->WriteInt16(bpss); + output->WriteInt16(spritewidths[i]); + output->WriteInt16(spriteheights[i]); + + if (compressOutput) + { + size_t lenloc = output->GetPosition(); + // write some space for the length data + output->WriteInt32(0); + + CompressSprite(image, output); + + size_t fileSizeSoFar = output->GetPosition(); + // write the length of the compressed data + output->Seek(lenloc, kSeekBegin); + output->WriteInt32((fileSizeSoFar - lenloc) - 4); + output->Seek(0, kSeekEnd); + } + else + { + output->WriteArray(image->GetDataForWriting(), spritewidths[i] * bpss, spriteheights[i]); + } + continue; + } + + if (_spriteData[i].Offset == 0) + { + // sprite doesn't exist + output->WriteInt16(0); // colour depth + spritewidths[i] = 0; + spriteheights[i] = 0; + spriteoffs[i] = 0; + continue; + } + + // not in memory -- seek to it in the source file + sprkey_t load_index = GetDataIndex(i); + SeekToSprite(load_index); + _lastLoad = load_index; + + short colDepth = _stream->ReadInt16(); + output->WriteInt16(colDepth); + + if (colDepth == 0) + continue; + + if (this->_compressed != compressOutput) + { + // shouldn't be able to get here + delete [] memBuffer; + delete output; + return -2; + } + + // and copy the data across + int width = _stream->ReadInt16(); + int height = _stream->ReadInt16(); + + spritewidths[i] = width; + spriteheights[i] = height; + + output->WriteInt16(width); + output->WriteInt16(height); + + int sizeToCopy; + if (this->_compressed) + { + sizeToCopy = _stream->ReadInt32(); + output->WriteInt32(sizeToCopy); + } + else + { + sizeToCopy = width * height * (int)colDepth; + } + + while (sizeToCopy > memBufferSize) + { + _stream->ReadArray(memBuffer, memBufferSize, 1); + output->WriteArray(memBuffer, memBufferSize, 1); + sizeToCopy -= memBufferSize; + } + + _stream->ReadArray(memBuffer, sizeToCopy, 1); + output->WriteArray(memBuffer, sizeToCopy, 1); + } + + delete [] memBuffer; + delete output; + + index.SpriteFileIDCheck = spriteFileIDCheck; + index.LastSlot = lastslot; + index.SpriteCount = numsprits; + index.Widths = spritewidths; + index.Heights = spriteheights; + index.Offsets = spriteoffs; + return 0; +} + +int SpriteCache::SaveSpriteIndex(const char *filename, const SpriteFileIndex &index) +{ + // write the sprite index file + Stream *out = File::CreateFile(filename); + if (!out) + return -1; + // write "SPRINDEX" id + out->WriteArray(spindexid, strlen(spindexid), 1); + // write version + out->WriteInt32(kSpridxfVersion_Current); + out->WriteInt32(index.SpriteFileIDCheck); + // write last sprite number and num sprites, to verify that + // it matches the spr file + out->WriteInt32(index.LastSlot); + out->WriteInt32(index.SpriteCount); + if (index.SpriteCount > 0) + { + out->WriteArrayOfInt16(&index.Widths.front(), index.Widths.size()); + out->WriteArrayOfInt16(&index.Heights.front(), index.Heights.size()); + out->WriteArrayOfInt64(&index.Offsets.front(), index.Offsets.size()); + } + delete out; + return 0; +} + +HError SpriteCache::InitFile(const char *filename, const char *sprindex_filename) +{ + SpriteFileVersion vers; + char buff[20]; + soff_t spr_initial_offs = 0; + int spriteFileID = 0; + + _stream.reset(Common::AssetManager::OpenAsset(filename)); + if (_stream == nullptr) + return new Error(String::FromFormat("Failed to open spriteset file '%s'.", filename)); + + spr_initial_offs = _stream->GetPosition(); + + vers = (SpriteFileVersion)_stream->ReadInt16(); + // read the "Sprite File" signature + _stream->ReadArray(&buff[0], 13, 1); + + if (vers < kSprfVersion_Uncompressed || vers > kSprfVersion_Current) + { + _stream.reset(); + return new Error(String::FromFormat("Unsupported spriteset format (requested %d, supported %d - %d).", vers, kSprfVersion_Uncompressed, kSprfVersion_Current)); + } + + // unknown version + buff[13] = 0; + if (strcmp(buff, spriteFileSig)) + { + _stream.reset(); + return new Error("Uknown spriteset format."); + } + + if (vers == kSprfVersion_Uncompressed) + { + this->_compressed = false; + } + else if (vers == kSprfVersion_Compressed) + { + this->_compressed = true; + } + else if (vers >= kSprfVersion_Last32bit) + { + this->_compressed = (_stream->ReadInt8() == 1); + spriteFileID = _stream->ReadInt32(); + } + + if (vers < kSprfVersion_Compressed) + { + // skip the palette + _stream->Seek(256 * 3); // sizeof(RGB) * 256 + } + + sprkey_t topmost; + if (vers < kSprfVersion_HighSpriteLimit) + topmost = _stream->ReadInt16(); + else + topmost = _stream->ReadInt32(); + if (vers < kSprfVersion_Uncompressed) + topmost = 200; + + EnlargeTo(topmost); + + // if there is a sprite index file, use it + if (LoadSpriteIndexFile(sprindex_filename, spriteFileID, spr_initial_offs, topmost)) + { + // Succeeded + return HError::None(); + } + + // Failed, index file is invalid; index sprites manually + return RebuildSpriteIndex(_stream.get(), topmost, vers); +} + +HError SpriteCache::RebuildSpriteIndex(AGS::Common::Stream *in, sprkey_t topmost, SpriteFileVersion vers) +{ + for (sprkey_t i = 0; i <= topmost; ++i) + { + _spriteData[i].Offset = in->GetPosition(); + _spriteData[i].Flags = 0; + + int coldep = in->ReadInt16(); + + if (coldep == 0) + { + // Empty slot + if (i > 0) + InitNullSpriteParams(i); + + if (in->EOS()) + break; + continue; + } + + if (in->EOS()) + break; + + if ((size_t)i >= _spriteData.size()) + break; + + _spriteData[i].Flags = SPRCACHEFLAG_ISASSET; + _spriteData[i].Image = nullptr; + + int wdd = in->ReadInt16(); + int htt = in->ReadInt16(); + _sprInfos[i].Width = wdd; + _sprInfos[i].Height = htt; + get_new_size_for_sprite(i, wdd, htt, _sprInfos[i].Width, _sprInfos[i].Height); + + size_t spriteDataSize; + if (vers == kSprfVersion_Compressed) + { + spriteDataSize = in->ReadInt32(); + } + else if (vers >= kSprfVersion_Last32bit) + { + spriteDataSize = this->_compressed ? in->ReadInt32() : wdd * coldep * htt; + } + else + { + spriteDataSize = wdd * coldep * htt; + } + in->Seek(spriteDataSize); + } + return HError::None(); +} + +bool SpriteCache::LoadSpriteIndexFile(const char *filename, int expectedFileID, soff_t spr_initial_offs, sprkey_t topmost) +{ + Stream *fidx = Common::AssetManager::OpenAsset(filename); + if (fidx == nullptr) + { + return false; + } + + char buffer[9]; + // check "SPRINDEX" id + fidx->ReadArray(&buffer[0], strlen(spindexid), 1); + buffer[8] = 0; + if (strcmp(buffer, spindexid)) + { + delete fidx; + return false; + } + // check version + SpriteIndexFileVersion vers = (SpriteIndexFileVersion)fidx->ReadInt32(); + if (vers < kSpridxfVersion_Initial || vers > kSpridxfVersion_Current) + { + delete fidx; + return false; + } + if (vers >= kSpridxfVersion_Last32bit) + { + if (fidx->ReadInt32() != expectedFileID) + { + delete fidx; + return false; + } + } + + sprkey_t topmost_index = fidx->ReadInt32(); + // end index+1 should be the same as num sprites + if (fidx->ReadInt32() != topmost_index + 1) + { + delete fidx; + return false; + } + + if (topmost_index != topmost) + { + delete fidx; + return false; + } + + sprkey_t numsprits = topmost_index + 1; + short *rspritewidths = new short[numsprits]; + short *rspriteheights = new short[numsprits]; + soff_t *spriteoffs = new soff_t[numsprits]; + + fidx->ReadArrayOfInt16(&rspritewidths[0], numsprits); + fidx->ReadArrayOfInt16(&rspriteheights[0], numsprits); + if (vers <= kSpridxfVersion_Last32bit) + { + for (sprkey_t i = 0; i < numsprits; ++i) + spriteoffs[i] = fidx->ReadInt32(); + } + else // large file support + { + fidx->ReadArrayOfInt64(spriteoffs, numsprits); + } + + for (sprkey_t i = 0; i <= topmost_index; ++i) + { + if (spriteoffs[i] != 0) + { + _spriteData[i].Flags = SPRCACHEFLAG_ISASSET; + _spriteData[i].Offset = spriteoffs[i] + spr_initial_offs; + get_new_size_for_sprite(i, rspritewidths[i], rspriteheights[i], _sprInfos[i].Width, _sprInfos[i].Height); + } + else if (i > 0) + { + InitNullSpriteParams(i); + } + } + + delete [] rspritewidths; + delete [] rspriteheights; + delete [] spriteoffs; + delete fidx; + return true; +} + +void SpriteCache::DetachFile() +{ + _stream.reset(); + _lastLoad = -2; +} + +int SpriteCache::AttachFile(const char *filename) +{ + _stream.reset(Common::AssetManager::OpenAsset((char *)filename)); + if (_stream == nullptr) + return -1; + return 0; +} + +bool SpriteCache::IsFileCompressed() const +{ + return _compressed; +} diff --git a/engines/ags/shared/ac/spritecache.h b/engines/ags/shared/ac/spritecache.h new file mode 100644 index 00000000000..7296d227df7 --- /dev/null +++ b/engines/ags/shared/ac/spritecache.h @@ -0,0 +1,240 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Sprite caching system. +// +// TODO: split out sprite serialization into something like "SpriteFile" class. +// SpriteCache should only manage caching and provide bitmaps by demand. +// There should be a separate unit which manages streaming and decompressing. +// Perhaps an interface which would allow multiple implementation depending +// on compression type. +// +// TODO: store sprite data in a specialized container type that is optimized +// for having most keys allocated in large continious sequences by default. +// +// Only for the reference: one of the ideas is for container to have a table +// of arrays of fixed size internally. When getting an item the hash would be +// first divided on array size to find the array the item resides in, then the +// item is taken from item from slot index = (hash - arrsize * arrindex). +// TODO: find out if there is already a hash table kind that follows similar +// principle. +// +//============================================================================= + +#ifndef __SPRCACHE_H +#define __SPRCACHE_H + +#include +#include +#include "core/platform.h" +#include "util/error.h" + +namespace AGS { namespace Common { class Stream; class Bitmap; } } +using namespace AGS; // FIXME later +typedef AGS::Common::HError HAGSError; + +struct SpriteInfo; + +// Tells that the sprite is found in the game resources. +#define SPRCACHEFLAG_ISASSET 0x01 +// Tells that the sprite index was remapped to another existing sprite. +#define SPRCACHEFLAG_REMAPPED 0x02 +// Locked sprites are ones that should not be freed when out of cache space. +#define SPRCACHEFLAG_LOCKED 0x04 + +// Max size of the sprite cache, in bytes +#if AGS_PLATFORM_OS_ANDROID || AGS_PLATFORM_OS_IOS +#define DEFAULTCACHESIZE_KB (32 * 1024) +#else +#define DEFAULTCACHESIZE_KB (128 * 1024) +#endif + +// TODO: research old version differences +enum SpriteFileVersion +{ + kSprfVersion_Uncompressed = 4, + kSprfVersion_Compressed = 5, + kSprfVersion_Last32bit = 6, + kSprfVersion_64bit = 10, + kSprfVersion_HighSpriteLimit = 11, + kSprfVersion_Current = kSprfVersion_HighSpriteLimit +}; + +enum SpriteIndexFileVersion +{ + kSpridxfVersion_Initial = 1, + kSpridxfVersion_Last32bit = 2, + kSpridxfVersion_64bit = 10, + kSpridxfVersion_HighSpriteLimit = 11, + kSpridxfVersion_Current = kSpridxfVersion_HighSpriteLimit +}; + + +typedef int32_t sprkey_t; + +// SpriteFileIndex contains sprite file's table of contents +struct SpriteFileIndex +{ + int SpriteFileIDCheck = 0; // tag matching sprite file and index file + sprkey_t LastSlot = -1; + sprkey_t SpriteCount = 0; + std::vector Widths; + std::vector Heights; + std::vector Offsets; +}; + + +class SpriteCache +{ +public: + static const sprkey_t MIN_SPRITE_INDEX = 1; // 0 is reserved for "empty sprite" + static const sprkey_t MAX_SPRITE_INDEX = INT32_MAX - 1; + static const size_t MAX_SPRITE_SLOTS = INT32_MAX; + + // Standart sprite file and sprite index names + static const Common::String DefaultSpriteFileName; + static const Common::String DefaultSpriteIndexName; + + SpriteCache(std::vector &sprInfos); + ~SpriteCache(); + + // Tells if there is a sprite registered for the given index; + // this includes sprites that were explicitly assigned but failed to init and were remapped + bool DoesSpriteExist(sprkey_t index) const; + // Makes sure sprite cache has allocated slots for all sprites up to the given inclusive limit; + // returns requested index on success, or -1 on failure. + sprkey_t EnlargeTo(sprkey_t topmost); + // Finds a free slot index, if all slots are occupied enlarges sprite bank; returns index + sprkey_t GetFreeIndex(); + // Returns current size of the cache, in bytes + size_t GetCacheSize() const; + // Gets the total size of the locked sprites, in bytes + size_t GetLockedSize() const; + // Returns maximal size limit of the cache, in bytes + size_t GetMaxCacheSize() const; + // Returns number of sprite slots in the bank (this includes both actual sprites and free slots) + sprkey_t GetSpriteSlotCount() const; + // Finds the topmost occupied slot index. Warning: may be slow. + sprkey_t FindTopmostSprite() const; + // Loads sprite and and locks in memory (so it cannot get removed implicitly) + void Precache(sprkey_t index); + // Remap the given index to the sprite 0 + void RemapSpriteToSprite0(sprkey_t index); + // Unregisters sprite from the bank and optionally deletes bitmap + void RemoveSprite(sprkey_t index, bool freeMemory); + // Deletes all loaded (non-locked, non-external) images from the cache; + // this keeps all the auxiliary sprite information intact + void DisposeAll(); + // Deletes all data and resets cache to the clear state + void Reset(); + // Assigns new sprite for the given index; this sprite won't be auto disposed + void SetSprite(sprkey_t index, Common::Bitmap *); + // Assigns new sprite for the given index, remapping it to sprite 0; + // optionally marks it as an asset placeholder + void SetEmptySprite(sprkey_t index, bool as_asset); + // Assigns new bitmap for the *registered* sprite without changing its properties + void SubstituteBitmap(sprkey_t index, Common::Bitmap *); + // Sets max cache size in bytes + void SetMaxCacheSize(size_t size); + + // Loads sprite reference information and inits sprite stream + HAGSError InitFile(const char *filename, const char *sprindex_filename); + // Tells if bitmaps in the file are compressed + bool IsFileCompressed() const; + // Opens file stream + int AttachFile(const char *filename); + // Closes file stream + void DetachFile(); + // Saves all sprites to file; fills in index data for external use + // TODO: refactor to be able to save main file and index file separately (separate function for gather data?) + int SaveToFile(const char *filename, bool compressOutput, SpriteFileIndex &index); + // Saves sprite index table in a separate file + int SaveSpriteIndex(const char *filename, const SpriteFileIndex &index); + + // Loads (if it's not in cache yet) and returns bitmap by the sprite index + Common::Bitmap *operator[] (sprkey_t index); + +private: + void Init(); + // Gets the index of a sprite which data is used for the given slot; + // in case of remapped sprite this will return the one given sprite is remapped to + sprkey_t GetDataIndex(sprkey_t index); + // Load sprite from game resource + size_t LoadSprite(sprkey_t index); + // Seek stream to sprite + void SeekToSprite(sprkey_t index); + // Delete the oldest image in cache + void DisposeOldest(); + + // Information required for the sprite streaming + // TODO: split into sprite cache and sprite stream data + struct SpriteData + { + soff_t Offset; // data offset + soff_t Size; // cache size of element, in bytes + uint32_t Flags; + // TODO: investigate if we may safely use unique_ptr here + // (some of these bitmaps may be assigned from outside of the cache) + Common::Bitmap *Image; // actual bitmap + + // Tells if there actually is a registered sprite in this slot + bool DoesSpriteExist() const; + // Tells if there's a game resource corresponding to this slot + bool IsAssetSprite() const; + // Tells if sprite was added externally, not loaded from game resources + bool IsExternalSprite() const; + // Tells if sprite is locked and should not be disposed by cache logic + bool IsLocked() const; + + SpriteData(); + ~SpriteData(); + }; + + // Provided map of sprite infos, to fill in loaded sprite properties + std::vector &_sprInfos; + // Array of sprite references + std::vector _spriteData; + bool _compressed; // are sprites compressed + + std::unique_ptr _stream; // the sprite stream + sprkey_t _lastLoad; // last loaded sprite index + + size_t _maxCacheSize; // cache size limit + size_t _lockedSize; // size in bytes of currently locked images + size_t _cacheSize; // size in bytes of currently cached images + + // MRU list: the way to track which sprites were used recently. + // When clearing up space for new sprites, cache first deletes the sprites + // that were last time used long ago. + std::vector _mrulist; + std::vector _mrubacklink; + int _liststart; + int _listend; + + // Loads sprite index file + bool LoadSpriteIndexFile(const char *filename, int expectedFileID, soff_t spr_initial_offs, sprkey_t topmost); + // Rebuilds sprite index from the main sprite file + HAGSError RebuildSpriteIndex(AGS::Common::Stream *in, sprkey_t topmost, SpriteFileVersion vers); + // Writes compressed sprite to the stream + void CompressSprite(Common::Bitmap *sprite, Common::Stream *out); + // Uncompresses sprite from stream into the given bitmap + void UnCompressSprite(Common::Bitmap *sprite, Common::Stream *in); + + // Initialize the empty sprite slot + void InitNullSpriteParams(sprkey_t index); +}; + +extern SpriteCache spriteset; + +#endif // __SPRCACHE_H diff --git a/engines/ags/shared/ac/view.cpp b/engines/ags/shared/ac/view.cpp new file mode 100644 index 00000000000..f3699cb8da0 --- /dev/null +++ b/engines/ags/shared/ac/view.cpp @@ -0,0 +1,214 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/view.h" +#include "util/alignedstream.h" + +using AGS::Common::AlignedStream; +using AGS::Common::Stream; + +ViewFrame::ViewFrame() + : pic(0) + , xoffs(0) + , yoffs(0) + , speed(0) + , flags(0) + , sound(0) +{ + reserved_for_future[0] = 0; + reserved_for_future[1] = 0; +} + +void ViewFrame::ReadFromFile(Stream *in) +{ + pic = in->ReadInt32(); + xoffs = in->ReadInt16(); + yoffs = in->ReadInt16(); + speed = in->ReadInt16(); + flags = in->ReadInt32(); + sound = in->ReadInt32(); + reserved_for_future[0] = in->ReadInt32(); + reserved_for_future[1] = in->ReadInt32(); +} + +void ViewFrame::WriteToFile(Stream *out) +{ + out->WriteInt32(pic); + out->WriteInt16(xoffs); + out->WriteInt16(yoffs); + out->WriteInt16(speed); + out->WriteInt32(flags); + out->WriteInt32(sound); + out->WriteInt32(reserved_for_future[0]); + out->WriteInt32(reserved_for_future[1]); +} + +ViewLoopNew::ViewLoopNew() + : numFrames(0) + , flags(0) + , frames(nullptr) +{ +} + +bool ViewLoopNew::RunNextLoop() +{ + return (flags & LOOPFLAG_RUNNEXTLOOP); +} + +void ViewLoopNew::Initialize(int frameCount) +{ + numFrames = frameCount; + flags = 0; + frames = (ViewFrame*)calloc(numFrames + 1, sizeof(ViewFrame)); +} + +void ViewLoopNew::Dispose() +{ + if (frames != nullptr) + { + free(frames); + frames = nullptr; + numFrames = 0; + } +} + +void ViewLoopNew::WriteToFile_v321(Stream *out) +{ + out->WriteInt16(numFrames); + out->WriteInt32(flags); + WriteFrames_Aligned(out); +} + +void ViewLoopNew::WriteFrames_Aligned(Stream *out) +{ + AlignedStream align_s(out, Common::kAligned_Write); + for (int i = 0; i < numFrames; ++i) + { + frames[i].WriteToFile(&align_s); + align_s.Reset(); + } +} + +void ViewLoopNew::ReadFromFile_v321(Stream *in) +{ + Initialize(in->ReadInt16()); + flags = in->ReadInt32(); + ReadFrames_Aligned(in); + + // an extra frame is allocated in memory to prevent + // crashes with empty loops -- set its picture to teh BLUE CUP!! + frames[numFrames].pic = 0; +} + +void ViewLoopNew::ReadFrames_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int i = 0; i < numFrames; ++i) + { + frames[i].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +ViewStruct::ViewStruct() + : numLoops(0) + , loops(nullptr) +{ +} + +void ViewStruct::Initialize(int loopCount) +{ + numLoops = loopCount; + if (numLoops > 0) + { + loops = (ViewLoopNew*)calloc(numLoops, sizeof(ViewLoopNew)); + } +} + +void ViewStruct::Dispose() +{ + if (numLoops > 0) + { + free(loops); + numLoops = 0; + } +} + +void ViewStruct::WriteToFile(Stream *out) +{ + out->WriteInt16(numLoops); + for (int i = 0; i < numLoops; i++) + { + loops[i].WriteToFile_v321(out); + } +} + +void ViewStruct::ReadFromFile(Stream *in) +{ + Initialize(in->ReadInt16()); + + for (int i = 0; i < numLoops; i++) + { + loops[i].ReadFromFile_v321(in); + } +} + +ViewStruct272::ViewStruct272() + : numloops(0) +{ + memset(numframes, 0, sizeof(numframes)); + memset(loopflags, 0, sizeof(loopflags)); +} + +void ViewStruct272::ReadFromFile(Stream *in) +{ + numloops = in->ReadInt16(); + for (int i = 0; i < 16; ++i) + { + numframes[i] = in->ReadInt16(); + } + in->ReadArrayOfInt32(loopflags, 16); + for (int j = 0; j < 16; ++j) + { + for (int i = 0; i < 20; ++i) + { + frames[j][i].ReadFromFile(in); + } + } +} + +void Convert272ViewsToNew (const std::vector &oldv, ViewStruct *newv) +{ + for (size_t a = 0; a < oldv.size(); a++) { + newv[a].Initialize(oldv[a].numloops); + + for (int b = 0; b < oldv[a].numloops; b++) + { + newv[a].loops[b].Initialize(oldv[a].numframes[b]); + + if ((oldv[a].numframes[b] > 0) && + (oldv[a].frames[b][oldv[a].numframes[b] - 1].pic == -1)) + { + newv[a].loops[b].flags = LOOPFLAG_RUNNEXTLOOP; + newv[a].loops[b].numFrames--; + } + else + newv[a].loops[b].flags = 0; + + for (int c = 0; c < newv[a].loops[b].numFrames; c++) + newv[a].loops[b].frames[c] = oldv[a].frames[b][c]; + } + } +} diff --git a/engines/ags/shared/ac/view.h b/engines/ags/shared/ac/view.h new file mode 100644 index 00000000000..14afd2b2a7e --- /dev/null +++ b/engines/ags/shared/ac/view.h @@ -0,0 +1,80 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_VIEW_H +#define __AC_VIEW_H + +#include + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +#define VFLG_FLIPSPRITE 1 + +struct ViewFrame { + int pic; + short xoffs, yoffs; + short speed; + int flags; + int sound; // play sound when this frame comes round + int reserved_for_future[2]; + ViewFrame(); + + void ReadFromFile(Common::Stream *in); + void WriteToFile(Common::Stream *out); +}; + +#define LOOPFLAG_RUNNEXTLOOP 1 + +struct ViewLoopNew +{ + short numFrames; + int flags; + ViewFrame *frames; + + ViewLoopNew(); + void Initialize(int frameCount); + void Dispose(); + bool RunNextLoop(); + void WriteToFile_v321(Common::Stream *out); + void ReadFromFile_v321(Common::Stream *in); + void WriteFrames_Aligned(Common::Stream *out); + void ReadFrames_Aligned(Common::Stream *in); +}; + +struct ViewStruct +{ + short numLoops; + ViewLoopNew *loops; + + ViewStruct(); + void Initialize(int loopCount); + void Dispose(); + void WriteToFile(Common::Stream *out); + void ReadFromFile(Common::Stream *in); +}; + +struct ViewStruct272 { + short numloops; + short numframes[16]; + int loopflags[16]; + ViewFrame frames[16][20]; + + ViewStruct272(); + void ReadFromFile(Common::Stream *in); +}; + +void Convert272ViewsToNew(const std::vector &oldv, ViewStruct *newv); + +#endif // __AC_VIEW_H \ No newline at end of file diff --git a/engines/ags/shared/ac/wordsdictionary.cpp b/engines/ags/shared/ac/wordsdictionary.cpp new file mode 100644 index 00000000000..a50bd46e351 --- /dev/null +++ b/engines/ags/shared/ac/wordsdictionary.cpp @@ -0,0 +1,173 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "ac/wordsdictionary.h" +#include "util/stream.h" +#include "util/string_compat.h" + +using AGS::Common::Stream; + +WordsDictionary::WordsDictionary() + : num_words(0) + , word(nullptr) + , wordnum(nullptr) +{ +} + +WordsDictionary::~WordsDictionary() +{ + free_memory(); +} + +void WordsDictionary::allocate_memory(int wordCount) +{ + num_words = wordCount; + if (num_words > 0) + { + word = new char*[wordCount]; + word[0] = new char[wordCount * MAX_PARSER_WORD_LENGTH]; + wordnum = new short[wordCount]; + for (int i = 1; i < wordCount; i++) + { + word[i] = word[0] + MAX_PARSER_WORD_LENGTH * i; + } + } +} + +void WordsDictionary::free_memory() +{ + if (num_words > 0) + { + delete [] word[0]; + delete [] word; + delete [] wordnum; + word = nullptr; + wordnum = nullptr; + num_words = 0; + } +} + +void WordsDictionary::sort () { + int aa, bb; + for (aa = 0; aa < num_words; aa++) { + for (bb = aa + 1; bb < num_words; bb++) { + if (((wordnum[aa] == wordnum[bb]) && (ags_stricmp(word[aa], word[bb]) > 0)) + || (wordnum[aa] > wordnum[bb])) { + short temp = wordnum[aa]; + char tempst[30]; + + wordnum[aa] = wordnum[bb]; + wordnum[bb] = temp; + strcpy(tempst, word[aa]); + strcpy(word[aa], word[bb]); + strcpy(word[bb], tempst); + bb = aa; + } + } + } +} + +int WordsDictionary::find_index (const char*wrem) { + int aa; + for (aa = 0; aa < num_words; aa++) { + if (ags_stricmp (wrem, word[aa]) == 0) + return aa; + } + return -1; +} + +const char *passwencstring = "Avis Durgan"; + +void decrypt_text(char*toenc) { + int adx = 0; + + while (1) { + toenc[0] -= passwencstring[adx]; + if (toenc[0] == 0) + break; + + adx++; + toenc++; + + if (adx > 10) + adx = 0; + } +} + +void read_string_decrypt(Stream *in, char *buf, size_t buf_sz) { + size_t len = in->ReadInt32(); + size_t slen = std::min(buf_sz - 1, len); + in->Read(buf, slen); + if (len > slen) + in->Seek(len - slen); + buf[slen] = 0; + decrypt_text(buf); +} + +void read_dictionary (WordsDictionary *dict, Stream *out) { + int ii; + + dict->allocate_memory(out->ReadInt32()); + for (ii = 0; ii < dict->num_words; ii++) { + read_string_decrypt (out, dict->word[ii], MAX_PARSER_WORD_LENGTH); + dict->wordnum[ii] = out->ReadInt16(); + } +} + +#if defined (OBSOLETE) +// TODO: not a part of wordsdictionary, move to obsoletes +void freadmissout(short *pptr, Stream *in) { + in->ReadArrayOfInt16(&pptr[0], 5); + in->ReadArrayOfInt16(&pptr[7], NUM_CONDIT - 7); + pptr[5] = pptr[6] = 0; +} +#endif + +void encrypt_text(char *toenc) { + int adx = 0, tobreak = 0; + + while (tobreak == 0) { + if (toenc[0] == 0) + tobreak = 1; + + toenc[0] += passwencstring[adx]; + adx++; + toenc++; + + if (adx > 10) + adx = 0; + } +} + +void write_string_encrypt(Stream *out, const char *s) { + int stlent = (int)strlen(s) + 1; + + out->WriteInt32(stlent); + char *enc = ags_strdup(s); + encrypt_text(enc); + out->WriteArray(enc, stlent, 1); + free(enc); +} + +void write_dictionary (WordsDictionary *dict, Stream *out) { + int ii; + + out->WriteInt32(dict->num_words); + for (ii = 0; ii < dict->num_words; ii++) { + write_string_encrypt (out, dict->word[ii]); + out->WriteInt16(dict->wordnum[ii]);//__putshort__lilendian(dict->wordnum[ii], writeto); + } +} diff --git a/engines/ags/shared/ac/wordsdictionary.h b/engines/ags/shared/ac/wordsdictionary.h new file mode 100644 index 00000000000..bfb7247709f --- /dev/null +++ b/engines/ags/shared/ac/wordsdictionary.h @@ -0,0 +1,55 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_WORDSDICTIONARY_H +#define __AC_WORDSDICTIONARY_H + +#include "core/types.h" + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +#define MAX_PARSER_WORD_LENGTH 30 +#define ANYWORD 29999 +#define RESTOFLINE 30000 + +struct WordsDictionary { + int num_words; + char**word; + short*wordnum; + + WordsDictionary(); + ~WordsDictionary(); + void allocate_memory(int wordCount); + void free_memory(); + void sort(); + int find_index (const char *); +}; + +extern const char *passwencstring; + +extern void decrypt_text(char*toenc); +extern void read_string_decrypt(Common::Stream *in, char *buf, size_t buf_sz); +extern void read_dictionary (WordsDictionary *dict, Common::Stream *in); + +#if defined (OBSOLETE) +// TODO: not a part of wordsdictionary, move to obsoletes +extern void freadmissout(short *pptr, Common::Stream *in); +#endif + +extern void encrypt_text(char *toenc); +extern void write_string_encrypt(Common::Stream *out, const char *s); +extern void write_dictionary (WordsDictionary *dict, Common::Stream *out); + +#endif // __AC_WORDSDICTIONARY_H \ No newline at end of file diff --git a/engines/ags/shared/api/stream_api.h b/engines/ags/shared/api/stream_api.h new file mode 100644 index 00000000000..d78e08c7cc3 --- /dev/null +++ b/engines/ags/shared/api/stream_api.h @@ -0,0 +1,90 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// IAGSStream is a contract for stream class, provided by engine to plugin +// on the need, such as saving/restoring the game. +// The user is advised to use advanced helper methods, such as Read/WriteX +// and Read/WriteArrayOfX to allow the stream implementation properly control +// endianness conversions and data padding, when needed. +// +//============================================================================= +#ifndef __AGS_CN_API__IAGSSTREAM_H +#define __AGS_CN_API__IAGSSTREAM_H + +// TODO: it would probably be better to not include core definition headers +// in API class headers, but make separate core headers specifically for +// plugins, and let plugin developers include them manually in plugin sources. +#include "core/types.h" + +namespace AGS +{ +namespace Common +{ + +enum StreamSeek +{ + kSeekBegin, + kSeekCurrent, + kSeekEnd +}; + +class IAGSStream +{ +public: + virtual ~IAGSStream() = default; + + virtual void Close() = 0; + + virtual bool IsValid() const = 0; + virtual bool EOS() const = 0; + virtual soff_t GetLength() const = 0; + virtual soff_t GetPosition() const = 0; + virtual bool CanRead() const = 0; + virtual bool CanWrite() const = 0; + virtual bool CanSeek() const = 0; + + virtual size_t Read(void *buffer, size_t size) = 0; + virtual int32_t ReadByte() = 0; + virtual size_t Write(const void *buffer, size_t size) = 0; + virtual int32_t WriteByte(uint8_t b) = 0; + + virtual int8_t ReadInt8() = 0; + virtual int16_t ReadInt16() = 0; + virtual int32_t ReadInt32() = 0; + virtual int64_t ReadInt64() = 0; + virtual bool ReadBool() = 0; + virtual size_t ReadArray(void *buffer, size_t elem_size, size_t count) = 0; + virtual size_t ReadArrayOfInt8(int8_t *buffer, size_t count) = 0; + virtual size_t ReadArrayOfInt16(int16_t *buffer, size_t count) = 0; + virtual size_t ReadArrayOfInt32(int32_t *buffer, size_t count) = 0; + virtual size_t ReadArrayOfInt64(int64_t *buffer, size_t count) = 0; + + virtual size_t WriteInt8(int8_t val) = 0;; + virtual size_t WriteInt16(int16_t val) = 0; + virtual size_t WriteInt32(int32_t val) = 0; + virtual size_t WriteInt64(int64_t val) = 0; + virtual size_t WriteBool(bool val) = 0; + virtual size_t WriteArray(const void *buffer, size_t elem_size, size_t count) = 0; + virtual size_t WriteArrayOfInt8(const int8_t *buffer, size_t count) = 0; + virtual size_t WriteArrayOfInt16(const int16_t *buffer, size_t count) = 0; + virtual size_t WriteArrayOfInt32(const int32_t *buffer, size_t count) = 0; + virtual size_t WriteArrayOfInt64(const int64_t *buffer, size_t count) = 0; + + virtual bool Seek(soff_t offset, StreamSeek origin = kSeekCurrent) = 0; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_API__IAGSSTREAM_H diff --git a/engines/ags/shared/core/asset.cpp b/engines/ags/shared/core/asset.cpp new file mode 100644 index 00000000000..86483b9352c --- /dev/null +++ b/engines/ags/shared/core/asset.cpp @@ -0,0 +1,37 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/asset.h" + +namespace AGS +{ +namespace Common +{ + +AssetInfo::AssetInfo() + : LibUid(0) + , Offset(0) + , Size(0) +{ +} +void AssetLibInfo::Unload() +{ + BaseFileName = ""; + BaseFilePath = ""; + LibFileNames.clear(); + AssetInfos.clear(); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/core/asset.h b/engines/ags/shared/core/asset.h new file mode 100644 index 00000000000..e619491e2e8 --- /dev/null +++ b/engines/ags/shared/core/asset.h @@ -0,0 +1,60 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AssetInfo and AssetLibInfo - classes describing generic asset library. +// +//============================================================================= + +#ifndef __AGS_CN_CORE__ASSET_H +#define __AGS_CN_CORE__ASSET_H + +#include +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +// Information on single asset +struct AssetInfo +{ + // A pair of filename and libuid is assumed to be unique in game scope + String FileName; // filename associated with asset + int32_t LibUid; // uid of library, containing this asset + soff_t Offset; // asset's position in library file (in bytes) + soff_t Size; // asset's size (in bytes) + + AssetInfo(); +}; + +typedef std::vector AssetVec; + +// Information on multifile asset library +struct AssetLibInfo +{ + String BaseFileName; // library's base (head) filename + String BaseFilePath; // full path to the base filename + std::vector LibFileNames; // filename for each library part + + // Library contents + AssetVec AssetInfos; // information on contained assets + + void Unload(); +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_CORE__ASSET_H diff --git a/engines/ags/shared/core/assetmanager.cpp b/engines/ags/shared/core/assetmanager.cpp new file mode 100644 index 00000000000..26c5ca26c46 --- /dev/null +++ b/engines/ags/shared/core/assetmanager.cpp @@ -0,0 +1,428 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/assetmanager.h" +#include "util/misc.h" // ci_fopen +#include "util/multifilelib.h" +#include "util/path.h" +#include "util/string_utils.h" + + +namespace AGS +{ +namespace Common +{ + +AssetLocation::AssetLocation() + : Offset(0) + , Size(0) +{ +} + + +AssetManager *AssetManager::_theAssetManager = nullptr; + +/* static */ bool AssetManager::CreateInstance() +{ + // Issue a warning - recreating asset manager is not a normal behavior + assert(_theAssetManager == NULL); + delete _theAssetManager; + _theAssetManager = new AssetManager(); + _theAssetManager->SetSearchPriority(kAssetPriorityDir); + return _theAssetManager != nullptr; // well, we should return _something_ +} + +/* static */ void AssetManager::DestroyInstance() +{ + delete _theAssetManager; + _theAssetManager = nullptr; +} + +AssetManager::~AssetManager() +{ + delete &_assetLib; +} + +/* static */ bool AssetManager::SetSearchPriority(AssetSearchPriority priority) +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_SetSearchPriority(priority) : false; +} + +/* static */ AssetSearchPriority AssetManager::GetSearchPriority() +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_GetSearchPriority() : kAssetPriorityUndefined; +} + +/* static */ bool AssetManager::IsDataFile(const String &data_file) +{ + Stream *in = ci_fopen(data_file, Common::kFile_Open, Common::kFile_Read); + if (in) + { + MFLUtil::MFLError err = MFLUtil::TestIsMFL(in, true); + delete in; + return err == MFLUtil::kMFLNoError; + } + return false; +} + +AssetError AssetManager::ReadDataFileTOC(const String &data_file, AssetLibInfo &lib) +{ + Stream *in = ci_fopen(data_file, Common::kFile_Open, Common::kFile_Read); + if (in) + { + MFLUtil::MFLError err = MFLUtil::ReadHeader(lib, in); + delete in; + return (err != MFLUtil::kMFLNoError) ? kAssetErrLibParse : kAssetNoError; + } + return kAssetErrNoLibFile; +} + +/* static */ AssetError AssetManager::SetDataFile(const String &data_file) +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_SetDataFile(data_file) : kAssetErrNoManager; +} + +/* static */ String AssetManager::GetLibraryForAsset(const String &asset_name) +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_GetLibraryForAsset(asset_name) : ""; +} + +/* static */ soff_t AssetManager::GetAssetOffset(const String &asset_name) +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_GetAssetOffset(asset_name) : 0; +} + +/* static */ soff_t AssetManager::GetAssetSize(const String &asset_name) +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_GetAssetSize(asset_name) : 0; +} + +/* static */ soff_t AssetManager::GetLastAssetSize() +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_GetLastAssetSize() : 0; +} + +/* static */ int AssetManager::GetAssetCount() +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_GetAssetCount() : 0; +} + +/* static */ String AssetManager::GetAssetFileByIndex(int index) +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_GetAssetFileByIndex(index) : ""; +} + +/* static */ String AssetManager::GetLibraryBaseFile() +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->_GetLibraryBaseFile() : ""; +} + +/* static */ const AssetLibInfo *AssetManager::GetLibraryTOC() +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? &_theAssetManager->_GetLibraryTOC() : nullptr; +} + +/* static */ bool AssetManager::GetAssetLocation(const String &asset_name, AssetLocation &loc) +{ + assert(_theAssetManager != NULL); + return _theAssetManager ? _theAssetManager->GetAssetByPriority(asset_name, loc, kFile_Open, kFile_Read) : false; +} + +/* static */ bool AssetManager::DoesAssetExist(const String &asset_name) +{ + assert(_theAssetManager != NULL); + if (!_theAssetManager) + { + return false; + } + return _theAssetManager->_DoesAssetExist(asset_name); +} + +/* static */ Stream *AssetManager::OpenAsset(const String &asset_name, + FileOpenMode open_mode, + FileWorkMode work_mode) +{ + assert(_theAssetManager != NULL); + if (!_theAssetManager) + { + return nullptr; + } + return _theAssetManager->OpenAssetAsStream(asset_name, open_mode, work_mode); +} + +AssetManager::AssetManager() + : _assetLib(*new AssetLibInfo()) + , _searchPriority(kAssetPriorityDir) + , _lastAssetSize(0) +{ +} + +bool AssetManager::_SetSearchPriority(AssetSearchPriority priority) +{ + _searchPriority = priority; + return true; +} + +AssetSearchPriority AssetManager::_GetSearchPriority() +{ + return _searchPriority; +} + +AssetError AssetManager::_SetDataFile(const String &data_file) +{ + if (data_file.IsEmpty()) + { + return kAssetErrNoLibFile; + } + if (Path::ComparePaths(_assetLib.BaseFilePath, data_file) == 0) + { + return kAssetNoError; + } + AssetError err = RegisterAssetLib(data_file, ""); + return err; +} + +String AssetManager::_GetLibraryForAsset(const String &asset_name) +{ + if (asset_name.IsEmpty()) + { + return ""; + } + AssetInfo *asset = FindAssetByFileName(asset_name); + if (!asset) + { + // asset not found + return ""; + } + + return MakeLibraryFileNameForAsset(asset); +} + +soff_t AssetManager::_GetAssetOffset(const String &asset_name) +{ + if (asset_name.IsEmpty()) + { + return -1; + } + AssetInfo *asset = FindAssetByFileName(asset_name); + if (asset) + { + return asset->Offset; + } + return -1; +} + +soff_t AssetManager::_GetAssetSize(const String &asset_name) +{ + if (asset_name.IsEmpty()) + { + return -1; + } + AssetInfo *asset = FindAssetByFileName(asset_name); + if (asset) + { + return asset->Size; + } + return -1; +} + +soff_t AssetManager::_GetLastAssetSize() +{ + return _lastAssetSize; +} + +int AssetManager::_GetAssetCount() +{ + return _assetLib.AssetInfos.size(); +} + +String AssetManager::_GetAssetFileByIndex(int index) +{ + if ((index < 0) || ((size_t)index >= _assetLib.AssetInfos.size())) + return nullptr; + + return _assetLib.AssetInfos[index].FileName; +} + +String AssetManager::_GetLibraryBaseFile() +{ + return _assetLib.BaseFileName; +} + +const AssetLibInfo &AssetManager::_GetLibraryTOC() const +{ + return _assetLib; +} + +bool AssetManager::_DoesAssetExist(const String &asset_name) +{ + return FindAssetByFileName(asset_name) != nullptr || + File::TestReadFile(asset_name); +} + +AssetError AssetManager::RegisterAssetLib(const String &data_file, const String &password) +{ + // base path is current directory + _basePath = "."; + + // open data library + Stream *in = ci_fopen(data_file, Common::kFile_Open, Common::kFile_Read); + if (!in) + return kAssetErrNoLibFile; // can't be opened, return error code + + // read MultiFileLibrary header (CLIB) + // PSP: allocate struct on the heap to avoid overflowing the stack. + MFLUtil::MFLError mfl_err = MFLUtil::ReadHeader(_assetLib, in); + delete in; + + if (mfl_err != MFLUtil::kMFLNoError) + { + _assetLib.Unload(); + return kAssetErrLibParse; + } + + // fixup base library filename + String nammwas = data_file; + String data_file_fixed = data_file; + // TODO: this algorythm should be in path/string utils + data_file_fixed.TruncateToRightSection('\\'); + data_file_fixed.TruncateToRightSection('/'); + if (data_file_fixed.Compare(nammwas) != 0) + { + // store complete path + _basePath = nammwas; + _basePath.TruncateToLeft(nammwas.GetLength() - data_file_fixed.GetLength()); + _basePath.TrimRight('\\'); + _basePath.TrimRight('/'); + } + + // set library filename + _assetLib.LibFileNames[0] = data_file_fixed; + // make a lowercase backup of the original file name + _assetLib.BaseFileName = data_file_fixed; + _assetLib.BaseFileName.MakeLower(); + _assetLib.BaseFilePath = Path::MakeAbsolutePath(data_file); + return kAssetNoError; +} + +AssetInfo *AssetManager::FindAssetByFileName(const String &asset_name) +{ + for (size_t i = 0; i < _assetLib.AssetInfos.size(); ++i) + { + if (_assetLib.AssetInfos[i].FileName.CompareNoCase(asset_name) == 0) + { + return &_assetLib.AssetInfos[i]; + } + } + return nullptr; +} + +String AssetManager::MakeLibraryFileNameForAsset(const AssetInfo *asset) +{ + // deduce asset library file containing this asset + return String::FromFormat("%s/%s",_basePath.GetCStr(), _assetLib.LibFileNames[asset->LibUid].GetCStr()); +} + +bool AssetManager::GetAssetFromLib(const String &asset_name, AssetLocation &loc, FileOpenMode open_mode, FileWorkMode work_mode) +{ + if (open_mode != Common::kFile_Open || work_mode != Common::kFile_Read) + return false; // creating/writing is allowed only for common files on disk + + AssetInfo *asset = FindAssetByFileName(asset_name); + if (!asset) + return false; // asset not found + + String libfile = cbuf_to_string_and_free( ci_find_file(nullptr, MakeLibraryFileNameForAsset(asset)) ); + if (libfile.IsEmpty()) + return false; + loc.FileName = libfile; + loc.Offset = asset->Offset; + loc.Size = asset->Size; + return true; +} + +bool AssetManager::GetAssetFromDir(const String &file_name, AssetLocation &loc, FileOpenMode open_mode, FileWorkMode work_mode) +{ + String exfile = cbuf_to_string_and_free( ci_find_file(nullptr, file_name) ); + if (exfile.IsEmpty() || !Path::IsFile(exfile)) + return false; + loc.FileName = exfile; + loc.Offset = 0; + loc.Size = File::GetFileSize(exfile); + return true; +} + +bool AssetManager::GetAssetByPriority(const String &asset_name, AssetLocation &loc, FileOpenMode open_mode, FileWorkMode work_mode) +{ + if (_searchPriority == kAssetPriorityDir) + { + // check for disk, otherwise use datafile + return GetAssetFromDir(asset_name, loc, open_mode, work_mode) || + GetAssetFromLib(asset_name, loc, open_mode, work_mode); + } + else if (_searchPriority == kAssetPriorityLib) + { + // check datafile first, then scan directory + return GetAssetFromLib(asset_name, loc, open_mode, work_mode) || + GetAssetFromDir(asset_name, loc, open_mode, work_mode); + } + return false; +} + +Stream *AssetManager::OpenAssetAsStream(const String &asset_name, FileOpenMode open_mode, FileWorkMode work_mode) +{ + AssetLocation loc; + if (GetAssetByPriority(asset_name, loc, open_mode, work_mode)) + { + Stream *s = File::OpenFile(loc.FileName, open_mode, work_mode); + if (s) + { + s->Seek(loc.Offset, kSeekBegin); + _lastAssetSize = loc.Size; + } + return s; + } + return nullptr; +} + + +String GetAssetErrorText(AssetError err) +{ + switch (err) + { + case kAssetNoError: + return "No error."; + case kAssetErrNoLibFile: + return "Asset library file not found or could not be opened."; + case kAssetErrLibParse: + return "Not an asset library or unsupported format."; + case kAssetErrNoManager: + return "Asset manager is not initialized."; + } + return "Unknown error."; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/core/assetmanager.h b/engines/ags/shared/core/assetmanager.h new file mode 100644 index 00000000000..8a765679ad2 --- /dev/null +++ b/engines/ags/shared/core/assetmanager.h @@ -0,0 +1,155 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Asset manager class fore reading and writing game resources +// Asset manager is a singleton +//----------------------------------------------------------------------------- +// +// The code is based on CLIB32, by Chris Jones (1998-99), DJGPP implementation +// of the CLIB reader. +// +//----------------------------------------------------------------------------- +// +// TODO: +// Ideally AssetManager should take care of enumerating all existing data +// packages and all files in them, while the game itself should not know where +// it receives the data from. +// Files should be registered based on their source package and their types. +// The user must not have access to this information, but is allowed to query +// all files of certain type (perhaps, filtered and ordered by their id). +// +//============================================================================= + +#ifndef __AGS_CN_CORE__ASSETMANAGER_H +#define __AGS_CN_CORE__ASSETMANAGER_H + +#include "util/file.h" // TODO: extract filestream mode constants or introduce generic ones + +namespace AGS +{ +namespace Common +{ + +class Stream; +struct MultiFileLib; +struct AssetLibInfo; +struct AssetInfo; + +enum AssetSearchPriority +{ + // TODO: rename this to something more obvious + kAssetPriorityUndefined, + kAssetPriorityLib, + kAssetPriorityDir +}; + +enum AssetError +{ + kAssetNoError = 0, + kAssetErrNoLibFile = -1, // library file not found or can't be read + kAssetErrLibParse = -2, // bad library file format or read error + kAssetErrNoManager = -6, // asset manager not initialized +}; + +// Explicit location of asset data +struct AssetLocation +{ + String FileName; // file where asset is located + soff_t Offset; // asset's position in file (in bytes) + soff_t Size; // asset's size (in bytes) + + AssetLocation(); +}; + + +class AssetManager +{ +public: + static bool CreateInstance(); + static void DestroyInstance(); + ~AssetManager(); + + static bool SetSearchPriority(AssetSearchPriority priority); + static AssetSearchPriority GetSearchPriority(); + + // Test if given file is main data file + static bool IsDataFile(const String &data_file); + // Read data file table of contents into provided struct + static AssetError ReadDataFileTOC(const String &data_file, AssetLibInfo &lib); + + // NOTE: this group of methods are only temporarily public + static AssetError SetDataFile(const String &data_file); + static String GetLibraryBaseFile(); + static const AssetLibInfo *GetLibraryTOC(); + static int GetAssetCount(); + static String GetLibraryForAsset(const String &asset_name); + static String GetAssetFileByIndex(int index); + static soff_t GetAssetOffset(const String &asset_name); + static soff_t GetAssetSize(const String &asset_name); + // TODO: instead of this support streams that work in a file subsection, limited by size + static soff_t GetLastAssetSize(); + // TODO: this is a workaround that lets us use back-end specific kind of streams + // to read the asset data. This is not ideal, because it limits us to reading from file. + // The better solution could be returning a standart stream object (like std::stream, + // or even std::streambuf), which is used to initialize both AGS and back-end compatible + // stream wrappers. + static bool GetAssetLocation(const String &asset_name, AssetLocation &loc); + + static bool DoesAssetExist(const String &asset_name); + static Stream *OpenAsset(const String &asset_name, + FileOpenMode open_mode = kFile_Open, + FileWorkMode work_mode = kFile_Read); + +private: + AssetManager(); + + bool _SetSearchPriority(AssetSearchPriority priority); + AssetSearchPriority _GetSearchPriority(); + AssetError _SetDataFile(const String &data_file); + String _GetLibraryBaseFile(); + const AssetLibInfo &_GetLibraryTOC() const; + int _GetAssetCount(); + String _GetLibraryForAsset(const String &asset_name); + String _GetAssetFileByIndex(int index); + soff_t _GetAssetOffset(const String &asset_name); + soff_t _GetAssetSize(const String &asset_name); + soff_t _GetLastAssetSize(); + + AssetError RegisterAssetLib(const String &data_file, const String &password); + + bool _DoesAssetExist(const String &asset_name); + + AssetInfo *FindAssetByFileName(const String &asset_name); + String MakeLibraryFileNameForAsset(const AssetInfo *asset); + + bool GetAssetFromLib(const String &asset_name, AssetLocation &loc, Common::FileOpenMode open_mode, Common::FileWorkMode work_mode); + bool GetAssetFromDir(const String &asset_name, AssetLocation &loc, Common::FileOpenMode open_mode, Common::FileWorkMode work_mode); + bool GetAssetByPriority(const String &asset_name, AssetLocation &loc, Common::FileOpenMode open_mode, Common::FileWorkMode work_mode); + Stream *OpenAssetAsStream(const String &asset_name, FileOpenMode open_mode, FileWorkMode work_mode); + + static AssetManager *_theAssetManager; + AssetSearchPriority _searchPriority; + + AssetLibInfo &_assetLib; + String _basePath; // library's parent path (directory) + soff_t _lastAssetSize; // size of asset that was opened last time +}; + + +String GetAssetErrorText(AssetError err); + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_CORE__ASSETMANAGER_H diff --git a/engines/ags/shared/core/def_version.h b/engines/ags/shared/core/def_version.h new file mode 100644 index 00000000000..05857771caa --- /dev/null +++ b/engines/ags/shared/core/def_version.h @@ -0,0 +1,17 @@ +#ifndef __AGS_CN_CORE__DEFVERSION_H +#define __AGS_CN_CORE__DEFVERSION_H + +#define ACI_VERSION_STR "3.5.1.0" +#if defined (RC_INVOKED) // for MSVC resource compiler +#define ACI_VERSION_MSRC_DEF 3,5,1,0 +#endif + +#ifdef NO_MP3_PLAYER +#define SPECIAL_VERSION "NMP" +#else +#define SPECIAL_VERSION "" +#endif + +#define ACI_COPYRIGHT_YEARS "2011-2020" + +#endif // __AGS_CN_CORE__DEFVERSION_H diff --git a/engines/ags/shared/core/platform.h b/engines/ags/shared/core/platform.h new file mode 100644 index 00000000000..b4ded89ed9f --- /dev/null +++ b/engines/ags/shared/core/platform.h @@ -0,0 +1,105 @@ +#ifndef __AC_PLATFORM_H +#define __AC_PLATFORM_H + +// platform definitions. Not intended for replacing types or checking for libraries. + +// check Android first because sometimes it can get confused with host OS +#if defined(__ANDROID__) || defined(ANDROID) + #define AGS_PLATFORM_OS_WINDOWS (0) + #define AGS_PLATFORM_OS_LINUX (0) + #define AGS_PLATFORM_OS_MACOS (0) + #define AGS_PLATFORM_OS_ANDROID (1) + #define AGS_PLATFORM_OS_IOS (0) + #define AGS_PLATFORM_OS_PSP (0) +#elif defined(_WIN32) + //define something for Windows (32-bit and 64-bit) + #define AGS_PLATFORM_OS_WINDOWS (1) + #define AGS_PLATFORM_OS_LINUX (0) + #define AGS_PLATFORM_OS_MACOS (0) + #define AGS_PLATFORM_OS_ANDROID (0) + #define AGS_PLATFORM_OS_IOS (0) + #define AGS_PLATFORM_OS_PSP (0) +#elif defined(__APPLE__) + #include "TargetConditionals.h" + #ifndef TARGET_OS_SIMULATOR + #define TARGET_OS_SIMULATOR (0) + #endif + #ifndef TARGET_OS_IOS + #define TARGET_OS_IOS (0) + #endif + #ifndef TARGET_OS_OSX + #define TARGET_OS_OSX (0) + #endif + + #if TARGET_OS_SIMULATOR || TARGET_IPHONE_SIMULATOR + #define AGS_PLATFORM_OS_WINDOWS (0) + #define AGS_PLATFORM_OS_LINUX (0) + #define AGS_PLATFORM_OS_MACOS (0) + #define AGS_PLATFORM_OS_ANDROID (0) + #define AGS_PLATFORM_OS_IOS (1) + #define AGS_PLATFORM_OS_PSP (0) + #elif TARGET_OS_IOS || TARGET_OS_IPHONE + #define AGS_PLATFORM_OS_WINDOWS (0) + #define AGS_PLATFORM_OS_LINUX (0) + #define AGS_PLATFORM_OS_MACOS (0) + #define AGS_PLATFORM_OS_ANDROID (0) + #define AGS_PLATFORM_OS_IOS (1) + #define AGS_PLATFORM_OS_PSP (0) + #elif TARGET_OS_OSX || TARGET_OS_MAC + #define AGS_PLATFORM_OS_WINDOWS (0) + #define AGS_PLATFORM_OS_LINUX (0) + #define AGS_PLATFORM_OS_MACOS (1) + #define AGS_PLATFORM_OS_ANDROID (0) + #define AGS_PLATFORM_OS_IOS (0) + #define AGS_PLATFORM_OS_PSP (0) + #else + #error "Unknown Apple platform" + #endif +#elif defined(__linux__) + #define AGS_PLATFORM_OS_WINDOWS (0) + #define AGS_PLATFORM_OS_LINUX (1) + #define AGS_PLATFORM_OS_MACOS (0) + #define AGS_PLATFORM_OS_ANDROID (0) + #define AGS_PLATFORM_OS_IOS (0) + #define AGS_PLATFORM_OS_PSP (0) +#else + #error "Unknown platform" +#endif + + +#if defined(__LP64__) + // LP64 machine, OS X or Linux + // int 32bit | long 64bit | long long 64bit | void* 64bit + #define AGS_PLATFORM_64BIT (1) +#elif defined(_WIN64) + // LLP64 machine, Windows + // int 32bit | long 32bit | long long 64bit | void* 64bit + #define AGS_PLATFORM_64BIT (1) +#else + // 32-bit machine, Windows or Linux or OS X + // int 32bit | long 32bit | long long 64bit | void* 32bit + #define AGS_PLATFORM_64BIT (0) +#endif + +#if defined(_WIN32) + #define AGS_PLATFORM_ENDIAN_LITTLE (1) + #define AGS_PLATFORM_ENDIAN_BIG (0) +#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + #define AGS_PLATFORM_ENDIAN_LITTLE (1) + #define AGS_PLATFORM_ENDIAN_BIG (0) +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + #define AGS_PLATFORM_ENDIAN_LITTLE (0) + #define AGS_PLATFORM_ENDIAN_BIG (1) +#else + #error "Unknown platform" +#endif + +#if defined(_DEBUG) + #define AGS_PLATFORM_DEBUG (1) +#elif ! defined(NDEBUG) + #define AGS_PLATFORM_DEBUG (1) +#else + #define AGS_PLATFORM_DEBUG (0) +#endif + +#endif // __AC_PLATFORM_H \ No newline at end of file diff --git a/engines/ags/shared/core/types.h b/engines/ags/shared/core/types.h new file mode 100644 index 00000000000..eadeb6ee34f --- /dev/null +++ b/engines/ags/shared/core/types.h @@ -0,0 +1,61 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Basic types definition +// +//============================================================================= +#ifndef __AGS_CN_CORE__TYPES_H +#define __AGS_CN_CORE__TYPES_H + +#include +#include +#include // for size_t +#include // for _WORDSIZE + +#ifndef NULL +#define NULL nullptr +#endif + +// Not all compilers have this. Added in clang and gcc followed +#ifndef __has_attribute + #define __has_attribute(x) 0 +#endif + +#ifndef FORCEINLINE + #ifdef _MSC_VER + #define FORCEINLINE __forceinline + + #elif defined (__GNUC__) || __has_attribute(__always_inline__) + #define FORCEINLINE inline __attribute__((__always_inline__)) + + #else + #define FORCEINLINE inline + + #endif +#endif + +// Stream offset type +typedef int64_t soff_t; + +#define fixed_t int32_t // fixed point type +#define color_t int32_t + +// TODO: use distinct fixed point class +enum +{ + kShift = 16, + kUnit = 1 << kShift +}; + +#endif // __AGS_CN_CORE__TYPES_H diff --git a/engines/ags/shared/debugging/assert.h b/engines/ags/shared/debugging/assert.h new file mode 100644 index 00000000000..34b1eb043f5 --- /dev/null +++ b/engines/ags/shared/debugging/assert.h @@ -0,0 +1,23 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Debug assertion tools +// +//============================================================================= +#ifndef __AGS_CN_DEBUG__ASSERT_H +#define __AGS_CN_DEBUG__ASSERT_H + +#include + +#endif // __AGS_CN_DEBUG__ASSERT_H diff --git a/engines/ags/shared/debugging/debugmanager.cpp b/engines/ags/shared/debugging/debugmanager.cpp new file mode 100644 index 00000000000..30558fe0e55 --- /dev/null +++ b/engines/ags/shared/debugging/debugmanager.cpp @@ -0,0 +1,254 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include +#include "debug/debugmanager.h" +#include "util/string_types.h" + +namespace AGS +{ +namespace Common +{ + +DebugOutput::DebugOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity, bool enabled) + : _id(id) + , _handler(handler) + , _enabled(enabled) + , _defaultVerbosity(def_verbosity) +{ + _groupFilter.resize(DbgMgr._lastGroupID + 1, _defaultVerbosity); +} + +String DebugOutput::GetID() const +{ + return _id; +} + +IOutputHandler *DebugOutput::GetHandler() const +{ + return _handler; +} + +bool DebugOutput::IsEnabled() const +{ + return _enabled; +} + +void DebugOutput::SetEnabled(bool enable) +{ + _enabled = enable; +} + +void DebugOutput::SetGroupFilter(DebugGroupID id, MessageType verbosity) +{ + uint32_t key = DbgMgr.GetGroup(id).UID.ID; + if (key != kDbgGroup_None) + _groupFilter[key] = verbosity; + else + _unresolvedGroups.insert(std::make_pair(id.SID, verbosity)); +} + +void DebugOutput::SetAllGroupFilters(MessageType verbosity) +{ + for (auto &group : _groupFilter) + group = verbosity; + for (auto &group : _unresolvedGroups) + group.second = verbosity; +} + +void DebugOutput::ClearGroupFilters() +{ + for (auto &gf : _groupFilter) + gf = kDbgMsg_None; + _unresolvedGroups.clear(); +} + +void DebugOutput::ResolveGroupID(DebugGroupID id) +{ + if (!id.IsValid()) + return; + + DebugGroupID real_id = DbgMgr.GetGroup(id).UID; + if (real_id.IsValid()) + { + if (_groupFilter.size() <= id.ID) + _groupFilter.resize(id.ID + 1, _defaultVerbosity); + GroupNameToMTMap::const_iterator it = _unresolvedGroups.find(real_id.SID); + if (it != _unresolvedGroups.end()) + { + _groupFilter[real_id.ID] = it->second; + _unresolvedGroups.erase(it); + } + } +} + +bool DebugOutput::TestGroup(DebugGroupID id, MessageType mt) const +{ + DebugGroupID real_id = DbgMgr.GetGroup(id).UID; + if (real_id.ID == kDbgGroup_None || real_id.ID >= _groupFilter.size()) + return false; + return (_groupFilter[real_id.ID] >= mt) != 0; +} + +DebugManager::DebugManager() +{ + // Add hardcoded groups + RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Main, "main"), "")); + RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Game, "game"), "Game")); + RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_Script, "script"), "Script")); + RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_SprCache, "sprcache"), "Sprite cache")); + RegisterGroup(DebugGroup(DebugGroupID(kDbgGroup_ManObj, "manobj"), "Managed obj")); + _firstFreeGroupID = _groups.size(); + _lastGroupID = _firstFreeGroupID; +} + +DebugGroup DebugManager::GetGroup(DebugGroupID id) +{ + if (id.ID != kDbgGroup_None) + { + return id.ID < _groups.size() ? _groups[id.ID] : DebugGroup(); + } + else if (!id.SID.IsEmpty()) + { + GroupByStringMap::const_iterator it = _groupByStrLookup.find(id.SID); + return it != _groupByStrLookup.end() ? _groups[it->second.ID] : DebugGroup(); + } + return DebugGroup(); +} + +PDebugOutput DebugManager::GetOutput(const String &id) +{ + OutMap::const_iterator it = _outputs.find(id); + return it != _outputs.end() ? it->second.Target : PDebugOutput(); +} + +DebugGroup DebugManager::RegisterGroup(const String &id, const String &out_name) +{ + DebugGroup group = GetGroup(id); + if (group.UID.IsValid()) + return group; + group = DebugGroup(DebugGroupID(++DbgMgr._lastGroupID, id), out_name); + _groups.push_back(group); + _groupByStrLookup[group.UID.SID] = group.UID; + + // Resolve group reference on every output target + for (OutMap::const_iterator it = _outputs.begin(); it != _outputs.end(); ++it) + { + it->second.Target->ResolveGroupID(group.UID); + } + return group; +} + +void DebugManager::RegisterGroup(const DebugGroup &group) +{ + _groups.push_back(group); + _groupByStrLookup[group.UID.SID] = group.UID; +} + +PDebugOutput DebugManager::RegisterOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity, bool enabled) +{ + _outputs[id].Target = PDebugOutput(new DebugOutput(id, handler, def_verbosity, enabled)); + _outputs[id].Suppressed = false; + return _outputs[id].Target; +} + +void DebugManager::UnregisterAll() +{ + _lastGroupID = _firstFreeGroupID; + _groups.clear(); + _groupByStrLookup.clear(); + _outputs.clear(); +} + +void DebugManager::UnregisterGroup(DebugGroupID id) +{ + DebugGroup group = GetGroup(id); + if (!group.UID.IsValid()) + return; + _groups[group.UID.ID] = DebugGroup(); + _groupByStrLookup.erase(group.UID.SID); +} + +void DebugManager::UnregisterOutput(const String &id) +{ + _outputs.erase(id); +} + +void DebugManager::Print(DebugGroupID group_id, MessageType mt, const String &text) +{ + const DebugGroup &group = GetGroup(group_id); + DebugMessage msg(text, group.UID.ID, group.OutputName, mt); + + for (OutMap::iterator it = _outputs.begin(); it != _outputs.end(); ++it) + { + SendMessage(it->second, msg); + } +} + +void DebugManager::SendMessage(const String &out_id, const DebugMessage &msg) +{ + OutMap::iterator it = _outputs.find(out_id); + if (it != _outputs.end()) + SendMessage(it->second, msg); +} + +void DebugManager::SendMessage(OutputSlot &out, const DebugMessage &msg) +{ + IOutputHandler *handler = out.Target->GetHandler(); + if (!handler || !out.Target->IsEnabled() || out.Suppressed) + return; + if (!out.Target->TestGroup(msg.GroupID, msg.MT)) + return; + // We suppress current target before the call so that if it makes + // a call to output system itself, message would not print to the + // same target + out.Suppressed = true; + handler->PrintMessage(msg); + out.Suppressed = false; +} + +// TODO: move this to the dynamically allocated engine object whenever it is implemented +DebugManager DbgMgr; + + +namespace Debug +{ + +void Printf(const char *fmt, ...) +{ + va_list argptr; + va_start(argptr, fmt); + DbgMgr.Print(kDbgGroup_Main, kDbgMsg_Default, String::FromFormatV(fmt, argptr)); + va_end(argptr); +} + +void Printf(MessageType mt, const char *fmt, ...) +{ + va_list argptr; + va_start(argptr, fmt); + DbgMgr.Print(kDbgGroup_Main, mt, String::FromFormatV(fmt, argptr)); + va_end(argptr); +} + +void Printf(DebugGroupID group, MessageType mt, const char *fmt, ...) +{ + va_list argptr; + va_start(argptr, fmt); + DbgMgr.Print(group, mt, String::FromFormatV(fmt, argptr)); + va_end(argptr); +} + +} // namespace Debug + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/debugging/debugmanager.h b/engines/ags/shared/debugging/debugmanager.h new file mode 100644 index 00000000000..48c8c9d17f8 --- /dev/null +++ b/engines/ags/shared/debugging/debugmanager.h @@ -0,0 +1,162 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// AGS logging system is built with idea that the engine components should not +// be bothered with specifying particular output method. Instead they use +// generic logging interface, and the actual message printing is done by one +// or more registered handlers. +// Firstly this makes logging functions independent of running platform or +// back-end, secondly it grants end-users ability to configure output according +// to their preference. +// +// To make the logging work we need to register two sets of "entities": +// debug groups and output targets. +// Debug group is an arbitrary object with a name that describes message +// sender. +// Output target defines printing handler and a set of verbosity rules +// one per each known group. +// +// When the message is sent, it is tagged with one of the existing group IDs +// and a message type (debug info, warning, error). This message is sent onto +// each of the registered output targets, which do checks to find out whether +// the message is permitted to be sent further to the printing handler, or not. +// +//============================================================================= +#ifndef __AGS_CN_DEBUG__DEBUGMANAGER_H +#define __AGS_CN_DEBUG__DEBUGMANAGER_H + +#include +#include +#include "debug/out.h" +#include "debug/outputhandler.h" +#include "util/string.h" +#include "util/string_types.h" + +namespace AGS +{ +namespace Common +{ + +// DebugGroup is a message sender definition, identified by DebugGroupID +// and providing OutputName that could be used when printing its messages. +// OutputName may or may not be same as DebugGroupID.SID. +struct DebugGroup +{ + DebugGroupID UID; + String OutputName; + + DebugGroup() {} + DebugGroup(DebugGroupID id, String out_name) : UID(id), OutputName(out_name) {} +}; + +// DebugOutput is a slot for IOutputHandler with its own group filter +class DebugOutput +{ +public: + DebugOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity = kDbgMsg_All, bool enabled = true); + + String GetID() const; + IOutputHandler *GetHandler() const; + + bool IsEnabled() const; + void SetEnabled(bool enable); + // Setup group filter: either allow or disallow a group with the given ID + void SetGroupFilter(DebugGroupID id, MessageType verbosity); + // Assign same verbosity level to all known groups + void SetAllGroupFilters(MessageType verbosity); + // Clear all group filters; this efficiently disables everything + void ClearGroupFilters(); + // Try to resolve group filter unknown IDs + void ResolveGroupID(DebugGroupID id); + // Test if given group id is permitted + bool TestGroup(DebugGroupID id, MessageType mt) const; + +private: + String _id; + IOutputHandler *_handler; + bool _enabled; + MessageType _defaultVerbosity; + // Set of permitted groups' numeric IDs + std::vector _groupFilter; + // Set of unresolved groups, which numeric IDs are not yet known + typedef std::unordered_map GroupNameToMTMap; + GroupNameToMTMap _unresolvedGroups; +}; + +typedef std::shared_ptr PDebugOutput; + + +class DebugManager +{ + friend class DebugOutput; + +public: + DebugManager(); + + // Gets full group ID for any partial one; if the group is not registered returns unset ID + DebugGroup GetGroup(DebugGroupID id); + // Gets output control interface for the given ID + PDebugOutput GetOutput(const String &id); + // Registers debugging group with the given string ID; numeric ID + // will be assigned internally. Returns full ID pair. + // If the group with such string id already exists, returns existing ID. + DebugGroup RegisterGroup(const String &id, const String &out_name); + // Registers output delegate for passing debug messages to; + // if the output with such id already exists, replaces the old one + PDebugOutput RegisterOutput(const String &id, IOutputHandler *handler, MessageType def_verbosity = kDbgMsg_All, bool enabled = true); + // Unregisters all groups and all targets + void UnregisterAll(); + // Unregisters debugging group with the given ID + void UnregisterGroup(DebugGroupID id); + // Unregisters output delegate with the given ID + void UnregisterOutput(const String &id); + + // Output message of given group and message type + void Print(DebugGroupID group_id, MessageType mt, const String &text); + // Send message directly to the output with given id; the message + // must pass the output's message filter though + void SendMessage(const String &out_id, const DebugMessage &msg); + +private: + // OutputSlot struct wraps over output target and adds a flag which indicates + // that this target is temporarily disabled (for internal use only) + struct OutputSlot + { + PDebugOutput Target; + bool Suppressed; + + OutputSlot() : Suppressed(false) {} + }; + + typedef std::vector GroupVector; + typedef std::unordered_map GroupByStringMap; + typedef std::unordered_map OutMap; + + void RegisterGroup(const DebugGroup &id); + void SendMessage(OutputSlot &out, const DebugMessage &msg); + + uint32_t _firstFreeGroupID; + uint32_t _lastGroupID; + GroupVector _groups; + GroupByStringMap _groupByStrLookup; + OutMap _outputs; +}; + +// TODO: move this to the dynamically allocated engine object whenever it is implemented +extern DebugManager DbgMgr; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_DEBUG__DEBUGMANAGER_H diff --git a/engines/ags/shared/debugging/out.h b/engines/ags/shared/debugging/out.h new file mode 100644 index 00000000000..383b31a481f --- /dev/null +++ b/engines/ags/shared/debugging/out.h @@ -0,0 +1,154 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Debug output interface provides functions which send a formatted message +// tagged with group ID and message type to the registered output handlers. +// +// Depending on configuration this message may be printed by any of those +// handlers, or none of them. The calling unit should not worry about where the +// message goes. +// +//----------------------------------------------------------------------------- +// +// On using message types. +// +// Please keep in mind, that there are different levels of errors. AGS logging +// system allows to classify debug message by two parameters: debug group and +// message type. Sometimes message type alone is not enough. Debug groups can +// also be used to distinct messages that has less (or higher) importance. +// +// For example, there are engine errors and user (game-dev) mistakes. Script +// commands that cannot be executed under given circumstances are user +// mistakes, and usually are not as severe as internal engine errors. This is +// why it is advised to use a separate debug group for mistakes like that to +// distinct them from reports on the internal engine's problems and make +// verbosity configuration flexible. +// +// kDbgMsg_Debug - is the most mundane type of message (if the printing function +// argument list does not specify message type, it is probably kDbgMsg_Debug). +// You can use it for almost anything, from noting some process steps to +// displaying current object state. If certain messages are meant to be printed +// very often, consider using another distinct debug group so that it may be +// disabled to reduce log verbosity. +// +// kDbgMsg_Info - is a type for important notifications, such as initialization +// and stopping of engine components. +// +// kDbgMsg_Warn - this is suggested for more significant cases, when you find +// out that something is not right, but is not immediately affecting engine +// processing. For example: certain object was constructed in a way that +// would make them behave unexpectedly, or certain common files are missing but +// it is not clear yet whether the game will be accessing them. +// In other words: use kDbgMsg_Warn when there is no problem right away, but +// you are *anticipating* that one may happen under certain circumstances. +// +// kDbgMsg_Error - use this kind of message is for actual serious problems. +// If certain operation assumes both positive and negative results are +// acceptable, it is preferred to report such negative result with simple +// kDbgMsg_Debug message. kDbgMsg_Error is for negative results that are not +// considered acceptable for normal run. +// +// kDbgMsg_Fatal - is the message type to be reported when the program or +// component abortion is imminent. +// +//============================================================================= +#ifndef __AGS_CN_DEBUG__OUT_H +#define __AGS_CN_DEBUG__OUT_H + +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +// Message types provide distinction for debug messages by their intent. +enum MessageType +{ + kDbgMsg_None = 0, + // Alerts may be informative messages with topmost level of importance, + // such as reporting engine startup and shutdown. + kDbgMsg_Alert , + // Fatal errors are ones that make program abort immediately. + kDbgMsg_Fatal , + // Error messages are about engine not being able to perform requested + // operation in a situation when that will affect game playability and + // further execution. + kDbgMsg_Error , + // Warnings are made when unexpected or non-standart behavior + // is detected in program, which is not immediately critical, + // but may be a symptom of a bigger problem. + kDbgMsg_Warn , + // General information messages. + kDbgMsg_Info , + // Debug reason is for arbitrary information about events and current + // game state. + kDbgMsg_Debug , + + + // Convenient aliases + kDbgMsg_Default = kDbgMsg_Debug, + kDbgMsg_All = kDbgMsg_Debug +}; + +// This enumeration is a list of common hard-coded groups, but more could +// be added via debugging configuration interface (see 'debug/debug.h'). +enum CommonDebugGroup +{ + kDbgGroup_None = -1, + // Main debug group is for reporting general engine status and issues + kDbgGroup_Main = 0, + // Game group is for logging game logic state and issues + kDbgGroup_Game, + // Log from the game script + kDbgGroup_Script, + // Sprite cache logging + kDbgGroup_SprCache, + // Group for debugging managed object state (can slow engine down!) + kDbgGroup_ManObj +}; + +// Debug group identifier defining either numeric or string id, or both +struct DebugGroupID +{ + uint32_t ID; + String SID; + + DebugGroupID() : ID(kDbgGroup_None) {} + DebugGroupID(uint32_t id, const String &sid = "") : ID(id), SID(sid) {} + DebugGroupID(const String &sid) : ID(kDbgGroup_None), SID(sid) {} + // Tells if any of the id components is valid + bool IsValid() const { return ID != kDbgGroup_None || !SID.IsEmpty(); } + // Tells if both id components are properly set + bool IsComplete() const { return ID != kDbgGroup_None && !SID.IsEmpty(); } +}; + +namespace Debug +{ + // + // Debug output + // + // Output formatted message of default group and default type + void Printf(const char *fmt, ...); + // Output formatted message of default group and given type + void Printf(MessageType mt, const char *fmt, ...); + // Output formatted message of given group and type + void Printf(DebugGroupID group_id, MessageType mt, const char *fmt, ...); + +} // namespace Debug + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_DEBUG__OUT_H diff --git a/engines/ags/shared/debugging/outputhandler.h b/engines/ags/shared/debugging/outputhandler.h new file mode 100644 index 00000000000..d77bcc7e520 --- /dev/null +++ b/engines/ags/shared/debugging/outputhandler.h @@ -0,0 +1,59 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// IOutputHandler is a debug printing interface. Its implementations can be +// registered as potential output for the debug log. +// +//============================================================================= +#ifndef __AGS_CN_DEBUG__OUTPUTHANDLER_H +#define __AGS_CN_DEBUG__OUTPUTHANDLER_H + +#include "debug/out.h" +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +struct DebugMessage +{ + String Text; + uint32_t GroupID; + String GroupName; + MessageType MT; + + DebugMessage() : GroupID(kDbgGroup_None), MT(kDbgMsg_None) {} + DebugMessage(const String &text, uint32_t group_id, const String &group_name, MessageType mt) + : Text(text) + , GroupID(group_id) + , GroupName(group_name) + , MT(mt) + {} +}; + +class IOutputHandler +{ +public: + virtual ~IOutputHandler() = default; + + // Print the given text sent from the debug group. + // Implementations are free to decide which message components are to be printed, and how. + virtual void PrintMessage(const DebugMessage &msg) = 0; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_DEBUG__OUTPUTHANDLER_H diff --git a/engines/ags/shared/font/agsfontrenderer.h b/engines/ags/shared/font/agsfontrenderer.h new file mode 100644 index 00000000000..2f27fcb6786 --- /dev/null +++ b/engines/ags/shared/font/agsfontrenderer.h @@ -0,0 +1,59 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_AGSFONTRENDERER_H +#define __AC_AGSFONTRENDERER_H + +struct BITMAP; + +// WARNING: this interface is exposed for plugins and declared for the second time in agsplugin.h +class IAGSFontRenderer +{ +public: + virtual bool LoadFromDisk(int fontNumber, int fontSize) = 0; + virtual void FreeMemory(int fontNumber) = 0; + virtual bool SupportsExtendedCharacters(int fontNumber) = 0; + virtual int GetTextWidth(const char *text, int fontNumber) = 0; + // Get actual height of the given line of text + virtual int GetTextHeight(const char *text, int fontNumber) = 0; + virtual void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) = 0; + virtual void AdjustYCoordinateForFont(int *ycoord, int fontNumber) = 0; + virtual void EnsureTextValidForFont(char *text, int fontNumber) = 0; +protected: + IAGSFontRenderer() = default; + ~IAGSFontRenderer() = default; +}; + +// Font render params, mainly for dealing with various compatibility issues and +// broken fonts. NOTE: currently left empty as a result of rewrite, but may be +// used again in the future. +struct FontRenderParams +{ + // Font's render multiplier + int SizeMultiplier = 1; +}; + +// NOTE: this extending interface is not yet exposed to plugins +class IAGSFontRenderer2 +{ +public: + virtual bool IsBitmapFont() = 0; + // Load font, applying extended font rendering parameters + virtual bool LoadFromDiskEx(int fontNumber, int fontSize, const FontRenderParams *params) = 0; +protected: + IAGSFontRenderer2() = default; + ~IAGSFontRenderer2() = default; +}; + +#endif // __AC_AGSFONTRENDERER_H diff --git a/engines/ags/shared/font/fonts.cpp b/engines/ags/shared/font/fonts.cpp new file mode 100644 index 00000000000..68167f3f4bf --- /dev/null +++ b/engines/ags/shared/font/fonts.cpp @@ -0,0 +1,400 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include +#include "ac/common.h" // set_our_eip +#include "ac/gamestructdefines.h" +#include "font/fonts.h" +#include "font/ttffontrenderer.h" +#include "font/wfnfontrenderer.h" +#include "gfx/bitmap.h" +#include "gui/guidefines.h" // MAXLINE +#include "util/string_utils.h" + +#define STD_BUFFER_SIZE 3000 + +using namespace AGS::Common; + +namespace AGS +{ +namespace Common +{ + +struct Font +{ + IAGSFontRenderer *Renderer; + IAGSFontRenderer2 *Renderer2; + FontInfo Info; + + Font(); +}; + +Font::Font() + : Renderer(nullptr) + , Renderer2(nullptr) +{} + +} // Common +} // AGS + +static std::vector fonts; +static TTFFontRenderer ttfRenderer; +static WFNFontRenderer wfnRenderer; + + +FontInfo::FontInfo() + : Flags(0) + , SizePt(0) + , SizeMultiplier(1) + , Outline(FONT_OUTLINE_NONE) + , YOffset(0) + , LineSpacing(0) + , AutoOutlineStyle(kRounded) + , AutoOutlineThickness(1) +{} + + +void init_font_renderer() +{ + alfont_init(); + alfont_text_mode(-1); +} + +void shutdown_font_renderer() +{ + set_our_eip(9919); + alfont_exit(); +} + +void adjust_y_coordinate_for_text(int* ypos, size_t fontnum) +{ + if (fontnum >= fonts.size() || !fonts[fontnum].Renderer) + return; + fonts[fontnum].Renderer->AdjustYCoordinateForFont(ypos, fontnum); +} + +bool font_first_renderer_loaded() +{ + return fonts.size() > 0 && fonts[0].Renderer != nullptr; +} + +bool is_font_loaded(size_t fontNumber) +{ + return fontNumber < fonts.size() && fonts[fontNumber].Renderer != nullptr;; +} + +IAGSFontRenderer* font_replace_renderer(size_t fontNumber, IAGSFontRenderer* renderer) +{ + if (fontNumber >= fonts.size()) + return nullptr; + IAGSFontRenderer* oldRender = fonts[fontNumber].Renderer; + fonts[fontNumber].Renderer = renderer; + fonts[fontNumber].Renderer2 = nullptr; + return oldRender; +} + +bool is_bitmap_font(size_t fontNumber) +{ + if (fontNumber >= fonts.size() || !fonts[fontNumber].Renderer2) + return false; + return fonts[fontNumber].Renderer2->IsBitmapFont(); +} + +bool font_supports_extended_characters(size_t fontNumber) +{ + if (fontNumber >= fonts.size() || !fonts[fontNumber].Renderer) + return false; + return fonts[fontNumber].Renderer->SupportsExtendedCharacters(fontNumber); +} + +void ensure_text_valid_for_font(char *text, size_t fontnum) +{ + if (fontnum >= fonts.size() || !fonts[fontnum].Renderer) + return; + fonts[fontnum].Renderer->EnsureTextValidForFont(text, fontnum); +} + +int get_font_scaling_mul(size_t fontNumber) +{ + if (fontNumber >= fonts.size() || !fonts[fontNumber].Renderer) + return 0; + return fonts[fontNumber].Info.SizeMultiplier; +} + +int wgettextwidth(const char *texx, size_t fontNumber) +{ + if (fontNumber >= fonts.size() || !fonts[fontNumber].Renderer) + return 0; + return fonts[fontNumber].Renderer->GetTextWidth(texx, fontNumber); +} + +int wgettextheight(const char *text, size_t fontNumber) +{ + if (fontNumber >= fonts.size() || !fonts[fontNumber].Renderer) + return 0; + return fonts[fontNumber].Renderer->GetTextHeight(text, fontNumber); +} + +int get_font_outline(size_t font_number) +{ + if (font_number >= fonts.size()) + return FONT_OUTLINE_NONE; + return fonts[font_number].Info.Outline; +} + +int get_font_outline_thickness(size_t font_number) +{ + if (font_number >= fonts.size()) + return 0; + return fonts[font_number].Info.AutoOutlineThickness; +} + +void set_font_outline(size_t font_number, int outline_type) +{ + if (font_number >= fonts.size()) + return; + fonts[font_number].Info.Outline = FONT_OUTLINE_AUTO; +} + +int getfontheight(size_t fontNumber) +{ + if (fontNumber >= fonts.size() || !fonts[fontNumber].Renderer) + return 0; + // There is no explicit method for getting maximal possible height of any + // random font renderer at the moment; the implementations of GetTextHeight + // are allowed to return varied results depending on the text parameter. + // We use special line of text to get more or less reliable font height. + const char *height_test_string = "ZHwypgfjqhkilIK"; + return fonts[fontNumber].Renderer->GetTextHeight(height_test_string, fontNumber); +} + +int getfontlinespacing(size_t fontNumber) +{ + if (fontNumber >= fonts.size()) + return 0; + int spacing = fonts[fontNumber].Info.LineSpacing; + // If the spacing parameter is not provided, then return default + // spacing, that is font's height. + return spacing > 0 ? spacing : getfontheight(fontNumber); +} + +bool use_default_linespacing(size_t fontNumber) +{ + if (fontNumber >= fonts.size()) + return false; + return fonts[fontNumber].Info.LineSpacing == 0; +} + +// Project-dependent implementation +extern int wgettextwidth_compensate(const char *tex, int font); + +namespace AGS { namespace Common { SplitLines Lines; } } + +// Replaces AGS-specific linebreak tags with common '\n' +void unescape_script_string(const char *cstr, std::vector &out) +{ + out.clear(); + // Handle the special case of the first char + if (cstr[0] == '[') + { + out.push_back('\n'); + cstr++; + } + // Replace all other occurrences as they're found + const char *off; + for (off = cstr; *off; ++off) + { + if (*off != '[') continue; + if (*(off - 1) == '\\') + { + // convert \[ into [ + out.insert(out.end(), cstr, off - 1); + out.push_back('['); + } + else + { + // convert [ into \n + out.insert(out.end(), cstr, off); + out.push_back('\n'); + } + cstr = off + 1; + } + out.insert(out.end(), cstr, off + 1); +} + +// Break up the text into lines +size_t split_lines(const char *todis, SplitLines &lines, int wii, int fonnt, size_t max_lines) { + // NOTE: following hack accomodates for the legacy math mistake in split_lines. + // It's hard to tell how cruicial it is for the game looks, so research may be needed. + // TODO: IMHO this should rely not on game format, but script API level, because it + // defines necessary adjustments to game scripts. If you want to fix this, find a way to + // pass this flag here all the way from game.options[OPT_BASESCRIPTAPI] (or game format). + // + // if (game.options[OPT_BASESCRIPTAPI] < $Your current version$) + wii -= 1; + + lines.Reset(); + unescape_script_string(todis, lines.LineBuf); + char *theline = &lines.LineBuf.front(); + + size_t i = 0; + size_t splitAt; + char nextCharWas; + while (1) { + splitAt = -1; + + if (theline[i] == 0) { + // end of the text, add the last line if necessary + if (i > 0) { + lines.Add(theline); + } + break; + } + + // temporarily terminate the line here and test its width + nextCharWas = theline[i + 1]; + theline[i + 1] = 0; + + // force end of line with the \n character + if (theline[i] == '\n') + splitAt = i; + // otherwise, see if we are too wide + else if (wgettextwidth_compensate(theline, fonnt) > wii) { + int endline = i; + while ((theline[endline] != ' ') && (endline > 0)) + endline--; + + // single very wide word, display as much as possible + if (endline == 0) + endline = i - 1; + + splitAt = endline; + } + + // restore the character that was there before + theline[i + 1] = nextCharWas; + + if (splitAt != -1) { + if (splitAt == 0 && !((theline[0] == ' ') || (theline[0] == '\n'))) { + // cannot split with current width restriction + lines.Reset(); + break; + } + // add this line + nextCharWas = theline[splitAt]; + theline[splitAt] = 0; + lines.Add(theline); + theline[splitAt] = nextCharWas; + if (lines.Count() >= max_lines) { + lines[lines.Count() - 1].Append("..."); + break; + } + // the next line starts from here + theline += splitAt; + // skip the space or new line that caused the line break + if ((theline[0] == ' ') || (theline[0] == '\n')) + theline++; + i = -1; + } + + i++; + } + return lines.Count(); +} + +void wouttextxy(Common::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, const char *texx) +{ + if (fontNumber >= fonts.size()) + return; + yyy += fonts[fontNumber].Info.YOffset; + if (yyy > ds->GetClip().Bottom) + return; // each char is clipped but this speeds it up + + if (fonts[fontNumber].Renderer != nullptr) + { + fonts[fontNumber].Renderer->RenderText(texx, fontNumber, (BITMAP*)ds->GetAllegroBitmap(), xxx, yyy, text_color); + } +} + +void set_fontinfo(size_t fontNumber, const FontInfo &finfo) +{ + if (fontNumber < fonts.size() && fonts[fontNumber].Renderer) + fonts[fontNumber].Info = finfo; +} + +// Loads a font from disk +bool wloadfont_size(size_t fontNumber, const FontInfo &font_info) +{ + if (fonts.size() <= fontNumber) + fonts.resize(fontNumber + 1); + else + wfreefont(fontNumber); + FontRenderParams params; + params.SizeMultiplier = font_info.SizeMultiplier; + + if (ttfRenderer.LoadFromDiskEx(fontNumber, font_info.SizePt, ¶ms)) + { + fonts[fontNumber].Renderer = &ttfRenderer; + fonts[fontNumber].Renderer2 = &ttfRenderer; + } + else if (wfnRenderer.LoadFromDiskEx(fontNumber, font_info.SizePt, ¶ms)) + { + fonts[fontNumber].Renderer = &wfnRenderer; + fonts[fontNumber].Renderer2 = &wfnRenderer; + } + + if (fonts[fontNumber].Renderer) + { + fonts[fontNumber].Info = font_info; + return true; + } + return false; +} + +void wgtprintf(Common::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, char *fmt, ...) +{ + if (fontNumber >= fonts.size()) + return; + + char tbuffer[2000]; + va_list ap; + + va_start(ap, fmt); + vsprintf(tbuffer, fmt, ap); + va_end(ap); + wouttextxy(ds, xxx, yyy, fontNumber, text_color, tbuffer); +} + +void wfreefont(size_t fontNumber) +{ + if (fontNumber >= fonts.size()) + return; + + if (fonts[fontNumber].Renderer != nullptr) + fonts[fontNumber].Renderer->FreeMemory(fontNumber); + + fonts[fontNumber].Renderer = nullptr; +} + +void free_all_fonts() +{ + for (size_t i = 0; i < fonts.size(); ++i) + { + if (fonts[i].Renderer != nullptr) + fonts[i].Renderer->FreeMemory(i); + } + fonts.clear(); +} diff --git a/engines/ags/shared/font/fonts.h b/engines/ags/shared/font/fonts.h new file mode 100644 index 00000000000..f9004c980b4 --- /dev/null +++ b/engines/ags/shared/font/fonts.h @@ -0,0 +1,112 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_FONT_H +#define __AC_FONT_H + +#include +#include "core/types.h" +#include "util/string.h" + +// TODO: we need to make some kind of TextManager class of this module + +namespace AGS { namespace Common { class Bitmap; } } +using namespace AGS; + +class IAGSFontRenderer; +class IAGSFontRenderer2; +struct FontInfo; +struct FontRenderParams; + +void init_font_renderer(); +void shutdown_font_renderer(); +void adjust_y_coordinate_for_text(int* ypos, size_t fontnum); +IAGSFontRenderer* font_replace_renderer(size_t fontNumber, IAGSFontRenderer* renderer); +bool font_first_renderer_loaded(); +bool is_font_loaded(size_t fontNumber); +bool is_bitmap_font(size_t fontNumber); +bool font_supports_extended_characters(size_t fontNumber); +// TODO: with changes to WFN font renderer that implemented safe rendering of +// strings containing invalid chars (since 3.3.1) this function is not +// important, except for (maybe) few particular cases. +// Furthermore, its use complicated things, because AGS could modify some texts +// at random times (usually - drawing routines). +// Need to check whether it is safe to completely remove it. +void ensure_text_valid_for_font(char *text, size_t fontnum); +// Get font's scaling multiplier +int get_font_scaling_mul(size_t fontNumber); +// Calculate actual width of a line of text +int wgettextwidth(const char *texx, size_t fontNumber); +// Calculates actual height of a line of text +int wgettextheight(const char *text, size_t fontNumber); +// Get font's height (maximal height of any line of text printed with this font) +int getfontheight(size_t fontNumber); +// Get font's line spacing +int getfontlinespacing(size_t fontNumber); +// Get is font is meant to use default line spacing +bool use_default_linespacing(size_t fontNumber); +// Get font's outline type +int get_font_outline(size_t font_number); +// Get font's automatic outline thickness (if set) +int get_font_outline_thickness(size_t font_number); +// Set font's outline type +void set_font_outline(size_t font_number, int outline_type); +// Outputs a single line of text on the defined position on bitmap, using defined font, color and parameters +int getfontlinespacing(size_t fontNumber); +// Print text on a surface using a given font +void wouttextxy(Common::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, const char *texx); +// Assigns FontInfo to the font +void set_fontinfo(size_t fontNumber, const FontInfo &finfo); +// Loads a font from disk +bool wloadfont_size(size_t fontNumber, const FontInfo &font_info); +void wgtprintf(Common::Bitmap *ds, int xxx, int yyy, size_t fontNumber, color_t text_color, char *fmt, ...); +// Free particular font's data +void wfreefont(size_t fontNumber); +// Free all fonts data +void free_all_fonts(); + +// SplitLines class represents a list of lines and is meant to reduce +// subsequent memory (de)allocations if used often during game loops +// and drawing. For that reason it is not equivalent to std::vector, +// but keeps constructed String buffers intact for most time. +// TODO: implement proper strings pool. +class SplitLines +{ +public: + inline size_t Count() const { return _count; } + inline const Common::String &operator[](size_t i) const { return _pool[i]; } + inline Common::String &operator[](size_t i) { return _pool[i]; } + inline void Clear() { _pool.clear(); _count = 0; } + inline void Reset() { _count = 0; } + inline void Add(const char *cstr) + { + if (_pool.size() == _count) _pool.resize(_count + 1); + _pool[_count++].SetString(cstr); + } + + // An auxiliary line processing buffer + std::vector LineBuf; + +private: + std::vector _pool; + size_t _count; // actual number of lines in use +}; + +// Break up the text into lines restricted by the given width; +// returns number of lines, or 0 if text cannot be split well to fit in this width +size_t split_lines(const char *texx, SplitLines &lines, int width, int fontNumber, size_t max_lines = -1); + +namespace AGS { namespace Common { extern SplitLines Lines; } } + +#endif // __AC_FONT_H diff --git a/engines/ags/shared/font/ttffontrenderer.cpp b/engines/ags/shared/font/ttffontrenderer.cpp new file mode 100644 index 00000000000..61a7ded323b --- /dev/null +++ b/engines/ags/shared/font/ttffontrenderer.cpp @@ -0,0 +1,142 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "core/platform.h" + +#define AGS_OUTLINE_FONT_FIX (!AGS_PLATFORM_OS_WINDOWS) + +#include "core/assetmanager.h" +#include "font/ttffontrenderer.h" +#include "util/stream.h" + +#if AGS_OUTLINE_FONT_FIX // TODO: factor out the hack in LoadFromDiskEx +#include "ac/gamestructdefines.h" +#include "font/fonts.h" +#endif + +using namespace AGS::Common; + +// project-specific implementation +extern bool ShouldAntiAliasText(); + +ALFONT_FONT *tempttffnt; +ALFONT_FONT *get_ttf_block(unsigned char* fontptr) +{ + memcpy(&tempttffnt, &fontptr[4], sizeof(tempttffnt)); + return tempttffnt; +} + + +// ***** TTF RENDERER ***** +void TTFFontRenderer::AdjustYCoordinateForFont(int *ycoord, int fontNumber) +{ + // TTF fonts already have space at the top, so try to remove the gap + // TODO: adding -1 was here before (check the comment above), + // but how universal is this "space at the top"? + // Also, why is this function used only in one case of text rendering? + // Review this after we upgrade the font library. + ycoord[0]--; +} + +void TTFFontRenderer::EnsureTextValidForFont(char *text, int fontNumber) +{ + // do nothing, TTF can handle all characters +} + +int TTFFontRenderer::GetTextWidth(const char *text, int fontNumber) +{ + return alfont_text_length(_fontData[fontNumber].AlFont, text); +} + +int TTFFontRenderer::GetTextHeight(const char *text, int fontNumber) +{ + return alfont_text_height(_fontData[fontNumber].AlFont); +} + +void TTFFontRenderer::RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) +{ + if (y > destination->cb) // optimisation + return; + + // Y - 1 because it seems to get drawn down a bit + if ((ShouldAntiAliasText()) && (bitmap_color_depth(destination) > 8)) + alfont_textout_aa(destination, _fontData[fontNumber].AlFont, text, x, y - 1, colour); + else + alfont_textout(destination, _fontData[fontNumber].AlFont, text, x, y - 1, colour); +} + +bool TTFFontRenderer::LoadFromDisk(int fontNumber, int fontSize) +{ + return LoadFromDiskEx(fontNumber, fontSize, nullptr); +} + +bool TTFFontRenderer::IsBitmapFont() +{ + return false; +} + +bool TTFFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize, const FontRenderParams *params) +{ + String file_name = String::FromFormat("agsfnt%d.ttf", fontNumber); + Stream *reader = AssetManager::OpenAsset(file_name); + char *membuffer; + + if (reader == nullptr) + return false; + + long lenof = AssetManager::GetLastAssetSize(); + + membuffer = (char *)malloc(lenof); + reader->ReadArray(membuffer, lenof, 1); + delete reader; + + ALFONT_FONT *alfptr = alfont_load_font_from_mem(membuffer, lenof); + free(membuffer); + + if (alfptr == nullptr) + return false; + + // TODO: move this somewhere, should not be right here +#if AGS_OUTLINE_FONT_FIX + // FIXME: (!!!) this fix should be done differently: + // 1. Find out which OUTLINE font was causing troubles; + // 2. Replace outline method ONLY if that troublesome font is used as outline. + // 3. Move this fix somewhere else!! (right after game load routine?) + // + // Check for the LucasFan font since it comes with an outline font that + // is drawn incorrectly with Freetype versions > 2.1.3. + // A simple workaround is to disable outline fonts for it and use + // automatic outline drawing. + if (get_font_outline(fontNumber) >=0 && + strcmp(alfont_get_name(alfptr), "LucasFan-Font") == 0) + set_font_outline(fontNumber, FONT_OUTLINE_AUTO); +#endif + if (fontSize == 0) + fontSize = 8; // compatibility fix + if (params && params->SizeMultiplier > 1) + fontSize *= params->SizeMultiplier; + if (fontSize > 0) + alfont_set_font_size(alfptr, fontSize); + + _fontData[fontNumber].AlFont = alfptr; + _fontData[fontNumber].Params = params ? *params : FontRenderParams(); + return true; +} + +void TTFFontRenderer::FreeMemory(int fontNumber) +{ + alfont_destroy_font(_fontData[fontNumber].AlFont); + _fontData.erase(fontNumber); +} diff --git a/engines/ags/shared/font/ttffontrenderer.h b/engines/ags/shared/font/ttffontrenderer.h new file mode 100644 index 00000000000..156c46d9bad --- /dev/null +++ b/engines/ags/shared/font/ttffontrenderer.h @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_TTFFONTRENDERER_H +#define __AC_TTFFONTRENDERER_H + +#include +#include "font/agsfontrenderer.h" + +struct ALFONT_FONT; + +class TTFFontRenderer : public IAGSFontRenderer, public IAGSFontRenderer2 { +public: + // IAGSFontRenderer implementation + bool LoadFromDisk(int fontNumber, int fontSize) override; + void FreeMemory(int fontNumber) override; + bool SupportsExtendedCharacters(int fontNumber) override { return true; } + int GetTextWidth(const char *text, int fontNumber) override; + int GetTextHeight(const char *text, int fontNumber) override; + void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) override ; + void AdjustYCoordinateForFont(int *ycoord, int fontNumber) override; + void EnsureTextValidForFont(char *text, int fontNumber) override; + + // IAGSFontRenderer2 implementation + bool IsBitmapFont() override; + bool LoadFromDiskEx(int fontNumber, int fontSize, const FontRenderParams *params) override; + +private: + struct FontData + { + ALFONT_FONT *AlFont; + FontRenderParams Params; + }; + std::map _fontData; +}; + +#endif // __AC_TTFFONTRENDERER_H diff --git a/engines/ags/shared/font/wfnfont.cpp b/engines/ags/shared/font/wfnfont.cpp new file mode 100644 index 00000000000..8f429f29ad8 --- /dev/null +++ b/engines/ags/shared/font/wfnfont.cpp @@ -0,0 +1,193 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "font/wfnfont.h" +#include "debug/out.h" +#include "util/memory.h" +#include "util/stream.h" + +using namespace AGS::Common; + +static const char *WFN_FILE_SIGNATURE = "WGT Font File "; +static const size_t WFN_FILE_SIG_LENGTH = 15; +static const size_t MinCharDataSize = sizeof(uint16_t) * 2; + +WFNChar::WFNChar() + : Width(0) + , Height(0) + , Data(nullptr) +{ +} + +void WFNChar::RestrictToBytes(size_t bytes) +{ + if (bytes < GetRequiredPixelSize()) + Height = static_cast(bytes / GetRowByteCount()); +} + +const WFNChar WFNFont::_emptyChar; + +void WFNFont::Clear() +{ + _refs.clear(); + _items.clear(); + _pixelData.clear(); +} + +WFNError WFNFont::ReadFromFile(Stream *in, const soff_t data_size) +{ + Clear(); + + const soff_t used_data_size = data_size > 0 ? data_size : in->GetLength(); + + // Read font header + char sig[WFN_FILE_SIG_LENGTH]; + in->Read(sig, WFN_FILE_SIG_LENGTH); + if (strncmp(sig, WFN_FILE_SIGNATURE, WFN_FILE_SIG_LENGTH) != 0) + { + Debug::Printf(kDbgMsg_Error, "\tWFN: bad format signature"); + return kWFNErr_BadSignature; // bad format + } + + const soff_t table_addr = static_cast(in->ReadInt16()); // offset table relative address + if (table_addr < WFN_FILE_SIG_LENGTH + sizeof(uint16_t) || table_addr >= used_data_size) + { + Debug::Printf(kDbgMsg_Error, "\tWFN: bad table address: %lld (%d - %d)", table_addr, WFN_FILE_SIG_LENGTH + sizeof(uint16_t), used_data_size); + return kWFNErr_BadTableAddress; // bad table address + } + + const soff_t offset_table_size = used_data_size - table_addr; + const soff_t raw_data_offset = WFN_FILE_SIG_LENGTH + sizeof(uint16_t); + const size_t total_char_data = static_cast(table_addr - raw_data_offset); + const size_t char_count = static_cast(offset_table_size / sizeof(uint16_t)); + + // We process character data in three steps: + // 1. For every character store offset of character item, excluding + // duplicates. + // 2. Allocate memory for character items and pixel array and copy + // appropriate data; test for possible format corruption. + // 3. Create array of references from characters to items; same item may be + // referenced by many characters. + WFNError err = kWFNErr_NoError; + + // Read character data array + uint8_t *raw_data = new uint8_t[total_char_data]; + in->Read(raw_data, total_char_data); + + // Read offset table + uint16_t *offset_table = new uint16_t[char_count]; + in->ReadArrayOfInt16((int16_t*)offset_table, char_count); + + // Read all referenced offsets in an unsorted vector + std::vector offs; + offs.reserve(char_count); // reserve max possible offsets + for (size_t i = 0; i < char_count; ++i) + { + const uint16_t off = offset_table[i]; + if (off < raw_data_offset || off + MinCharDataSize > table_addr) + { + Debug::Printf("\tWFN: character %d -- bad item offset: %d (%d - %d, +%d)", + i, off, raw_data_offset, table_addr, MinCharDataSize); + err = kWFNErr_HasBadCharacters; // warn about potentially corrupt format + continue; // bad character offset + } + offs.push_back(off); + } + // sort offsets vector and remove any duplicates + std::sort(offs.begin(), offs.end()); + std::vector(offs.begin(), std::unique(offs.begin(), offs.end())).swap(offs); + + // Now that we know number of valid character items, parse and store character data + WFNChar init_ch; + _items.resize(offs.size()); + size_t total_pixel_size = 0; + for (size_t i = 0; i < _items.size(); ++i) + { + const uint8_t *p_data = raw_data + offs[i] - raw_data_offset; + init_ch.Width = Memory::ReadInt16LE(p_data); + init_ch.Height = Memory::ReadInt16LE(p_data + sizeof(uint16_t)); + total_pixel_size += init_ch.GetRequiredPixelSize(); + _items[i] = init_ch; + } + + // Now that we know actual size of pixels in use, create pixel data array; + // since the items are sorted, the pixel data will be stored sequentially as well. + // At this point offs and _items have related elements in the same order. + _pixelData.resize(total_pixel_size); + std::vector::iterator pixel_it = _pixelData.begin(); // write ptr + for (size_t i = 0; i < _items.size(); ++i) + { + const size_t pixel_data_size = _items[i].GetRequiredPixelSize(); + if (pixel_data_size == 0) + { + Debug::Printf("\tWFN: item at off %d -- null size", offs[i]); + err = kWFNErr_HasBadCharacters; + continue; // just an empty character + } + const uint16_t raw_off = offs[i] - raw_data_offset + MinCharDataSize; // offset in raw array + size_t src_size = pixel_data_size; + if (i + 1 != _items.size() && raw_off + src_size > offs[i + 1] - raw_data_offset) + { // character pixel data overlaps next character + Debug::Printf("\tWFN: item at off %d -- pixel data overlaps next known item (at %d, +%d)", + offs[i], offs[i + 1], MinCharDataSize + src_size); + err = kWFNErr_HasBadCharacters; // warn about potentially corrupt format + src_size = offs[i + 1] - offs[i] - MinCharDataSize; + } + + if (raw_off + src_size > total_char_data) + { // character pixel data overflow buffer + Debug::Printf("\tWFN: item at off %d -- pixel data exceeds available data (at %d, +%d)", + offs[i], table_addr, MinCharDataSize + src_size); + err = kWFNErr_HasBadCharacters; // warn about potentially corrupt format + src_size = total_char_data - raw_off; + } + _items[i].RestrictToBytes(src_size); + + assert(pixel_it + pixel_data_size <= _pixelData.end()); // should not normally fail + std::copy(raw_data + raw_off, raw_data + raw_off + src_size, pixel_it); + _items[i].Data = &(*pixel_it); + pixel_it += pixel_data_size; + } + + // Create final reference array + _refs.resize(char_count); + for (size_t i = 0; i < char_count; ++i) + { + const uint16_t off = offset_table[i]; + // if bad character offset - reference empty character + if (off < raw_data_offset || off + MinCharDataSize > table_addr) + { + _refs[i] = &_emptyChar; + } + else + { + // in usual case the offset table references items in strict order + if (i < _items.size() && offs[i] == off) + _refs[i] = &_items[i]; + else + { + // we know beforehand that such item must exist + std::vector::const_iterator at = std::lower_bound(offs.begin(), offs.end(), off); + assert(at != offs.end() && *at == off && // should not normally fail + at - offs.begin() >= 0 && static_cast(at - offs.begin()) < _items.size()); + _refs[i] = &_items[at - offs.begin()]; // set up reference to item + } + } + } + + delete [] raw_data; + delete [] offset_table; + return err; +} diff --git a/engines/ags/shared/font/wfnfont.h b/engines/ags/shared/font/wfnfont.h new file mode 100644 index 00000000000..7b7099957ff --- /dev/null +++ b/engines/ags/shared/font/wfnfont.h @@ -0,0 +1,102 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// WFNFont - an immutable AGS font object. +// +//----------------------------------------------------------------------------- +// +// WFN format: +// - signature ( 15 ) +// - offsets table offset ( 2 ) +// - characters table (for unknown number of char items): +// - width ( 2 ) +// - height ( 2 ) +// - pixel bits ( (width / 8 + 1) * height ) +// - any unknown data +// - offsets table (for X chars): +// - character offset ( 2 ) +// +// NOTE: unfortunately, at the moment the format does not provide means to +// know the number of supported characters for certain, and the size of the +// data (file) is used to determine that. +// +//============================================================================= + +#ifndef __AGS_CN_FONT__WFNFONT_H +#define __AGS_CN_FONT__WFNFONT_H + +#include +#include "core/types.h" + +namespace AGS { namespace Common { class Stream; } } + +enum WFNError +{ + kWFNErr_NoError, + kWFNErr_BadSignature, + kWFNErr_BadTableAddress, + kWFNErr_HasBadCharacters +}; + +struct WFNChar +{ + uint16_t Width; + uint16_t Height; + const uint8_t *Data; + + WFNChar(); + + inline size_t GetRowByteCount() const + { + return (Width + 7) / 8; + } + + inline size_t GetRequiredPixelSize() const + { + return GetRowByteCount() * Height; + } + + // Ensure character's width & height fit in given number of pixel bytes + void RestrictToBytes(size_t bytes); +}; + + +class WFNFont +{ +public: + inline uint16_t GetCharCount() const + { + return static_cast(_refs.size()); + } + + // Get WFN character for the given code; if the character is missing, returns empty character + inline const WFNChar &GetChar(uint8_t code) const + { + return code < _refs.size() ? *_refs[code] : _emptyChar; + } + + void Clear(); + // Reads WFNFont object, using data_size bytes from stream; if data_size = 0, + // the available stream's length is used instead. Returns error code. + WFNError ReadFromFile(AGS::Common::Stream *in, const soff_t data_size = 0); + +protected: + std::vector _refs; // reference array, contains pointers to elements of _items + std::vector _items; // actual character items + std::vector _pixelData; // pixel data array + + static const WFNChar _emptyChar; // a dummy character to substitute bad symbols +}; + +#endif // __AGS_CN_FONT__WFNFONT_H diff --git a/engines/ags/shared/font/wfnfontrenderer.cpp b/engines/ags/shared/font/wfnfontrenderer.cpp new file mode 100644 index 00000000000..165539f17ea --- /dev/null +++ b/engines/ags/shared/font/wfnfontrenderer.cpp @@ -0,0 +1,179 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" // our_eip +#include "core/assetmanager.h" +#include "debug/out.h" +#include "font/wfnfont.h" +#include "font/wfnfontrenderer.h" +#include "gfx/bitmap.h" +#include "util/stream.h" + +using namespace AGS::Common; + +static unsigned char GetCharCode(unsigned char wanted_code, const WFNFont* font) +{ + return wanted_code < font->GetCharCount() ? wanted_code : '?'; +} + +static int RenderChar(Bitmap *ds, const int at_x, const int at_y, const WFNChar &wfn_char, const int scale, const color_t text_color); + +void WFNFontRenderer::AdjustYCoordinateForFont(int *ycoord, int fontNumber) +{ + // Do nothing +} + +void WFNFontRenderer::EnsureTextValidForFont(char *text, int fontNumber) +{ + const WFNFont* font = _fontData[fontNumber].Font; + // replace any extended characters with question marks + for (; *text; ++text) + { + if ((unsigned char)*text >= font->GetCharCount()) + { + *text = '?'; + } + } +} + +int WFNFontRenderer::GetTextWidth(const char *text, int fontNumber) +{ + const WFNFont* font = _fontData[fontNumber].Font; + const FontRenderParams ¶ms = _fontData[fontNumber].Params; + int text_width = 0; + + for (; *text; ++text) + { + const WFNChar &wfn_char = font->GetChar(GetCharCode(*text, font)); + text_width += wfn_char.Width; + } + return text_width * params.SizeMultiplier; +} + +int WFNFontRenderer::GetTextHeight(const char *text, int fontNumber) +{ + const WFNFont* font = _fontData[fontNumber].Font; + const FontRenderParams ¶ms = _fontData[fontNumber].Params; + int max_height = 0; + + for (; *text; ++text) + { + const WFNChar &wfn_char = font->GetChar(GetCharCode(*text, font)); + const uint16_t height = wfn_char.Height; + if (height > max_height) + max_height = height; + } + return max_height * params.SizeMultiplier; +} + +Bitmap render_wrapper; +void WFNFontRenderer::RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) +{ + int oldeip = get_our_eip(); + set_our_eip(415); + + const WFNFont* font = _fontData[fontNumber].Font; + const FontRenderParams ¶ms = _fontData[fontNumber].Params; + render_wrapper.WrapAllegroBitmap(destination, true); + + for (; *text; ++text) + x += RenderChar(&render_wrapper, x, y, font->GetChar(GetCharCode(*text, font)), params.SizeMultiplier, colour); + + set_our_eip(oldeip); +} + +int RenderChar(Bitmap *ds, const int at_x, const int at_y, const WFNChar &wfn_char, const int scale, const color_t text_color) +{ + const int width = wfn_char.Width; + const int height = wfn_char.Height; + const unsigned char *actdata = wfn_char.Data; + const int bytewid = wfn_char.GetRowByteCount(); + + int x = at_x; + int y = at_y; + for (int h = 0; h < height; ++h) + { + for (int w = 0; w < width; ++w) + { + if (((actdata[h * bytewid + (w / 8)] & (0x80 >> (w % 8))) != 0)) { + if (scale > 1) + { + ds->FillRect(Rect(x + w, y + h, x + w + (scale - 1), + y + h + (scale - 1)), text_color); + } + else + { + ds->PutPixel(x + w, y + h, text_color); + } + } + + x += scale - 1; + } + y += scale - 1; + x = at_x; + } + return width * scale; +} + +bool WFNFontRenderer::LoadFromDisk(int fontNumber, int fontSize) +{ + return LoadFromDiskEx(fontNumber, fontSize, nullptr); +} + +bool WFNFontRenderer::IsBitmapFont() +{ + return true; +} + +bool WFNFontRenderer::LoadFromDiskEx(int fontNumber, int fontSize, const FontRenderParams *params) +{ + String file_name; + Stream *ffi = nullptr; + + file_name.Format("agsfnt%d.wfn", fontNumber); + ffi = AssetManager::OpenAsset(file_name); + if (ffi == nullptr) + { + // actual font not found, try font 0 instead + file_name = "agsfnt0.wfn"; + ffi = AssetManager::OpenAsset(file_name); + if (ffi == nullptr) + return false; + } + + WFNFont *font = new WFNFont(); + WFNError err = font->ReadFromFile(ffi, AssetManager::GetLastAssetSize()); + delete ffi; + if (err == kWFNErr_HasBadCharacters) + Debug::Printf(kDbgMsg_Warn, "WARNING: font '%s' has mistakes in data format, some characters may be displayed incorrectly", file_name.GetCStr()); + else if (err != kWFNErr_NoError) + { + delete font; + return false; + } + _fontData[fontNumber].Font = font; + _fontData[fontNumber].Params = params ? *params : FontRenderParams(); + return true; +} + +void WFNFontRenderer::FreeMemory(int fontNumber) +{ + delete _fontData[fontNumber].Font; + _fontData.erase(fontNumber); +} + +bool WFNFontRenderer::SupportsExtendedCharacters(int fontNumber) +{ + return _fontData[fontNumber].Font->GetCharCount() > 128; +} diff --git a/engines/ags/shared/font/wfnfontrenderer.h b/engines/ags/shared/font/wfnfontrenderer.h new file mode 100644 index 00000000000..740c15d8958 --- /dev/null +++ b/engines/ags/shared/font/wfnfontrenderer.h @@ -0,0 +1,46 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_WFNFONTRENDERER_H +#define __AC_WFNFONTRENDERER_H + +#include +#include "font/agsfontrenderer.h" + +class WFNFont; + +class WFNFontRenderer : public IAGSFontRenderer, public IAGSFontRenderer2 { +public: + bool LoadFromDisk(int fontNumber, int fontSize) override; + void FreeMemory(int fontNumber) override; + bool SupportsExtendedCharacters(int fontNumber) override; + int GetTextWidth(const char *text, int fontNumber) override; + int GetTextHeight(const char *text, int fontNumber) override; + void RenderText(const char *text, int fontNumber, BITMAP *destination, int x, int y, int colour) override; + void AdjustYCoordinateForFont(int *ycoord, int fontNumber) override; + void EnsureTextValidForFont(char *text, int fontNumber) override; + + bool IsBitmapFont() override; + bool LoadFromDiskEx(int fontNumber, int fontSize, const FontRenderParams *params) override; + +private: + struct FontData + { + WFNFont *Font; + FontRenderParams Params; + }; + std::map _fontData; +}; + +#endif // __AC_WFNFONTRENDERER_H diff --git a/engines/ags/shared/game/customproperties.cpp b/engines/ags/shared/game/customproperties.cpp new file mode 100644 index 00000000000..fd39cc36df0 --- /dev/null +++ b/engines/ags/shared/game/customproperties.cpp @@ -0,0 +1,136 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "game/customproperties.h" +#include "util/stream.h" +#include "util/string_utils.h" + +namespace AGS +{ +namespace Common +{ + +PropertyDesc::PropertyDesc() +{ + Type = kPropertyBoolean; +} + +PropertyDesc::PropertyDesc(const String &name, PropertyType type, const String &desc, const String &def_value) +{ + Name = name; + Type = type; + Description = desc; + DefaultValue = def_value; +} + + +namespace Properties +{ + +PropertyError ReadSchema(PropertySchema &schema, Stream *in) +{ + PropertyVersion version = (PropertyVersion)in->ReadInt32(); + if (version < kPropertyVersion_Initial || + version > kPropertyVersion_Current) + { + return kPropertyErr_UnsupportedFormat; + } + + PropertyDesc prop; + int count = in->ReadInt32(); + if (version == kPropertyVersion_Initial) + { + for (int i = 0; i < count; ++i) + { + prop.Name.Read(in, LEGACY_MAX_CUSTOM_PROP_SCHEMA_NAME_LENGTH); + prop.Description.Read(in, LEGACY_MAX_CUSTOM_PROP_DESC_LENGTH); + prop.DefaultValue.Read(in, LEGACY_MAX_CUSTOM_PROP_VALUE_LENGTH); + prop.Type = (PropertyType)in->ReadInt32(); + schema[prop.Name] = prop; + } + } + else + { + for (int i = 0; i < count; ++i) + { + prop.Name = StrUtil::ReadString(in); + prop.Type = (PropertyType)in->ReadInt32(); + prop.Description = StrUtil::ReadString(in); + prop.DefaultValue = StrUtil::ReadString(in); + schema[prop.Name] = prop; + } + } + return kPropertyErr_NoError; +} + +void WriteSchema(const PropertySchema &schema, Stream *out) +{ + out->WriteInt32(kPropertyVersion_Current); + out->WriteInt32(schema.size()); + for (PropertySchema::const_iterator it = schema.begin(); + it != schema.end(); ++it) + { + const PropertyDesc &prop = it->second; + StrUtil::WriteString(prop.Name, out); + out->WriteInt32(prop.Type); + StrUtil::WriteString(prop.Description, out); + StrUtil::WriteString(prop.DefaultValue, out); + } +} + +PropertyError ReadValues(StringIMap &map, Stream *in) +{ + PropertyVersion version = (PropertyVersion)in->ReadInt32(); + if (version < kPropertyVersion_Initial || + version > kPropertyVersion_Current) + { + return kPropertyErr_UnsupportedFormat; + } + + int count = in->ReadInt32(); + if (version == kPropertyVersion_Initial) + { + for (int i = 0; i < count; ++i) + { + String name = String::FromStream(in, LEGACY_MAX_CUSTOM_PROP_NAME_LENGTH); + map[name] = String::FromStream(in, LEGACY_MAX_CUSTOM_PROP_VALUE_LENGTH); + } + } + else + { + for (int i = 0; i < count; ++i) + { + String name = StrUtil::ReadString(in); + map[name] = StrUtil::ReadString(in); + } + } + return kPropertyErr_NoError; +} + +void WriteValues(const StringIMap &map, Stream *out) +{ + out->WriteInt32(kPropertyVersion_Current); + out->WriteInt32(map.size()); + for (StringIMap::const_iterator it = map.begin(); + it != map.end(); ++it) + { + StrUtil::WriteString(it->first, out); + StrUtil::WriteString(it->second, out); + } +} + +} // namespace Properties + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/game/customproperties.h b/engines/ags/shared/game/customproperties.h new file mode 100644 index 00000000000..afd819cd855 --- /dev/null +++ b/engines/ags/shared/game/customproperties.h @@ -0,0 +1,102 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Custom property structs +// +//----------------------------------------------------------------------------- +// +// Custom property schema is kept by GameSetupStruct object as a single +// instance and defines property type and default value. Every game entity that +// has properties implemented keeps CustomProperties object, which stores +// actual property values only if ones are different from defaults. +// +//============================================================================= +#ifndef __AGS_CN_GAME__CUSTOMPROPERTIES_H +#define __AGS_CN_GAME__CUSTOMPROPERTIES_H + +#include +#include "util/string.h" +#include "util/string_types.h" + +#define LEGACY_MAX_CUSTOM_PROPERTIES 30 +// NOTE: for some reason the property name stored in schema object was limited +// to only 20 characters, while the custom properties map could hold up to 200. +// Whether this was an error or design choice is unknown. +#define LEGACY_MAX_CUSTOM_PROP_SCHEMA_NAME_LENGTH 20 +#define LEGACY_MAX_CUSTOM_PROP_NAME_LENGTH 200 +#define LEGACY_MAX_CUSTOM_PROP_DESC_LENGTH 100 +#define LEGACY_MAX_CUSTOM_PROP_VALUE_LENGTH 500 + +namespace AGS +{ +namespace Common +{ + +enum PropertyVersion +{ + kPropertyVersion_Initial = 1, + kPropertyVersion_340, + kPropertyVersion_Current = kPropertyVersion_340 +}; + +enum PropertyType +{ + kPropertyUndefined = 0, + kPropertyBoolean, + kPropertyInteger, + kPropertyString +}; + +enum PropertyError +{ + kPropertyErr_NoError, + kPropertyErr_UnsupportedFormat +}; + +// +// PropertyDesc - a description of a single custom property +// +struct PropertyDesc +{ + String Name; + PropertyType Type; + String Description; + String DefaultValue; + + PropertyDesc(); + PropertyDesc(const String &name, PropertyType type, const String &desc, const String &def_value); +}; + +// NOTE: AGS has case-insensitive property IDs +// Schema - a map of property descriptions +typedef std::unordered_map PropertySchema; + + +namespace Properties +{ + PropertyError ReadSchema(PropertySchema &schema, Stream *in); + void WriteSchema(const PropertySchema &schema, Stream *out); + + // Reads property values from the stream and assign them to map. + // The non-matching existing map items, if any, are NOT erased. + PropertyError ReadValues(StringIMap &map, Stream *in); + // Writes property values chunk to the stream + void WriteValues(const StringIMap &map, Stream *out); + +} // namespace Properties + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_GAME__CUSTOMPROPERTIES_H diff --git a/engines/ags/shared/game/interactions.cpp b/engines/ags/shared/game/interactions.cpp new file mode 100644 index 00000000000..0786c805b0c --- /dev/null +++ b/engines/ags/shared/game/interactions.cpp @@ -0,0 +1,429 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/common.h" // quit +#include "game/interactions.h" +#include "util/alignedstream.h" +#include "util/math.h" + +using namespace AGS::Common; + +InteractionVariable globalvars[MAX_GLOBAL_VARIABLES] = {InteractionVariable("Global 1", 0, 0)}; +int numGlobalVars = 1; + +namespace AGS +{ +namespace Common +{ + +InteractionValue::InteractionValue() +{ + Type = kInterValLiteralInt; + Value = 0; + Extra = 0; +} + +void InteractionValue::Read(Stream *in) +{ + Type = (InterValType)in->ReadInt8(); + Value = in->ReadInt32(); + Extra = in->ReadInt32(); +} + +void InteractionValue::Write(Stream *out) const +{ + out->WriteInt8(Type); + out->WriteInt32(Value); + out->WriteInt32(Extra); +} + +//----------------------------------------------------------------------------- + +InteractionCommand::InteractionCommand() + : Type(0) + , Parent(nullptr) +{ +} + +InteractionCommand::InteractionCommand(const InteractionCommand &ic) +{ + *this = ic; +} + +void InteractionCommand::Assign(const InteractionCommand &ic, InteractionCommandList *parent) +{ + Type = ic.Type; + memcpy(Data, ic.Data, sizeof(Data)); + Children.reset(ic.Children.get() ? new InteractionCommandList(*ic.Children) : nullptr); + Parent = parent; +} + +void InteractionCommand::Reset() +{ + Type = 0; + memset(Data, 0, sizeof(Data)); + Children.reset(); + Parent = nullptr; +} + +void InteractionCommand::ReadValues_Aligned(Stream *in) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (int i = 0; i < MAX_ACTION_ARGS; ++i) + { + Data[i].Read(&align_s); + align_s.Reset(); + } +} + +void InteractionCommand::Read_v321(Stream *in, bool &has_children) +{ + in->ReadInt32(); // skip the 32-bit vtbl ptr (the old serialization peculiarity) + Type = in->ReadInt32(); + ReadValues_Aligned(in); + has_children = in->ReadInt32() != 0; + in->ReadInt32(); // skip 32-bit Parent pointer +} + +void InteractionCommand::WriteValues_Aligned(Stream *out) const +{ + AlignedStream align_s(out, Common::kAligned_Write); + for (int i = 0; i < MAX_ACTION_ARGS; ++i) + { + Data[i].Write(&align_s); + align_s.Reset(); + } +} + +void InteractionCommand::Write_v321(Stream *out) const +{ + out->WriteInt32(0); // write dummy 32-bit vtbl ptr + out->WriteInt32(Type); + WriteValues_Aligned(out); + out->WriteInt32(Children.get() ? 1 : 0); + out->WriteInt32(Parent ? 1 : 0); +} + +InteractionCommand &InteractionCommand::operator = (const InteractionCommand &ic) +{ + Type = ic.Type; + memcpy(Data, ic.Data, sizeof(Data)); + Children.reset(ic.Children.get() ? new InteractionCommandList(*ic.Children) : nullptr); + Parent = ic.Parent; + return *this; +} + +//----------------------------------------------------------------------------- + +InteractionCommandList::InteractionCommandList() + : TimesRun(0) +{ +} + +InteractionCommandList::InteractionCommandList(const InteractionCommandList &icmd_list) +{ + TimesRun = icmd_list.TimesRun; + Cmds.resize(icmd_list.Cmds.size()); + for (size_t i = 0; i < icmd_list.Cmds.size(); ++i) + { + Cmds[i].Assign(icmd_list.Cmds[i], this); + } +} + +void InteractionCommandList::Reset() +{ + Cmds.clear(); + TimesRun = 0; +} + +void InteractionCommandList::Read_Aligned(Stream *in, std::vector &cmd_children) +{ + AlignedStream align_s(in, Common::kAligned_Read); + for (size_t i = 0; i < Cmds.size(); ++i) + { + bool has_children; + Cmds[i].Read_v321(&align_s, has_children); + cmd_children[i] = has_children; + align_s.Reset(); + } +} + +void InteractionCommandList::Read_v321(Stream *in) +{ + size_t cmd_count = in->ReadInt32(); + TimesRun = in->ReadInt32(); + + std::vector cmd_children; + Cmds.resize(cmd_count); + cmd_children.resize(cmd_count); + Read_Aligned(in, cmd_children); + + for (size_t i = 0; i < cmd_count; ++i) + { + if (cmd_children[i]) + { + Cmds[i].Children.reset(new InteractionCommandList()); + Cmds[i].Children->Read_v321(in); + } + Cmds[i].Parent = this; + } +} + +void InteractionCommandList::Write_Aligned(Stream *out) const +{ + AlignedStream align_s(out, Common::kAligned_Write); + for (InterCmdVector::const_iterator it = Cmds.begin(); it != Cmds.end(); ++it) + { + it->Write_v321(&align_s); + align_s.Reset(); + } +} + +void InteractionCommandList::Write_v321(Stream *out) const +{ + size_t cmd_count = Cmds.size(); + out->WriteInt32(cmd_count); + out->WriteInt32(TimesRun); + + Write_Aligned(out); + + for (size_t i = 0; i < cmd_count; ++i) + { + if (Cmds[i].Children.get() != nullptr) + Cmds[i].Children->Write_v321(out); + } +} + +//----------------------------------------------------------------------------- + +InteractionEvent::InteractionEvent() + : Type(0) + , TimesRun(0) +{ +} + +InteractionEvent::InteractionEvent(const InteractionEvent &ie) +{ + *this = ie; +} + +InteractionEvent &InteractionEvent::operator = (const InteractionEvent &ie) +{ + Type = ie.Type; + TimesRun = ie.TimesRun; + Response.reset(ie.Response.get() ? new InteractionCommandList(*ie.Response) : nullptr); + return *this; +} + +//----------------------------------------------------------------------------- + +Interaction::Interaction() +{ +} + +Interaction::Interaction(const Interaction &ni) +{ + *this = ni; +} + +Interaction &Interaction::operator =(const Interaction &ni) +{ + Events.resize(ni.Events.size()); + for (size_t i = 0; i < ni.Events.size(); ++i) + { + Events[i] = InteractionEvent(ni.Events[i]); + } + return *this; +} + +void Interaction::CopyTimesRun(const Interaction &inter) +{ + assert(Events.size() == inter.Events.size()); + size_t count = Math::Min(Events.size(), inter.Events.size()); + for (size_t i = 0; i < count; ++i) + { + Events[i].TimesRun = inter.Events[i].TimesRun; + } +} + +void Interaction::Reset() +{ + Events.clear(); +} + +Interaction *Interaction::CreateFromStream(Stream *in) +{ + if (in->ReadInt32() != kInteractionVersion_Initial) + return nullptr; // unsupported format + + const size_t evt_count = in->ReadInt32(); + if (evt_count > MAX_NEWINTERACTION_EVENTS) + quit("Can't deserialize interaction: too many events"); + + int types[MAX_NEWINTERACTION_EVENTS]; + int load_response[MAX_NEWINTERACTION_EVENTS]; + in->ReadArrayOfInt32(types, evt_count); + in->ReadArrayOfInt32(load_response, evt_count); + + Interaction *inter = new Interaction(); + inter->Events.resize(evt_count); + for (size_t i = 0; i < evt_count; ++i) + { + InteractionEvent &evt = inter->Events[i]; + evt.Type = types[i]; + if (load_response[i] != 0) + { + evt.Response.reset(new InteractionCommandList()); + evt.Response->Read_v321(in); + } + } + return inter; +} + +void Interaction::Write(Stream *out) const +{ + out->WriteInt32(kInteractionVersion_Initial); // Version + const size_t evt_count = Events.size(); + out->WriteInt32(evt_count); + for (size_t i = 0; i < evt_count; ++i) + { + out->WriteInt32(Events[i].Type); + } + + // The pointer is only checked against NULL to determine whether the event exists + for (size_t i = 0; i < evt_count; ++i) + { + out->WriteInt32 (Events[i].Response.get() ? 1 : 0); + } + + for (size_t i = 0; i < evt_count; ++i) + { + if (Events[i].Response.get()) + Events[i].Response->Write_v321(out); + } +} + +void Interaction::ReadFromSavedgame_v321(Stream *in) +{ + const size_t evt_count = in->ReadInt32(); + if (evt_count > MAX_NEWINTERACTION_EVENTS) + quit("Can't deserialize interaction: too many events"); + + Events.resize(evt_count); + for (size_t i = 0; i < evt_count; ++i) + { + Events[i].Type = in->ReadInt32(); + } + const size_t padding = (MAX_NEWINTERACTION_EVENTS - evt_count); + for (size_t i = 0; i < padding; ++i) + in->ReadInt32(); // cannot skip when reading aligned structs + ReadTimesRunFromSave_v321(in); + + // Skip an array of dummy 32-bit pointers + for (size_t i = 0; i < MAX_NEWINTERACTION_EVENTS; ++i) + in->ReadInt32(); +} + +void Interaction::WriteToSavedgame_v321(Stream *out) const +{ + const size_t evt_count = Events.size(); + out->WriteInt32(evt_count); + + for (size_t i = 0; i < evt_count; ++i) + { + out->WriteInt32(Events[i].Type); + } + out->WriteByteCount(0, (MAX_NEWINTERACTION_EVENTS - evt_count) * sizeof(int32_t)); + WriteTimesRunToSave_v321(out); + + // Array of dummy 32-bit pointers + out->WriteByteCount(0, MAX_NEWINTERACTION_EVENTS * sizeof(int32_t)); +} + +void Interaction::ReadTimesRunFromSave_v321(Stream *in) +{ + const size_t evt_count = Events.size(); + for (size_t i = 0; i < evt_count; ++i) + { + Events[i].TimesRun = in->ReadInt32(); + } + const size_t padding = (MAX_NEWINTERACTION_EVENTS - evt_count); + for (size_t i = 0; i < padding; ++i) + in->ReadInt32(); // cannot skip when reading aligned structs +} + +void Interaction::WriteTimesRunToSave_v321(Stream *out) const +{ + const size_t evt_count = Events.size(); + for (size_t i = 0; i < Events.size(); ++i) + { + out->WriteInt32(Events[i].TimesRun); + } + out->WriteByteCount(0, (MAX_NEWINTERACTION_EVENTS - evt_count) * sizeof(int32_t)); +} + +//----------------------------------------------------------------------------- + +#define INTER_VAR_NAME_LENGTH 23 + +InteractionVariable::InteractionVariable() + : Type(0) + , Value(0) +{ +} + +InteractionVariable::InteractionVariable(const String &name, char type, int val) + : Name(name) + , Type(type) + , Value(val) +{ +} + +void InteractionVariable::Read(Stream *in) +{ + Name.ReadCount(in, INTER_VAR_NAME_LENGTH); + Type = in->ReadInt8(); + Value = in->ReadInt32(); +} + +void InteractionVariable::Write(Common::Stream *out) const +{ + out->Write(Name, INTER_VAR_NAME_LENGTH); + out->WriteInt8(Type); + out->WriteInt32(Value); +} + +//----------------------------------------------------------------------------- + +InteractionScripts *InteractionScripts::CreateFromStream(Stream *in) +{ + const size_t evt_count = in->ReadInt32(); + if (evt_count > MAX_NEWINTERACTION_EVENTS) + { + quit("Can't deserialize interaction scripts: too many events"); + return nullptr; + } + + InteractionScripts *scripts = new InteractionScripts(); + for (size_t i = 0; i < evt_count; ++i) + { + String name = String::FromStream(in); + scripts->ScriptFuncNames.push_back(name); + } + return scripts; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/game/interactions.h b/engines/ags/shared/game/interactions.h new file mode 100644 index 00000000000..92e9320200c --- /dev/null +++ b/engines/ags/shared/game/interactions.h @@ -0,0 +1,212 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Interaction structs. +// +//----------------------------------------------------------------------------- +// +// Most of the interaction types here were used before the script and have +// very limited capabilities. They were removed from AGS completely in +// generation 3.0. The code is left for backwards compatibility. +// +//----------------------------------------------------------------------------- +// +/* THE WAY THIS WORKS: +* +* Interaction (Hotspot 1) +* | +* +-- eventTypes [NUM_EVENTS] +* +-- InteractionCommandList [NUM_EVENTS] (Look at hotspot) +* | +* +-- InteractionCommand [NUM_COMMANDS] (Display message) +* | +* +-- InteractionValue [NUM_ARGUMENTS] (5) +*/ +// +//============================================================================= +#ifndef __AGS_CN_GAME__INTEREACTIONS_H +#define __AGS_CN_GAME__INTEREACTIONS_H + +#include +#include "util/string_types.h" + +#define LOCAL_VARIABLE_OFFSET 10000 +#define MAX_GLOBAL_VARIABLES 100 +#define MAX_ACTION_ARGS 5 +#define MAX_NEWINTERACTION_EVENTS 30 +#define MAX_COMMANDS_PER_LIST 40 + +namespace AGS +{ +namespace Common +{ + +enum InterValType +{ + kInterValLiteralInt = 1, + kInterValVariable = 2, + kInterValBoolean = 3, + kInterValCharnum = 4 +}; + +enum InteractionVersion +{ + kInteractionVersion_Initial = 1 +}; + +// InteractionValue represents an argument of interaction command +struct InteractionValue +{ + InterValType Type; // value type + int Value; // value definition + int Extra; + + InteractionValue(); + + void Read(Stream *in); + void Write(Stream *out) const; +}; + + +struct InteractionCommandList; +typedef std::unique_ptr UInterCmdList; + +// InteractionCommand represents a single command (action), an item of Command List +struct InteractionCommand +{ + int Type; // type of action + InteractionValue Data[MAX_ACTION_ARGS]; // action arguments + UInterCmdList Children; // list of sub-actions + InteractionCommandList *Parent; // action parent (optional) + + InteractionCommand(); + InteractionCommand(const InteractionCommand &ic); + + void Assign(const InteractionCommand &ic, InteractionCommandList *parent); + void Reset(); + + void Read_v321(Stream *in, bool &has_children); + void Write_v321(Stream *out) const; + + InteractionCommand &operator = (const InteractionCommand &ic); + +private: + void ReadValues_Aligned(Stream *in); + void WriteValues_Aligned(Stream *out) const; +}; + + +typedef std::vector InterCmdVector; +// InteractionCommandList represents a list of commands (actions) that need to be +// performed on particular game event +struct InteractionCommandList +{ + InterCmdVector Cmds; // actions to run + int TimesRun; // used by engine to track score changes + + InteractionCommandList(); + InteractionCommandList(const InteractionCommandList &icmd_list); + + void Reset(); + + void Read_v321(Stream *in); + void Write_v321(Stream *out) const; + +protected: + void Read_Aligned(Common::Stream *in, std::vector &cmd_children); + void Write_Aligned(Common::Stream *out) const; +}; + + +// InteractionEvent is a single event with a list of commands to performed +struct InteractionEvent +{ + int Type; // type of event + int TimesRun; // used by engine to track score changes + UInterCmdList Response; // list of commands to run + + InteractionEvent(); + InteractionEvent(const InteractionEvent &ie); + + InteractionEvent &operator = (const InteractionEvent &ic); +}; + +typedef std::vector InterEvtVector; +// Interaction is the list of events and responses for a game or game object +struct Interaction +{ + // The first few event types depend on the item - ID's of 100+ are + // custom events (used for subroutines) + InterEvtVector Events; + + Interaction(); + Interaction(const Interaction &inter); + + // Copy information on number of times events of this interaction were fired + void CopyTimesRun(const Interaction &inter); + void Reset(); + + // Game static data (de)serialization + static Interaction *CreateFromStream(Stream *in); + void Write(Stream *out) const; + + // Reading and writing runtime data from/to savedgame; + // NOTE: these are backwards-compatible methods, that do not always + // have practical sense + void ReadFromSavedgame_v321(Stream *in); + void WriteToSavedgame_v321(Stream *out) const; + void ReadTimesRunFromSave_v321(Stream *in); + void WriteTimesRunToSave_v321(Stream *out) const; + + Interaction &operator =(const Interaction &inter); +}; + +typedef std::shared_ptr PInteraction; + + +// Legacy pre-3.0 kind of global and local room variables +struct InteractionVariable +{ + String Name {}; + char Type {'\0'}; + int Value {0}; + + InteractionVariable(); + InteractionVariable(const String &name, char type, int val); + + void Read(Stream *in); + void Write(Stream *out) const; +}; + +typedef std::vector InterVarVector; + + +// A list of script function names for all supported events +struct InteractionScripts +{ + StringV ScriptFuncNames; + + static InteractionScripts *CreateFromStream(Stream *in); +}; + +typedef std::shared_ptr PInteractionScripts; + +} // namespace Common +} // namespace AGS + +// Legacy global variables +extern AGS::Common::InteractionVariable globalvars[MAX_GLOBAL_VARIABLES]; +extern int numGlobalVars; + +#endif // __AGS_CN_GAME__INTEREACTIONS_H diff --git a/engines/ags/shared/game/main_game_file.cpp b/engines/ags/shared/game/main_game_file.cpp new file mode 100644 index 00000000000..94339a11f70 --- /dev/null +++ b/engines/ags/shared/game/main_game_file.cpp @@ -0,0 +1,831 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/audiocliptype.h" +#include "ac/dialogtopic.h" +#include "ac/gamesetupstruct.h" +#include "ac/spritecache.h" +#include "ac/view.h" +#include "ac/wordsdictionary.h" +#include "ac/dynobj/scriptaudioclip.h" +#include "core/asset.h" +#include "core/assetmanager.h" +#include "game/main_game_file.h" +#include "gui/guimain.h" +#include "script/cc_error.h" +#include "util/alignedstream.h" +#include "util/path.h" +#include "util/string_compat.h" +#include "util/string_utils.h" +#include "font/fonts.h" + +namespace AGS +{ +namespace Common +{ + +const String MainGameSource::DefaultFilename_v3 = "game28.dta"; +const String MainGameSource::DefaultFilename_v2 = "ac2game.dta"; +const String MainGameSource::Signature = "Adventure Creator Game File v2"; + +MainGameSource::MainGameSource() + : DataVersion(kGameVersion_Undefined) +{ +} + +String GetMainGameFileErrorText(MainGameFileErrorType err) +{ + switch (err) + { + case kMGFErr_NoError: + return "No error."; + case kMGFErr_FileOpenFailed: + return "Main game file not found or could not be opened."; + case kMGFErr_SignatureFailed: + return "Not an AGS main game file or unsupported format."; + case kMGFErr_FormatVersionTooOld: + return "Format version is too old; this engine can only run games made with AGS 2.5 or later."; + case kMGFErr_FormatVersionNotSupported: + return "Format version not supported."; + case kMGFErr_CapsNotSupported: + return "The game requires extended capabilities which aren't supported by the engine."; + case kMGFErr_InvalidNativeResolution: + return "Unable to determine native game resolution."; + case kMGFErr_TooManySprites: + return "Too many sprites for this engine to handle."; + case kMGFErr_TooManyCursors: + return "Too many cursors for this engine to handle."; + case kMGFErr_InvalidPropertySchema: + return "Failed to deserialize custom properties schema."; + case kMGFErr_InvalidPropertyValues: + return "Errors encountered when reading custom properties."; + case kMGFErr_NoGlobalScript: + return "No global script in game."; + case kMGFErr_CreateGlobalScriptFailed: + return "Failed to load global script."; + case kMGFErr_CreateDialogScriptFailed: + return "Failed to load dialog script."; + case kMGFErr_CreateScriptModuleFailed: + return "Failed to load script module."; + case kMGFErr_GameEntityFailed: + return "Failed to load one or more game entities."; + case kMGFErr_PluginDataFmtNotSupported: + return "Format version of plugin data is not supported."; + case kMGFErr_PluginDataSizeTooLarge: + return "Plugin data size is too large."; + } + return "Unknown error."; +} + +LoadedGameEntities::LoadedGameEntities(GameSetupStruct &game, DialogTopic *&dialogs, ViewStruct *&views) + : Game(game) + , Dialogs(dialogs) + , Views(views) + , SpriteCount(0) +{ +} + +LoadedGameEntities::~LoadedGameEntities() = default; + +bool IsMainGameLibrary(const String &filename) +{ + // We must not only detect if the given file is a correct AGS data library, + // we also have to assure that this library contains main game asset. + // Library may contain some optional data (digital audio, speech etc), but + // that is not what we want. + AssetLibInfo lib; + if (AssetManager::ReadDataFileTOC(filename, lib) != kAssetNoError) + return false; + for (size_t i = 0; i < lib.AssetInfos.size(); ++i) + { + if (lib.AssetInfos[i].FileName.CompareNoCase(MainGameSource::DefaultFilename_v3) == 0 || + lib.AssetInfos[i].FileName.CompareNoCase(MainGameSource::DefaultFilename_v2) == 0) + { + return true; + } + } + return false; +} + +// Begins reading main game file from a generic stream +HGameFileError OpenMainGameFileBase(PStream &in, MainGameSource &src) +{ + // Check data signature + String data_sig = String::FromStreamCount(in.get(), MainGameSource::Signature.GetLength()); + if (data_sig.Compare(MainGameSource::Signature)) + return new MainGameFileError(kMGFErr_SignatureFailed); + // Read data format version and requested engine version + src.DataVersion = (GameDataVersion)in->ReadInt32(); + if (src.DataVersion >= kGameVersion_230) + src.CompiledWith = StrUtil::ReadString(in.get()); + if (src.DataVersion < kGameVersion_250) + return new MainGameFileError(kMGFErr_FormatVersionTooOld, String::FromFormat("Required format version: %d, supported %d - %d", src.DataVersion, kGameVersion_250, kGameVersion_Current)); + if (src.DataVersion > kGameVersion_Current) + return new MainGameFileError(kMGFErr_FormatVersionNotSupported, + String::FromFormat("Game was compiled with %s. Required format version: %d, supported %d - %d", src.CompiledWith.GetCStr(), src.DataVersion, kGameVersion_250, kGameVersion_Current)); + // Read required capabilities + if (src.DataVersion >= kGameVersion_341) + { + size_t count = in->ReadInt32(); + for (size_t i = 0; i < count; ++i) + src.Caps.insert(StrUtil::ReadString(in.get())); + } + // Everything is fine, return opened stream + src.InputStream = in; + // Remember loaded game data version + // NOTE: this global variable is embedded in the code too much to get + // rid of it too easily; the easy way is to set it whenever the main + // game file is opened. + loaded_game_file_version = src.DataVersion; + return HGameFileError::None(); +} + +HGameFileError OpenMainGameFile(const String &filename, MainGameSource &src) +{ + // Cleanup source struct + src = MainGameSource(); + // Try to open given file + PStream in(File::OpenFileRead(filename)); + if (!in) + return new MainGameFileError(kMGFErr_FileOpenFailed, String::FromFormat("Filename: %s.", filename.GetCStr())); + src.Filename = filename; + return OpenMainGameFileBase(in, src); +} + +HGameFileError OpenMainGameFileFromDefaultAsset(MainGameSource &src) +{ + // Cleanup source struct + src = MainGameSource(); + // Try to find and open main game file + String filename = MainGameSource::DefaultFilename_v3; + PStream in(AssetManager::OpenAsset(filename)); + if (!in) + { + filename = MainGameSource::DefaultFilename_v2; + in = PStream(AssetManager::OpenAsset(filename)); + } + if (!in) + return new MainGameFileError(kMGFErr_FileOpenFailed, String::FromFormat("Filename: %s.", filename.GetCStr())); + src.Filename = filename; + return OpenMainGameFileBase(in, src); +} + +HGameFileError ReadDialogScript(PScript &dialog_script, Stream *in, GameDataVersion data_ver) +{ + if (data_ver > kGameVersion_310) // 3.1.1+ dialog script + { + dialog_script.reset(ccScript::CreateFromStream(in)); + if (dialog_script == nullptr) + return new MainGameFileError(kMGFErr_CreateDialogScriptFailed, ccErrorString); + } + else // 2.x and < 3.1.1 dialog + { + dialog_script.reset(); + } + return HGameFileError::None(); +} + +HGameFileError ReadScriptModules(std::vector &sc_mods, Stream *in, GameDataVersion data_ver) +{ + if (data_ver >= kGameVersion_270) // 2.7.0+ script modules + { + int count = in->ReadInt32(); + sc_mods.resize(count); + for (int i = 0; i < count; ++i) + { + sc_mods[i].reset(ccScript::CreateFromStream(in)); + if (sc_mods[i] == nullptr) + return new MainGameFileError(kMGFErr_CreateScriptModuleFailed, ccErrorString); + } + } + else + { + sc_mods.resize(0); + } + return HGameFileError::None(); +} + +void ReadViewStruct272_Aligned(std::vector &oldv, Stream *in, size_t count) +{ + AlignedStream align_s(in, Common::kAligned_Read); + oldv.resize(count); + for (size_t i = 0; i < count; ++i) + { + oldv[i].ReadFromFile(&align_s); + align_s.Reset(); + } +} + +void ReadViews(GameSetupStruct &game, ViewStruct *&views, Stream *in, GameDataVersion data_ver) +{ + int count = game.numviews; + views = (ViewStruct*)calloc(sizeof(ViewStruct) * count, 1); + if (data_ver > kGameVersion_272) // 3.x views + { + for (int i = 0; i < game.numviews; ++i) + { + views[i].ReadFromFile(in); + } + } + else // 2.x views + { + std::vector oldv; + ReadViewStruct272_Aligned(oldv, in, count); + Convert272ViewsToNew(oldv, views); + } +} + +void ReadDialogs(DialogTopic *&dialog, + std::vector< std::shared_ptr > &old_dialog_scripts, + std::vector &old_dialog_src, + std::vector &old_speech_lines, + Stream *in, GameDataVersion data_ver, int dlg_count) +{ + // TODO: I suspect +5 was a hacky way to "supress" memory access mistakes; + // double check and remove if proved unnecessary + dialog = (DialogTopic*)malloc(sizeof(DialogTopic) * dlg_count + 5); + for (int i = 0; i < dlg_count; ++i) + { + dialog[i].ReadFromFile(in); + } + + if (data_ver > kGameVersion_310) + return; + + old_dialog_scripts.resize(dlg_count); + old_dialog_src.resize(dlg_count); + for (int i = 0; i < dlg_count; ++i) + { + // NOTE: originally this was read into dialog[i].optionscripts + old_dialog_scripts[i].reset(new unsigned char[dialog[i].codesize]); + in->Read(old_dialog_scripts[i].get(), dialog[i].codesize); + + // Encrypted text script + int script_text_len = in->ReadInt32(); + if (script_text_len > 1) + { + // Originally in the Editor +20000 bytes more were allocated, with comment: + // "add a large buffer because it will get added to if another option is added" + // which probably refered to this data used by old editor directly to edit dialogs + char *buffer = new char[script_text_len]; + in->Read(buffer, script_text_len); + if (data_ver > kGameVersion_260) + decrypt_text(buffer); + old_dialog_src[i] = buffer; + delete [] buffer; + } + else + { + in->Seek(script_text_len); + } + } + + // Read the dialog lines + // + // TODO: investigate this: these strings were read much simplier in the editor, see code: + /* + char stringbuffer[1000]; + for (bb=0;bb= 26) && (encrypted)) + read_string_decrypt(iii, stringbuffer); + else + fgetstring(stringbuffer, iii); + } + */ + int i = 0; + char buffer[1000]; + if (data_ver <= kGameVersion_260) + { + // Plain text on <= 2.60 + bool end_reached = false; + + while (!end_reached) + { + char* nextchar = buffer; + + while (1) + { + *nextchar = in->ReadInt8(); + if (*nextchar == 0) + break; + + if ((unsigned char)*nextchar == 0xEF) + { + end_reached = true; + in->Seek(-1); + break; + } + + nextchar++; + } + + if (end_reached) + break; + + old_speech_lines.push_back(buffer); + i++; + } + } + else + { + // Encrypted text on > 2.60 + while (1) + { + size_t newlen = in->ReadInt32(); + if (static_cast(newlen) == 0xCAFEBEEF) // GUI magic + { + in->Seek(-4); + break; + } + + newlen = Math::Min(newlen, sizeof(buffer) - 1); + in->Read(buffer, newlen); + buffer[newlen] = 0; + decrypt_text(buffer); + old_speech_lines.push_back(buffer); + i++; + } + } +} + +HGameFileError ReadPlugins(std::vector &infos, Stream *in) +{ + int fmt_ver = in->ReadInt32(); + if (fmt_ver != 1) + return new MainGameFileError(kMGFErr_PluginDataFmtNotSupported, String::FromFormat("Version: %d, supported: %d", fmt_ver, 1)); + + int pl_count = in->ReadInt32(); + for (int i = 0; i < pl_count; ++i) + { + String name = String::FromStream(in); + size_t datasize = in->ReadInt32(); + // just check for silly datasizes + if (datasize > PLUGIN_SAVEBUFFERSIZE) + return new MainGameFileError(kMGFErr_PluginDataSizeTooLarge, String::FromFormat("Required: %u, max: %u", datasize, PLUGIN_SAVEBUFFERSIZE)); + + PluginInfo info; + info.Name = name; + if (datasize > 0) + { + info.Data.reset(new char[datasize]); + in->Read(info.Data.get(), datasize); + } + info.DataLen = datasize; + infos.push_back(info); + } + return HGameFileError::None(); +} + +// Create the missing audioClips data structure for 3.1.x games. +// This is done by going through the data files and adding all music*.* +// and sound*.* files to it. +void BuildAudioClipArray(const std::vector &assets, std::vector &audioclips) +{ + char temp_name[30]; + int temp_number; + char temp_extension[10]; + + for (const String &asset : assets) + { + if (sscanf(asset.GetCStr(), "%5s%d.%3s", temp_name, &temp_number, temp_extension) != 3) + continue; + + ScriptAudioClip clip; + if (ags_stricmp(temp_extension, "mp3") == 0) + clip.fileType = eAudioFileMP3; + else if (ags_stricmp(temp_extension, "wav") == 0) + clip.fileType = eAudioFileWAV; + else if (ags_stricmp(temp_extension, "voc") == 0) + clip.fileType = eAudioFileVOC; + else if (ags_stricmp(temp_extension, "mid") == 0) + clip.fileType = eAudioFileMIDI; + else if ((ags_stricmp(temp_extension, "mod") == 0) || (ags_stricmp(temp_extension, "xm") == 0) + || (ags_stricmp(temp_extension, "s3m") == 0) || (ags_stricmp(temp_extension, "it") == 0)) + clip.fileType = eAudioFileMOD; + else if (ags_stricmp(temp_extension, "ogg") == 0) + clip.fileType = eAudioFileOGG; + else + continue; + + if (ags_stricmp(temp_name, "music") == 0) + { + clip.scriptName.Format("aMusic%d", temp_number); + clip.fileName.Format("music%d.%s", temp_number, temp_extension); + clip.bundlingType = (ags_stricmp(temp_extension, "mid") == 0) ? AUCL_BUNDLE_EXE : AUCL_BUNDLE_VOX; + clip.type = 2; + clip.defaultRepeat = 1; + } + else if (ags_stricmp(temp_name, "sound") == 0) + { + clip.scriptName.Format("aSound%d", temp_number); + clip.fileName.Format("sound%d.%s", temp_number, temp_extension); + clip.bundlingType = AUCL_BUNDLE_EXE; + clip.type = 3; + clip.defaultRepeat = 0; + } + else + { + continue; + } + + clip.defaultVolume = 100; + clip.defaultPriority = 50; + clip.id = audioclips.size(); + audioclips.push_back(clip); + } +} + +void ApplySpriteData(GameSetupStruct &game, const LoadedGameEntities &ents, GameDataVersion data_ver) +{ + // Apply sprite flags read from original format (sequential array) + spriteset.EnlargeTo(ents.SpriteCount - 1); + for (size_t i = 0; i < ents.SpriteCount; ++i) + { + game.SpriteInfos[i].Flags = ents.SpriteFlags[i]; + } + + // Promote sprite resolutions and mark legacy resolution setting + if (data_ver < kGameVersion_350) + { + for (size_t i = 0; i < ents.SpriteCount; ++i) + { + SpriteInfo &info = game.SpriteInfos[i]; + if (game.IsLegacyHiRes() == info.IsLegacyHiRes()) + info.Flags &= ~(SPF_HIRES | SPF_VAR_RESOLUTION); + else + info.Flags |= SPF_VAR_RESOLUTION; + } + } +} + +void UpgradeFonts(GameSetupStruct &game, GameDataVersion data_ver) +{ + if (data_ver < kGameVersion_350) + { + for (int i = 0; i < game.numfonts; ++i) + { + FontInfo &finfo = game.fonts[i]; + // If the game is hi-res but font is designed for low-res, then scale it up + if (game.IsLegacyHiRes() && game.options[OPT_HIRES_FONTS] == 0) + { + finfo.SizeMultiplier = HIRES_COORD_MULTIPLIER; + } + else + { + finfo.SizeMultiplier = 1; + } + } + } + if (data_ver < kGameVersion_351) + { + for (size_t font = 0; font < game.numfonts; font++) + { + FontInfo &finfo = game.fonts[font]; + // Thickness that corresponds to 1 game pixel + finfo.AutoOutlineThickness = + // if it's a scaled up bitmap font, move the outline out more + (is_bitmap_font(font) && get_font_scaling_mul(font) > 1) ? + get_fixed_pixel_size(1) : 1; + finfo.AutoOutlineStyle = FontInfo::kSquared; + } + } +} + +// Convert audio data to the current version +void UpgradeAudio(GameSetupStruct &game, GameDataVersion data_ver) +{ + if (data_ver >= kGameVersion_320) + return; + + // An explanation of building audio clips array for pre-3.2 games. + // + // When AGS version 3.2 was released, it contained new audio system. + // In the nutshell, prior to 3.2 audio files had to be manually put + // to game project directory and their IDs were taken out of filenames. + // Since 3.2 this information is stored inside the game data. + // To make the modern engine compatible with pre-3.2 games, we have + // to scan game data packages for audio files, and enumerate them + // ourselves, then add this information to game struct. + + // Create soundClips and audioClipTypes structures. + std::vector audiocliptypes; + std::vector audioclips; + + // TODO: find out what is 4 (maybe music, sound, ambient sound, voice?) + audiocliptypes.resize(4); + for (int i = 0; i < 4; i++) + { + audiocliptypes[i].reservedChannels = 1; + audiocliptypes[i].id = i; + audiocliptypes[i].volume_reduction_while_speech_playing = 10; + } + audiocliptypes[3].reservedChannels = 0; + + audioclips.reserve(1000); + // Read audio clip names from "music.vox", then from main library + // TODO: this may become inconvenient that this code has to know about + // "music.vox"; there might be better ways to handle this. + // possibly making AssetManager download several libraries at once will + // resolve this (as well as make it unnecessary to switch between them) + std::vector assets; + // Append contents of "music.vox" + AssetLibInfo music_lib; + if (AssetManager::ReadDataFileTOC("music.vox", music_lib) == kAssetNoError) + { + for (const AssetInfo &info : music_lib.AssetInfos) + { + if (info.FileName.CompareLeftNoCase("music", 5) == 0 || info.FileName.CompareLeftNoCase("sound", 5) == 0) + assets.push_back(info.FileName); + } + } + // Append contents of the main game file + const AssetLibInfo *game_lib = AssetManager::GetLibraryTOC(); + if (game_lib) + { + for (const AssetInfo &info : game_lib->AssetInfos) + { + if (info.FileName.CompareLeftNoCase("music", 5) == 0 || info.FileName.CompareLeftNoCase("sound", 5) == 0) + assets.push_back(info.FileName); + } + } + // Append contents of the game directory + // TODO: use explicit path instead of cwd? keep this consistent with AssetManager! + { + al_ffblk ff; + if (al_findfirst("*.*", &ff, FA_ALL & ~(FA_DIREC)) == 0) + { + do + { + if (ags_strnicmp(ff.name, "music", 5) == 0 || ags_strnicmp(ff.name, "sound", 5) == 0) + assets.push_back(ff.name); + } + while (al_findnext(&ff) == 0); + al_findclose(&ff); + } + } + BuildAudioClipArray(assets, audioclips); + + // Copy gathered data over to game + game.audioClipTypes = audiocliptypes; + game.audioClips = audioclips; + + // Setup sound clip played on score event + game.scoreClipID = -1; +} + +// Convert character data to the current version +void UpgradeCharacters(GameSetupStruct &game, GameDataVersion data_ver) +{ + // TODO: this was done to simplify code transition; ideally we should be + // working with GameSetupStruct's getters and setters here + CharacterInfo *&chars = game.chars; + const int numcharacters = game.numcharacters; + + // Fixup charakter script names for 2.x (EGO -> cEgo) + if (data_ver <= kGameVersion_272) + { + String tempbuffer; + for (int i = 0; i < numcharacters; i++) + { + if (chars[i].scrname[0] == 0) + continue; + tempbuffer.Format("c%c%s", chars[i].scrname[0], ags_strlwr(&chars[i].scrname[1])); + snprintf(chars[i].scrname, MAX_SCRIPT_NAME_LEN, "%s", tempbuffer.GetCStr()); + } + } + + // Fix character walk speed for < 3.1.1 + if (data_ver <= kGameVersion_310) + { + for (int i = 0; i < numcharacters; i++) + { + if (game.options[OPT_ANTIGLIDE]) + chars[i].flags |= CHF_ANTIGLIDE; + } + } + + // Characters can always walk through each other on < 2.54 + if (data_ver < kGameVersion_254) + { + for (int i = 0; i < numcharacters; i++) + { + chars[i].flags |= CHF_NOBLOCKING; + } + } +} + +void UpgradeMouseCursors(GameSetupStruct &game, GameDataVersion data_ver) +{ + if (data_ver <= kGameVersion_272) + { + // Change cursor.view from 0 to -1 for non-animating cursors. + for (int i = 0; i < game.numcursors; ++i) + { + if (game.mcurs[i].view == 0) + game.mcurs[i].view = -1; + } + } +} + +// Adjusts score clip id, depending on game data version +void AdjustScoreSound(GameSetupStruct &game, GameDataVersion data_ver) +{ + if (data_ver < kGameVersion_320) + { + game.scoreClipID = -1; + if (game.options[OPT_SCORESOUND] > 0) + { + ScriptAudioClip* clip = GetAudioClipForOldStyleNumber(game, false, game.options[OPT_SCORESOUND]); + if (clip) + game.scoreClipID = clip->id; + else + game.scoreClipID = -1; + } + } +} + +// Assigns default global message at given index +void SetDefaultGlmsg(GameSetupStruct &game, int msgnum, const char *val) +{ + // TODO: find out why the index should be lowered by 500 + // (or rather if we may pass correct index right away) + msgnum -= 500; + if (game.messages[msgnum] == nullptr) + game.messages[msgnum] = ags_strdup(val); +} + +// Sets up default global messages (these are used mainly in older games) +void SetDefaultGlobalMessages(GameSetupStruct &game) +{ + SetDefaultGlmsg(game, 983, "Sorry, not now."); + SetDefaultGlmsg(game, 984, "Restore"); + SetDefaultGlmsg(game, 985, "Cancel"); + SetDefaultGlmsg(game, 986, "Select a game to restore:"); + SetDefaultGlmsg(game, 987, "Save"); + SetDefaultGlmsg(game, 988, "Type a name to save as:"); + SetDefaultGlmsg(game, 989, "Replace"); + SetDefaultGlmsg(game, 990, "The save directory is full. You must replace an existing game:"); + SetDefaultGlmsg(game, 991, "Replace:"); + SetDefaultGlmsg(game, 992, "With:"); + SetDefaultGlmsg(game, 993, "Quit"); + SetDefaultGlmsg(game, 994, "Play"); + SetDefaultGlmsg(game, 995, "Are you sure you want to quit?"); + SetDefaultGlmsg(game, 996, "You are carrying nothing."); +} + +void FixupSaveDirectory(GameSetupStruct &game) +{ + // If the save game folder was not specified by game author, create one of + // the game name, game GUID, or uniqueid, as a last resort + if (!game.saveGameFolderName[0]) + { + if (game.gamename[0]) + snprintf(game.saveGameFolderName, MAX_SG_FOLDER_LEN, "%s", game.gamename); + else if (game.guid[0]) + snprintf(game.saveGameFolderName, MAX_SG_FOLDER_LEN, "%s", game.guid); + else + snprintf(game.saveGameFolderName, MAX_SG_FOLDER_LEN, "AGS-Game-%d", game.uniqueid); + } + // Lastly, fixup folder name by removing any illegal characters + String s = Path::FixupSharedFilename(game.saveGameFolderName); + snprintf(game.saveGameFolderName, MAX_SG_FOLDER_LEN, "%s", s.GetCStr()); +} + +HGameFileError ReadSpriteFlags(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver) +{ + uint32_t sprcount; + if (data_ver < kGameVersion_256) + sprcount = LEGACY_MAX_SPRITES_V25; + else + sprcount = in->ReadInt32(); + if (sprcount > (uint32_t)SpriteCache::MAX_SPRITE_INDEX + 1) + return new MainGameFileError(kMGFErr_TooManySprites, String::FromFormat("Count: %u, max: %u", sprcount, (uint32_t)SpriteCache::MAX_SPRITE_INDEX + 1)); + + ents.SpriteCount = sprcount; + ents.SpriteFlags.reset(new char[sprcount]); + in->Read(ents.SpriteFlags.get(), sprcount); + return HGameFileError::None(); +} + +HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver) +{ + GameSetupStruct &game = ents.Game; + + { + AlignedStream align_s(in, Common::kAligned_Read); + game.GameSetupStructBase::ReadFromFile(&align_s); + } + + if (game.GetGameRes().IsNull()) + return new MainGameFileError(kMGFErr_InvalidNativeResolution); + + game.read_savegame_info(in, data_ver); + game.read_font_infos(in, data_ver); + HGameFileError err = ReadSpriteFlags(ents, in, data_ver); + if (!err) + return err; + game.ReadInvInfo_Aligned(in); + err = game.read_cursors(in, data_ver); + if (!err) + return err; + game.read_interaction_scripts(in, data_ver); + game.read_words_dictionary(in); + + if (!game.load_compiled_script) + return new MainGameFileError(kMGFErr_NoGlobalScript); + ents.GlobalScript.reset(ccScript::CreateFromStream(in)); + if (!ents.GlobalScript) + return new MainGameFileError(kMGFErr_CreateGlobalScriptFailed, ccErrorString); + err = ReadDialogScript(ents.DialogScript, in, data_ver); + if (!err) + return err; + err = ReadScriptModules(ents.ScriptModules, in, data_ver); + if (!err) + return err; + + ReadViews(game, ents.Views, in, data_ver); + + if (data_ver <= kGameVersion_251) + { + // skip unknown data + int count = in->ReadInt32(); + in->Seek(count * 0x204); + } + + game.read_characters(in, data_ver); + game.read_lipsync(in, data_ver); + game.read_messages(in, data_ver); + + ReadDialogs(ents.Dialogs, ents.OldDialogScripts, ents.OldDialogSources, ents.OldSpeechLines, + in, data_ver, game.numdialog); + HError err2 = GUI::ReadGUI(guis, in); + if (!err2) + return new MainGameFileError(kMGFErr_GameEntityFailed, err2); + game.numgui = guis.size(); + + if (data_ver >= kGameVersion_260) + { + err = ReadPlugins(ents.PluginInfos, in); + if (!err) + return err; + } + + err = game.read_customprops(in, data_ver); + if (!err) + return err; + err = game.read_audio(in, data_ver); + if (!err) + return err; + game.read_room_names(in, data_ver); + return err; +} + +HGameFileError UpdateGameData(LoadedGameEntities &ents, GameDataVersion data_ver) +{ + GameSetupStruct &game = ents.Game; + ApplySpriteData(game, ents, data_ver); + UpgradeFonts(game, data_ver); + UpgradeAudio(game, data_ver); + AdjustScoreSound(game, data_ver); + UpgradeCharacters(game, data_ver); + UpgradeMouseCursors(game, data_ver); + SetDefaultGlobalMessages(game); + // Global talking animation speed + if (data_ver < kGameVersion_312) + { + // Fix animation speed for old formats + game.options[OPT_GLOBALTALKANIMSPD] = 5; + } + else if (data_ver < kGameVersion_330) + { + // Convert game option for 3.1.2 - 3.2 games + game.options[OPT_GLOBALTALKANIMSPD] = game.options[OPT_GLOBALTALKANIMSPD] != 0 ? 5 : (-5 - 1); + } + // Old dialog options API for pre-3.4.0.2 games + if (data_ver < kGameVersion_340_2) + { + game.options[OPT_DIALOGOPTIONSAPI] = -1; + } + // Relative asset resolution in pre-3.5.0.8 (always enabled) + if (data_ver < kGameVersion_350) + { + game.options[OPT_RELATIVEASSETRES] = 1; + } + FixupSaveDirectory(game); + return HGameFileError::None(); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/game/main_game_file.h b/engines/ags/shared/game/main_game_file.h new file mode 100644 index 00000000000..a5084003a84 --- /dev/null +++ b/engines/ags/shared/game/main_game_file.h @@ -0,0 +1,147 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// This unit provides functions for reading main game file into appropriate +// data structures. Main game file contains general game data, such as global +// options, lists of static game entities and compiled scripts modules. +// +//============================================================================= + +#ifndef __AGS_CN_GAME__MAINGAMEFILE_H +#define __AGS_CN_GAME__MAINGAMEFILE_H + +#include +#include +#include +#include "ac/game_version.h" +#include "game/plugininfo.h" +#include "script/cc_script.h" +#include "util/error.h" +#include "util/stream.h" +#include "util/string.h" +#include "util/version.h" + +struct GameSetupStruct; +struct DialogTopic; +struct ViewStruct; + +namespace AGS +{ +namespace Common +{ + +// Error codes for main game file reading +enum MainGameFileErrorType +{ + kMGFErr_NoError, + kMGFErr_FileOpenFailed, + kMGFErr_SignatureFailed, + // separate error given for "too old" format to provide clarifying message + kMGFErr_FormatVersionTooOld, + kMGFErr_FormatVersionNotSupported, + kMGFErr_CapsNotSupported, + kMGFErr_InvalidNativeResolution, + kMGFErr_TooManySprites, + kMGFErr_TooManyCursors, + kMGFErr_InvalidPropertySchema, + kMGFErr_InvalidPropertyValues, + kMGFErr_NoGlobalScript, + kMGFErr_CreateGlobalScriptFailed, + kMGFErr_CreateDialogScriptFailed, + kMGFErr_CreateScriptModuleFailed, + kMGFErr_GameEntityFailed, + kMGFErr_PluginDataFmtNotSupported, + kMGFErr_PluginDataSizeTooLarge +}; + +String GetMainGameFileErrorText(MainGameFileErrorType err); + +typedef TypedCodeError MainGameFileError; +typedef ErrorHandle HGameFileError; +typedef std::shared_ptr PStream; + +// MainGameSource defines a successfully opened main game file +struct MainGameSource +{ + // Standart main game file names for 3.* and 2.* games respectively + static const String DefaultFilename_v3; + static const String DefaultFilename_v2; + // Signature of the current game format + static const String Signature; + + // Name of the asset file + String Filename; + // Game file format version + GameDataVersion DataVersion; + // Tool identifier (like version) this game was compiled with + String CompiledWith; + // Extended engine capabilities required by the game; their primary use + // currently is to let "alternate" game formats indicate themselves + std::set Caps; + // A ponter to the opened stream + PStream InputStream; + + MainGameSource(); +}; + +// LoadedGameEntities is meant for keeping objects loaded from the game file. +// Because copying/assignment methods are not properly implemented for some +// of these objects yet, they have to be attached using references to be read +// directly. This is temporary solution that has to be resolved by the future +// code refactoring. +struct LoadedGameEntities +{ + GameSetupStruct &Game; + DialogTopic *&Dialogs; + ViewStruct *&Views; + PScript GlobalScript; + PScript DialogScript; + std::vector ScriptModules; + std::vector PluginInfos; + + // Original sprite data (when it was read into const-sized arrays) + size_t SpriteCount; + std::unique_ptr SpriteFlags; + + // Old dialog support + // legacy compiled dialog script of its own format, + // requires separate interpreting + std::vector< std::shared_ptr > OldDialogScripts; + // probably, actual dialog script sources kept within some older games + std::vector OldDialogSources; + // speech texts displayed during dialog + std::vector OldSpeechLines; + + LoadedGameEntities(GameSetupStruct &game, DialogTopic *&dialogs, ViewStruct *&views); + ~LoadedGameEntities(); +}; + +// Tells if the given path (library filename) contains main game file +bool IsMainGameLibrary(const String &filename); +// Opens main game file for reading from an arbitrary file +HGameFileError OpenMainGameFile(const String &filename, MainGameSource &src); +// Opens main game file for reading from the asset library (uses default asset name) +HGameFileError OpenMainGameFileFromDefaultAsset(MainGameSource &src); +// Reads game data, applies necessary conversions to match current format version +HGameFileError ReadGameData(LoadedGameEntities &ents, Stream *in, GameDataVersion data_ver); +// Applies necessary updates, conversions and fixups to the loaded data +// making it compatible with current engine +HGameFileError UpdateGameData(LoadedGameEntities &ents, GameDataVersion data_ver); +// Ensures that the game saves directory path is valid +void FixupSaveDirectory(GameSetupStruct &game); + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_GAME__MAINGAMEFILE_H diff --git a/engines/ags/shared/game/plugininfo.h b/engines/ags/shared/game/plugininfo.h new file mode 100644 index 00000000000..bf75cde7f9c --- /dev/null +++ b/engines/ags/shared/game/plugininfo.h @@ -0,0 +1,47 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// PluginInfo - a struct defining general information on game plugin. +// +//============================================================================= + +#ifndef __AGS_CN_GAME__PLUGININFO_H +#define __AGS_CN_GAME__PLUGININFO_H + +#include +#include "util/string.h" + +// TODO: why 10 MB limit? +#define PLUGIN_SAVEBUFFERSIZE 10247680 + +namespace AGS +{ +namespace Common +{ + +struct PluginInfo +{ + // (File)name of plugin + String Name; + // Custom data for plugin + std::shared_ptr Data; + size_t DataLen; + + PluginInfo() : DataLen(0) {} +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_GAME__PLUGININFO_H diff --git a/engines/ags/shared/game/room_file.cpp b/engines/ags/shared/game/room_file.cpp new file mode 100644 index 00000000000..65a65e69c3e --- /dev/null +++ b/engines/ags/shared/game/room_file.cpp @@ -0,0 +1,966 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" // update_polled_stuff +#include "ac/common_defines.h" +#include "ac/gamestructdefines.h" +#include "ac/wordsdictionary.h" // TODO: extract string decryption +#include "core/assetmanager.h" +#include "debug/out.h" +#include "game/customproperties.h" +#include "game/room_file.h" +#include "game/roomstruct.h" +#include "gfx/bitmap.h" +#include "script/cc_error.h" +#include "script/cc_script.h" +#include "util/compress.h" +#include "util/string_utils.h" + +// default number of hotspots to read from the room file +#define MIN_ROOM_HOTSPOTS 20 +#define LEGACY_HOTSPOT_NAME_LEN 30 +#define LEGACY_ROOM_PASSWORD_LENGTH 11 +#define LEGACY_ROOM_PASSWORD_SALT 60 +#define ROOM_MESSAGE_FLAG_DISPLAYNEXT 200 +#define ROOM_LEGACY_OPTIONS_SIZE 10 +#define LEGACY_TINT_IS_ENABLED 0x80000000 + +namespace AGS +{ +namespace Common +{ + +RoomDataSource::RoomDataSource() + : DataVersion(kRoomVersion_Undefined) +{ +} + +String GetRoomFileErrorText(RoomFileErrorType err) +{ + switch (err) + { + case kRoomFileErr_NoError: + return "No error."; + case kRoomFileErr_FileOpenFailed: + return "Room file was not found or could not be opened."; + case kRoomFileErr_FormatNotSupported: + return "Format version not supported."; + case kRoomFileErr_UnexpectedEOF: + return "Unexpected end of file."; + case kRoomFileErr_UnknownBlockType: + return "Unknown block type."; + case kRoomFileErr_OldBlockNotSupported: + return "Block type is too old and not supported by this version of the engine."; + case kRoomFileErr_BlockDataOverlapping: + return "Block data overlapping."; + case kRoomFileErr_IncompatibleEngine: + return "This engine cannot handle requested room content."; + case kRoomFileErr_ScriptLoadFailed: + return "Script load failed."; + case kRoomFileErr_InconsistentData: + return "Inconsistent room data, or file is corrupted."; + case kRoomFileErr_PropertiesBlockFormat: + return "Unknown format of the custom properties block."; + case kRoomFileErr_InvalidPropertyValues: + return "Errors encountered when reading custom properties."; + case kRoomFileErr_BlockNotFound: + return "Required block was not found."; + } + return "Unknown error."; +} + +HRoomFileError OpenRoomFile(const String &filename, RoomDataSource &src) +{ + // Cleanup source struct + src = RoomDataSource(); + // Try to open room file + Stream *in = AssetManager::OpenAsset(filename); + if (in == nullptr) + return new RoomFileError(kRoomFileErr_FileOpenFailed, String::FromFormat("Filename: %s.", filename.GetCStr())); + // Read room header + src.Filename = filename; + src.DataVersion = (RoomFileVersion)in->ReadInt16(); + if (src.DataVersion < kRoomVersion_250b || src.DataVersion > kRoomVersion_Current) + return new RoomFileError(kRoomFileErr_FormatNotSupported, String::FromFormat("Required format version: %d, supported %d - %d", src.DataVersion, kRoomVersion_250b, kRoomVersion_Current)); + // Everything is fine, return opened stream + src.InputStream.reset(in); + return HRoomFileError::None(); +} + + +enum RoomFileBlock +{ + kRoomFblk_None = 0, + // Main room data + kRoomFblk_Main = 1, + // Room script text source (was present in older room formats) + kRoomFblk_Script = 2, + // Old versions of compiled script (no longer supported) + kRoomFblk_CompScript = 3, + kRoomFblk_CompScript2 = 4, + // Names of the room objects + kRoomFblk_ObjectNames = 5, + // Secondary room backgrounds + kRoomFblk_AnimBg = 6, + // Contemporary compiled script + kRoomFblk_CompScript3 = 7, + // Custom properties + kRoomFblk_Properties = 8, + // Script names of the room objects + kRoomFblk_ObjectScNames = 9, + // End of room data tag + kRoomFile_EOF = 0xFF +}; + + +void ReadRoomObject(RoomObjectInfo &obj, Stream *in) +{ + obj.Sprite = in->ReadInt16(); + obj.X = in->ReadInt16(); + obj.Y = in->ReadInt16(); + obj.Room = in->ReadInt16(); + obj.IsOn = in->ReadInt16() != 0; +} + +void WriteRoomObject(const RoomObjectInfo &obj, Stream *out) +{ + // TODO: expand serialization into 32-bit values at least for the sprite index!! + out->WriteInt16((int16_t)obj.Sprite); + out->WriteInt16((int16_t)obj.X); + out->WriteInt16((int16_t)obj.Y); + out->WriteInt16((int16_t)obj.Room); + out->WriteInt16(obj.IsOn ? 1 : 0); +} + + +// Main room data +HRoomFileError ReadMainBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) +{ + int bpp; + if (data_ver >= kRoomVersion_208) + bpp = in->ReadInt32(); + else + bpp = 1; + + if (bpp < 1) + bpp = 1; + + room->BackgroundBPP = bpp; + room->WalkBehindCount = in->ReadInt16(); + if (room->WalkBehindCount > MAX_WALK_BEHINDS) + return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many walk-behinds (in room: %d, max: %d).", room->WalkBehindCount, MAX_WALK_BEHINDS)); + + // Walk-behinds baselines + for (size_t i = 0; i < room->WalkBehindCount; ++i) + room->WalkBehinds[i].Baseline = in->ReadInt16(); + + room->HotspotCount = in->ReadInt32(); + if (room->HotspotCount == 0) + room->HotspotCount = MIN_ROOM_HOTSPOTS; + if (room->HotspotCount > MAX_ROOM_HOTSPOTS) + return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many hotspots (in room: %d, max: %d).", room->HotspotCount, MAX_ROOM_HOTSPOTS)); + + // Hotspots walk-to points + for (size_t i = 0; i < room->HotspotCount; ++i) + { + room->Hotspots[i].WalkTo.X = in->ReadInt16(); + room->Hotspots[i].WalkTo.Y = in->ReadInt16(); + } + + // Hotspots names and script names + for (size_t i = 0; i < room->HotspotCount; ++i) + { + if (data_ver >= kRoomVersion_3415) + room->Hotspots[i].Name = StrUtil::ReadString(in); + else if (data_ver >= kRoomVersion_303a) + room->Hotspots[i].Name = String::FromStream(in); + else + room->Hotspots[i].Name = String::FromStreamCount(in, LEGACY_HOTSPOT_NAME_LEN); + } + + if (data_ver >= kRoomVersion_270) + { + for (size_t i = 0; i < room->HotspotCount; ++i) + { + if (data_ver >= kRoomVersion_3415) + room->Hotspots[i].ScriptName = StrUtil::ReadString(in); + else + room->Hotspots[i].ScriptName = String::FromStreamCount(in, MAX_SCRIPT_NAME_LEN); + } + } + + // TODO: remove from format later + size_t polypoint_areas = in->ReadInt32(); + if (polypoint_areas > 0) + return new RoomFileError(kRoomFileErr_IncompatibleEngine, "Legacy poly-point areas are no longer supported."); + /* NOTE: implementation hidden in room_file_deprecated.cpp + for (size_t i = 0; i < polypoint_areas; ++i) + wallpoints[i].Read(in); + */ + + update_polled_stuff_if_runtime(); + + room->Edges.Top = in->ReadInt16(); + room->Edges.Bottom = in->ReadInt16(); + room->Edges.Left = in->ReadInt16(); + room->Edges.Right = in->ReadInt16(); + + // Room objects + room->ObjectCount = in->ReadInt16(); + if (room->ObjectCount > MAX_ROOM_OBJECTS) + return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many objects (in room: %d, max: %d).", room->ObjectCount, MAX_ROOM_OBJECTS)); + + for (size_t i = 0; i < room->ObjectCount; ++i) + ReadRoomObject(room->Objects[i], in); + + // Legacy interactions + if (data_ver >= kRoomVersion_253) + { + size_t localvar_count = in->ReadInt32(); + if (localvar_count > 0) + { + room->LocalVariables.resize(localvar_count); + for (size_t i = 0; i < localvar_count; ++i) + room->LocalVariables[i].Read(in); + } + } + + if (data_ver >= kRoomVersion_241 && data_ver < kRoomVersion_300a) + { + for (size_t i = 0; i < room->HotspotCount; ++i) + room->Hotspots[i].Interaction.reset(Interaction::CreateFromStream(in)); + for (size_t i = 0; i < room->ObjectCount; ++i) + room->Objects[i].Interaction.reset(Interaction::CreateFromStream(in)); + room->Interaction.reset(Interaction::CreateFromStream(in)); + } + + if (data_ver >= kRoomVersion_255b) + { + room->RegionCount = in->ReadInt32(); + if (room->RegionCount > MAX_ROOM_REGIONS) + return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many regions (in room: %d, max: %d).", room->RegionCount, MAX_ROOM_REGIONS)); + + if (data_ver < kRoomVersion_300a) + { + for (size_t i = 0; i < room->RegionCount; ++i) + room->Regions[i].Interaction.reset(Interaction::CreateFromStream(in)); + } + } + + // Event script links + if (data_ver >= kRoomVersion_300a) + { + room->EventHandlers.reset(InteractionScripts::CreateFromStream(in)); + for (size_t i = 0; i < room->HotspotCount; ++i) + room->Hotspots[i].EventHandlers.reset(InteractionScripts::CreateFromStream(in)); + for (size_t i = 0; i < room->ObjectCount; ++i) + room->Objects[i].EventHandlers.reset(InteractionScripts::CreateFromStream(in)); + for (size_t i = 0; i < room->RegionCount; ++i) + room->Regions[i].EventHandlers.reset(InteractionScripts::CreateFromStream(in)); + } + + if (data_ver >= kRoomVersion_200_alpha) + { + for (size_t i = 0; i < room->ObjectCount; ++i) + room->Objects[i].Baseline = in->ReadInt32(); + room->Width = in->ReadInt16(); + room->Height = in->ReadInt16(); + } + + if (data_ver >= kRoomVersion_262) + for (size_t i = 0; i < room->ObjectCount; ++i) + room->Objects[i].Flags = in->ReadInt16(); + + if (data_ver >= kRoomVersion_200_final) + room->MaskResolution = in->ReadInt16(); + + room->WalkAreaCount = MAX_WALK_AREAS; + if (data_ver >= kRoomVersion_240) + room->WalkAreaCount = in->ReadInt32(); + if (room->WalkAreaCount > MAX_WALK_AREAS + 1) + return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many walkable areas (in room: %d, max: %d).", room->WalkAreaCount, MAX_WALK_AREAS + 1)); + + if (data_ver >= kRoomVersion_200_alpha7) + for (size_t i = 0; i < room->WalkAreaCount; ++i) + room->WalkAreas[i].ScalingFar = in->ReadInt16(); + if (data_ver >= kRoomVersion_214) + for (size_t i = 0; i < room->WalkAreaCount; ++i) + room->WalkAreas[i].Light = in->ReadInt16(); + if (data_ver >= kRoomVersion_251) + { + for (size_t i = 0; i < room->WalkAreaCount; ++i) + room->WalkAreas[i].ScalingNear = in->ReadInt16(); + for (size_t i = 0; i < room->WalkAreaCount; ++i) + room->WalkAreas[i].Top = in->ReadInt16(); + for (size_t i = 0; i < room->WalkAreaCount; ++i) + room->WalkAreas[i].Bottom = in->ReadInt16(); + } + + in->Seek(LEGACY_ROOM_PASSWORD_LENGTH); // skip password + room->Options.StartupMusic = in->ReadInt8(); + room->Options.SaveLoadDisabled = in->ReadInt8() != 0; + room->Options.PlayerCharOff = in->ReadInt8() != 0; + room->Options.PlayerView = in->ReadInt8(); + room->Options.MusicVolume = (RoomVolumeMod)in->ReadInt8(); + in->Seek(ROOM_LEGACY_OPTIONS_SIZE - 5); + + room->MessageCount = in->ReadInt16(); + if (room->MessageCount > MAX_MESSAGES) + return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many room messages (in room: %d, max: %d).", room->MessageCount, MAX_MESSAGES)); + + if (data_ver >= kRoomVersion_272) + room->GameID = in->ReadInt32(); + + if (data_ver >= kRoomVersion_pre114_3) + { + for (size_t i = 0; i < room->MessageCount; ++i) + { + room->MessageInfos[i].DisplayAs = in->ReadInt8(); + room->MessageInfos[i].Flags = in->ReadInt8(); + } + } + + char buffer[3000]; + for (size_t i = 0; i < room->MessageCount; ++i) + { + if (data_ver >= kRoomVersion_261) + read_string_decrypt(in, buffer, sizeof(buffer)); + else + StrUtil::ReadCStr(buffer, in, sizeof(buffer)); + room->Messages[i] = buffer; + } + + // Very old format legacy room animations (FullAnimation) + if (data_ver >= kRoomVersion_pre114_6) + { + // TODO: remove from format later + size_t fullanim_count = in->ReadInt16(); + if (fullanim_count > 0) + return new RoomFileError(kRoomFileErr_IncompatibleEngine, "Room animations are no longer supported."); + /* NOTE: implementation hidden in room_file_deprecated.cpp + in->ReadArray(&fullanims[0], sizeof(FullAnimation), fullanim_count); + */ + } + + // Ancient "graphical scripts". We currently don't support them because + // there's no knowledge on how to convert them to modern engine. + if ((data_ver >= kRoomVersion_pre114_4) && (data_ver < kRoomVersion_250a)) + { + return new RoomFileError(kRoomFileErr_IncompatibleEngine, "Pre-2.5 graphical scripts are no longer supported."); + /* NOTE: implementation hidden in room_file_deprecated.cpp + ReadPre250Scripts(in); + */ + } + + if (data_ver >= kRoomVersion_114) + { + for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i) + room->WalkAreas[i].Light = in->ReadInt16(); + } + if (data_ver >= kRoomVersion_255b) + { + for (size_t i = 0; i < room->RegionCount; ++i) + room->Regions[i].Light = in->ReadInt16(); + for (size_t i = 0; i < room->RegionCount; ++i) + room->Regions[i].Tint = in->ReadInt32(); + } + + update_polled_stuff_if_runtime(); + // Primary background + Bitmap *mask = nullptr; + if (data_ver >= kRoomVersion_pre114_5) + load_lzw(in, &mask, room->BackgroundBPP, room->Palette); + else + loadcompressed_allegro(in, &mask, room->Palette); + room->BgFrames[0].Graphic.reset(mask); + + update_polled_stuff_if_runtime(); + // Mask bitmaps + if (data_ver >= kRoomVersion_255b) + { + loadcompressed_allegro(in, &mask, room->Palette); + } + else if (data_ver >= kRoomVersion_114) + { + // an old version - clear the 'shadow' area into a blank regions bmp + loadcompressed_allegro(in, &mask, room->Palette); + delete mask; + mask = nullptr; + } + room->RegionMask.reset(mask); + update_polled_stuff_if_runtime(); + loadcompressed_allegro(in, &mask, room->Palette); + room->WalkAreaMask.reset(mask); + update_polled_stuff_if_runtime(); + loadcompressed_allegro(in, &mask, room->Palette); + room->WalkBehindMask.reset(mask); + update_polled_stuff_if_runtime(); + loadcompressed_allegro(in, &mask, room->Palette); + room->HotspotMask.reset(mask); + return HRoomFileError::None(); +} + +// Room script sources (original text) +HRoomFileError ReadScriptBlock(char *&buf, Stream *in, RoomFileVersion data_ver) +{ + size_t len = in->ReadInt32(); + buf = new char[len + 1]; + in->Read(buf, len); + buf[len] = 0; + for (size_t i = 0; i < len; ++i) + buf[i] += passwencstring[i % 11]; + return HRoomFileError::None(); +} + +// Compiled room script +HRoomFileError ReadCompSc3Block(RoomStruct *room, Stream *in, RoomFileVersion data_ver) +{ + room->CompiledScript.reset(ccScript::CreateFromStream(in)); + if (room->CompiledScript == nullptr) + return new RoomFileError(kRoomFileErr_ScriptLoadFailed, ccErrorString); + return HRoomFileError::None(); +} + +// Room object names +HRoomFileError ReadObjNamesBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) +{ + int name_count = in->ReadByte(); + if (name_count != room->ObjectCount) + return new RoomFileError(kRoomFileErr_InconsistentData, + String::FromFormat("In the object names block, expected name count: %d, got %d", room->ObjectCount, name_count)); + + for (size_t i = 0; i < room->ObjectCount; ++i) + { + if (data_ver >= kRoomVersion_3415) + room->Objects[i].Name = StrUtil::ReadString(in); + else + room->Objects[i].Name.ReadCount(in, LEGACY_MAXOBJNAMELEN); + } + return HRoomFileError::None(); +} + +// Room object script names +HRoomFileError ReadObjScNamesBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) +{ + int name_count = in->ReadByte(); + if (name_count != room->ObjectCount) + return new RoomFileError(kRoomFileErr_InconsistentData, + String::FromFormat("In the object script names block, expected name count: %d, got %d", room->ObjectCount, name_count)); + + for (size_t i = 0; i < room->ObjectCount; ++i) + { + if (data_ver >= kRoomVersion_3415) + room->Objects[i].ScriptName = StrUtil::ReadString(in); + else + room->Objects[i].ScriptName.ReadCount(in, MAX_SCRIPT_NAME_LEN); + } + return HRoomFileError::None(); +} + +// Secondary backgrounds +HRoomFileError ReadAnimBgBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) +{ + room->BgFrameCount = in->ReadByte(); + if (room->BgFrameCount > MAX_ROOM_BGFRAMES) + return new RoomFileError(kRoomFileErr_IncompatibleEngine, String::FromFormat("Too many room backgrounds (in room: %d, max: %d).", room->BgFrameCount, MAX_ROOM_BGFRAMES)); + + room->BgAnimSpeed = in->ReadByte(); + if (data_ver >= kRoomVersion_255a) + { + for (size_t i = 0; i < room->BgFrameCount; ++i) + room->BgFrames[i].IsPaletteShared = in->ReadInt8() != 0; + } + + for (size_t i = 1; i < room->BgFrameCount; ++i) + { + update_polled_stuff_if_runtime(); + Bitmap *frame = nullptr; + load_lzw(in, &frame, room->BackgroundBPP, room->BgFrames[i].Palette); + room->BgFrames[i].Graphic.reset(frame); + } + return HRoomFileError::None(); +} + +// Read custom properties +HRoomFileError ReadPropertiesBlock(RoomStruct *room, Stream *in, RoomFileVersion data_ver) +{ + int prop_ver = in->ReadInt32(); + if (prop_ver != 1) + return new RoomFileError(kRoomFileErr_PropertiesBlockFormat, String::FromFormat("Expected version %d, got %d", 1, prop_ver)); + + int errors = 0; + errors += Properties::ReadValues(room->Properties, in); + for (size_t i = 0; i < room->HotspotCount; ++i) + errors += Properties::ReadValues(room->Hotspots[i].Properties, in); + for (size_t i = 0; i < room->ObjectCount; ++i) + errors += Properties::ReadValues(room->Objects[i].Properties, in); + + if (errors > 0) + return new RoomFileError(kRoomFileErr_InvalidPropertyValues); + return HRoomFileError::None(); +} + +HRoomFileError ReadRoomBlock(RoomStruct *room, Stream *in, RoomFileBlock block, RoomFileVersion data_ver) +{ + soff_t block_len = data_ver < kRoomVersion_350 ? in->ReadInt32() : in->ReadInt64(); + soff_t block_end = in->GetPosition() + block_len; + + HRoomFileError err; + switch (block) + { + case kRoomFblk_Main: + err = ReadMainBlock(room, in, data_ver); + break; + case kRoomFblk_Script: + in->Seek(block_len); // no longer read source script text into RoomStruct + break; + case kRoomFblk_CompScript3: + err = ReadCompSc3Block(room, in, data_ver); + break; + case kRoomFblk_ObjectNames: + err = ReadObjNamesBlock(room, in, data_ver); + break; + case kRoomFblk_ObjectScNames: + err = ReadObjScNamesBlock(room, in, data_ver); + break; + case kRoomFblk_AnimBg: + err = ReadAnimBgBlock(room, in, data_ver); + break; + case kRoomFblk_Properties: + err = ReadPropertiesBlock(room, in, data_ver); + break; + case kRoomFblk_CompScript: + case kRoomFblk_CompScript2: + return new RoomFileError(kRoomFileErr_OldBlockNotSupported, + String::FromFormat("Type: %d.", block)); + default: + return new RoomFileError(kRoomFileErr_UnknownBlockType, + String::FromFormat("Type: %d, known range: %d - %d.", block, kRoomFblk_Main, kRoomFblk_ObjectScNames)); + } + + if (!err) + return err; + + soff_t cur_pos = in->GetPosition(); + if (cur_pos > block_end) + { + return new RoomFileError(kRoomFileErr_BlockDataOverlapping, + String::FromFormat("Type: %d, expected to end at offset: %u, finished reading at %u.", block, block_end, cur_pos)); + } + else if (cur_pos < block_end) + { + Debug::Printf(kDbgMsg_Warn, "WARNING: room data blocks nonsequential, block type %d expected to end at %u, finished reading at %u", + block, block_end, cur_pos); + in->Seek(block_end, Common::kSeekBegin); + } + return HRoomFileError::None(); +} + + +HRoomFileError ReadRoomData(RoomStruct *room, Stream *in, RoomFileVersion data_ver) +{ + room->DataVersion = data_ver; + + RoomFileBlock block; + do + { + update_polled_stuff_if_runtime(); + int b = in->ReadByte(); + if (b < 0) + return new RoomFileError(kRoomFileErr_UnexpectedEOF); + block = (RoomFileBlock)b; + if (block != kRoomFile_EOF) + { + HRoomFileError err = ReadRoomBlock(room, in, block, data_ver); + if (!err) + return err; + } + } + while (block != kRoomFile_EOF); + return HRoomFileError::None(); +} + +HRoomFileError UpdateRoomData(RoomStruct *room, RoomFileVersion data_ver, bool game_is_hires, const std::vector &sprinfos) +{ + if (data_ver < kRoomVersion_200_final) + room->MaskResolution = room->BgFrames[0].Graphic->GetWidth() > 320 ? kRoomHiRes : kRoomLoRes; + if (data_ver < kRoomVersion_3508) + { + // Save legacy resolution if it DOES NOT match game's; + // otherwise it gets promoted to "real resolution" + if (room->MaskResolution == 1 && game_is_hires) + room->SetResolution(kRoomLoRes); + else if (room->MaskResolution > 1 && !game_is_hires) + room->SetResolution(kRoomHiRes); + } + + // Old version - copy walkable areas to regions + if (data_ver < kRoomVersion_255b) + { + if (!room->RegionMask) + room->RegionMask.reset(BitmapHelper::CreateBitmap(room->WalkAreaMask->GetWidth(), room->WalkAreaMask->GetHeight(), 8)); + room->RegionMask->Blit(room->WalkAreaMask.get(), 0, 0, 0, 0, room->RegionMask->GetWidth(), room->RegionMask->GetHeight()); + for (size_t i = 0; i < MAX_ROOM_REGIONS; ++i) + { + room->Regions[i].Light = room->WalkAreas[i].Light; + room->Regions[i].Tint = 255; + } + } + + // Fill in dummy interaction objects into unused slots + // TODO: remove this later, need to rework the legacy interaction usage around the engine code to prevent crashes + if (data_ver < kRoomVersion_300a) + { + if (!room->Interaction) + room->Interaction.reset(new Interaction()); + for (size_t i = 0; i < (size_t)MAX_ROOM_HOTSPOTS; ++i) + if (!room->Hotspots[i].Interaction) + room->Hotspots[i].Interaction.reset(new Interaction()); + for (size_t i = 0; i < (size_t)MAX_ROOM_OBJECTS; ++i) + if (!room->Objects[i].Interaction) + room->Objects[i].Interaction.reset(new Interaction()); + for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i) + if (!room->Regions[i].Interaction) + room->Regions[i].Interaction.reset(new Interaction()); + } + + // Upgade room object script names + if (data_ver < kRoomVersion_300a) + { + for (size_t i = 0; i < room->ObjectCount; ++i) + { + if (room->Objects[i].ScriptName.GetLength() > 0) + { + String jibbledScriptName; + jibbledScriptName.Format("o%s", room->Objects[i].ScriptName.GetCStr()); + jibbledScriptName.MakeLower(); + if (jibbledScriptName.GetLength() >= 2) + jibbledScriptName.SetAt(1, toupper(jibbledScriptName[1u])); + room->Objects[i].ScriptName = jibbledScriptName; + } + } + } + + // Pre-3.0.3, multiply up co-ordinates for high-res games to bring them + // to the proper game coordinate system. + // If you change this, also change convert_room_coordinates_to_data_res + // function in the engine + if (data_ver < kRoomVersion_303b && game_is_hires) + { + const int mul = HIRES_COORD_MULTIPLIER; + for (size_t i = 0; i < room->ObjectCount; ++i) + { + room->Objects[i].X *= mul; + room->Objects[i].Y *= mul; + if (room->Objects[i].Baseline > 0) + { + room->Objects[i].Baseline *= mul; + } + } + + for (size_t i = 0; i < room->HotspotCount; ++i) + { + room->Hotspots[i].WalkTo.X *= mul; + room->Hotspots[i].WalkTo.Y *= mul; + } + + for (size_t i = 0; i < room->WalkBehindCount; ++i) + { + room->WalkBehinds[i].Baseline *= mul; + } + + room->Edges.Left *= mul; + room->Edges.Top *= mul; + room->Edges.Bottom *= mul; + room->Edges.Right *= mul; + room->Width *= mul; + room->Height *= mul; + } + + // Adjust object Y coordinate by adding sprite's height + // NOTE: this is impossible to do without game sprite information loaded beforehand + // NOTE: this should be done after coordinate conversion above for simplicity + if (data_ver < kRoomVersion_300a) + { + for (size_t i = 0; i < room->ObjectCount; ++i) + room->Objects[i].Y += sprinfos[room->Objects[i].Sprite].Height; + } + + if (data_ver >= kRoomVersion_251) + { + // if they set a contiuously scaled area where the top + // and bottom zoom levels are identical, set it as a normal + // scaled area + for (size_t i = 0; i < room->WalkAreaCount; ++i) + { + if (room->WalkAreas[i].ScalingFar == room->WalkAreas[i].ScalingNear) + room->WalkAreas[i].ScalingNear = NOT_VECTOR_SCALED; + } + } + + // Convert the old format region tint saturation + if (data_ver < kRoomVersion_3404) + { + for (size_t i = 0; i < room->RegionCount; ++i) + { + if ((room->Regions[i].Tint & LEGACY_TINT_IS_ENABLED) != 0) + { + room->Regions[i].Tint &= ~LEGACY_TINT_IS_ENABLED; + // older versions of the editor had a bug - work around it + int tint_amount = (room->Regions[i].Light > 0 ? room->Regions[i].Light : 50); + room->Regions[i].Tint |= (tint_amount & 0xFF) << 24; + room->Regions[i].Light = 255; + } + } + } + + // Older format room messages had flags appended to the message string + // TODO: find out which data versions had these; is it safe to assume this was before kRoomVersion_pre114_3? + for (size_t i = 0; i < room->MessageCount; ++i) + { + if (!room->Messages[i].IsEmpty() && room->Messages[i].GetLast() == (char)ROOM_MESSAGE_FLAG_DISPLAYNEXT) + { + room->Messages[i].ClipRight(1); + room->MessageInfos[i].Flags |= MSG_DISPLAYNEXT; + } + } + + // sync bpalettes[0] with room.pal + memcpy(room->BgFrames[0].Palette, room->Palette, sizeof(color) * 256); + return HRoomFileError::None(); +} + +HRoomFileError ExtractScriptText(String &script, Stream *in, RoomFileVersion data_ver) +{ + RoomFileBlock block; + do + { + int b = in->ReadByte(); + if (b < 0) + return new RoomFileError(kRoomFileErr_UnexpectedEOF); + block = (RoomFileBlock)b; + soff_t block_len = data_ver < kRoomVersion_350 ? in->ReadInt32() : in->ReadInt64(); + if (block == kRoomFblk_Script) + { + char *buf = nullptr; + HRoomFileError err = ReadScriptBlock(buf, in, data_ver); + if (err) + { + script = buf; + delete buf; + } + return err; + } + if (block != kRoomFile_EOF) + in->Seek(block_len); // skip block + } while (block != kRoomFile_EOF); + return new RoomFileError(kRoomFileErr_BlockNotFound); +} + + +// Type of function that writes single room block. +typedef void(*PfnWriteBlock)(const RoomStruct *room, Stream *out); +// Generic function that saves a block and automatically adds its size into header +void WriteBlock(const RoomStruct *room, RoomFileBlock block, PfnWriteBlock writer, Stream *out) +{ + // Write block's header + out->WriteByte(block); + soff_t sz_at = out->GetPosition(); + out->WriteInt64(0); // block size placeholder + // Call writer to save actual block contents + writer(room, out); + + // Now calculate the block's size... + soff_t end_at = out->GetPosition(); + soff_t block_size = (end_at - sz_at) - sizeof(int64_t); + // ...return back and write block's size in the placeholder + out->Seek(sz_at, Common::kSeekBegin); + out->WriteInt64(block_size); + // ...and get back to the end of the file + out->Seek(0, Common::kSeekEnd); +} + +void WriteInteractionScripts(const InteractionScripts *interactions, Stream *out) +{ + out->WriteInt32(interactions->ScriptFuncNames.size()); + for (size_t i = 0; i < interactions->ScriptFuncNames.size(); ++i) + interactions->ScriptFuncNames[i].Write(out); +} + +void WriteMainBlock(const RoomStruct *room, Stream *out) +{ + out->WriteInt32(room->BackgroundBPP); + out->WriteInt16((int16_t)room->WalkBehindCount); + for (size_t i = 0; i < room->WalkBehindCount; ++i) + out->WriteInt16(room->WalkBehinds[i].Baseline); + + out->WriteInt32(room->HotspotCount); + for (size_t i = 0; i < room->HotspotCount; ++i) + { + out->WriteInt16(room->Hotspots[i].WalkTo.X); + out->WriteInt16(room->Hotspots[i].WalkTo.Y); + } + for (size_t i = 0; i < room->HotspotCount; ++i) + Common::StrUtil::WriteString(room->Hotspots[i].Name, out); + for (size_t i = 0; i < room->HotspotCount; ++i) + Common::StrUtil::WriteString(room->Hotspots[i].ScriptName, out); + + out->WriteInt32(0); // legacy poly-point areas + + out->WriteInt16(room->Edges.Top); + out->WriteInt16(room->Edges.Bottom); + out->WriteInt16(room->Edges.Left); + out->WriteInt16(room->Edges.Right); + + out->WriteInt16((int16_t)room->ObjectCount); + for (size_t i = 0; i < room->ObjectCount; ++i) + { + WriteRoomObject(room->Objects[i], out); + } + + out->WriteInt32(0); // legacy interaction vars + out->WriteInt32(MAX_ROOM_REGIONS); + + WriteInteractionScripts(room->EventHandlers.get(), out); + for (size_t i = 0; i < room->HotspotCount; ++i) + WriteInteractionScripts(room->Hotspots[i].EventHandlers.get(), out); + for (size_t i = 0; i < room->ObjectCount; ++i) + WriteInteractionScripts(room->Objects[i].EventHandlers.get(), out); + for (size_t i = 0; i < room->RegionCount; ++i) + WriteInteractionScripts(room->Regions[i].EventHandlers.get(), out); + + for (size_t i = 0; i < room->ObjectCount; ++i) + out->WriteInt32(room->Objects[i].Baseline); + out->WriteInt16(room->Width); + out->WriteInt16(room->Height); + for (size_t i = 0; i < room->ObjectCount; ++i) + out->WriteInt16(room->Objects[i].Flags); + out->WriteInt16(room->MaskResolution); + + out->WriteInt32(MAX_WALK_AREAS + 1); + for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i) + out->WriteInt16(room->WalkAreas[i].ScalingFar); + for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i) + out->WriteInt16(room->WalkAreas[i].Light); + for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i) + out->WriteInt16(room->WalkAreas[i].ScalingNear); + for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i) + out->WriteInt16(room->WalkAreas[i].Top); + for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i) + out->WriteInt16(room->WalkAreas[i].Bottom); + + out->WriteByteCount(0, LEGACY_ROOM_PASSWORD_LENGTH); + out->WriteInt8(room->Options.StartupMusic); + out->WriteInt8(room->Options.SaveLoadDisabled ? 1 : 0); + out->WriteInt8(room->Options.PlayerCharOff ? 1 : 0); + out->WriteInt8(room->Options.PlayerView); + out->WriteInt8(room->Options.MusicVolume); + out->WriteByteCount(0, ROOM_LEGACY_OPTIONS_SIZE - 5); + out->WriteInt16((int16_t)room->MessageCount); + out->WriteInt32(room->GameID); + for (size_t i = 0; i < room->MessageCount; ++i) + { + out->WriteInt8(room->MessageInfos[i].DisplayAs); + out->WriteInt8(room->MessageInfos[i].Flags); + } + for (size_t i = 0; i < room->MessageCount; ++i) + write_string_encrypt(out, room->Messages[i]); + + out->WriteInt16(0); // legacy room animations + + for (size_t i = 0; i < (size_t)MAX_WALK_AREAS + 1; ++i) + out->WriteInt16(room->WalkAreas[i].Light); + for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i) + out->WriteInt16(room->Regions[i].Light); + for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i) + out->WriteInt32(room->Regions[i].Tint); + + save_lzw(out, room->BgFrames[0].Graphic.get(), room->Palette); + savecompressed_allegro(out, room->RegionMask.get(), room->Palette); + savecompressed_allegro(out, room->WalkAreaMask.get(), room->Palette); + savecompressed_allegro(out, room->WalkBehindMask.get(), room->Palette); + savecompressed_allegro(out, room->HotspotMask.get(), room->Palette); +} + +void WriteCompSc3Block(const RoomStruct *room, Stream *out) +{ + room->CompiledScript->Write(out); +} + +void WriteObjNamesBlock(const RoomStruct *room, Stream *out) +{ + out->WriteByte((int8_t)room->ObjectCount); + for (size_t i = 0; i < room->ObjectCount; ++i) + Common::StrUtil::WriteString(room->Objects[i].Name, out); +} + +void WriteObjScNamesBlock(const RoomStruct *room, Stream *out) +{ + out->WriteByte((int8_t)room->ObjectCount); + for (size_t i = 0; i < room->ObjectCount; ++i) + Common::StrUtil::WriteString(room->Objects[i].ScriptName, out); +} + +void WriteAnimBgBlock(const RoomStruct *room, Stream *out) +{ + out->WriteByte((int8_t)room->BgFrameCount); + out->WriteByte(room->BgAnimSpeed); + + for (size_t i = 0; i < room->BgFrameCount; ++i) + out->WriteInt8(room->BgFrames[i].IsPaletteShared ? 1 : 0); + for (size_t i = 1; i < room->BgFrameCount; ++i) + save_lzw(out, room->BgFrames[i].Graphic.get(), room->BgFrames[i].Palette); +} + +void WritePropertiesBlock(const RoomStruct *room, Stream *out) +{ + out->WriteInt32(1); // Version 1 of properties block + Properties::WriteValues(room->Properties, out); + for (size_t i = 0; i < room->HotspotCount; ++i) + Properties::WriteValues(room->Hotspots[i].Properties, out); + for (size_t i = 0; i < room->ObjectCount; ++i) + Properties::WriteValues(room->Objects[i].Properties, out); +} + +HRoomFileError WriteRoomData(const RoomStruct *room, Stream *out, RoomFileVersion data_ver) +{ + if (data_ver < kRoomVersion_Current) + return new RoomFileError(kRoomFileErr_FormatNotSupported, "We no longer support saving room in the older format."); + + // Header + out->WriteInt16(data_ver); + // Main data + WriteBlock(room, kRoomFblk_Main, WriteMainBlock, out); + // Compiled script + if (room->CompiledScript) + WriteBlock(room, kRoomFblk_CompScript3, WriteCompSc3Block, out); + // Object names + if (room->ObjectCount > 0) + { + WriteBlock(room, kRoomFblk_ObjectNames, WriteObjNamesBlock, out); + WriteBlock(room, kRoomFblk_ObjectScNames, WriteObjScNamesBlock, out); + } + // Secondary background frames + if (room->BgFrameCount > 1) + WriteBlock(room, kRoomFblk_AnimBg, WriteAnimBgBlock, out); + // Custom properties + WriteBlock(room, kRoomFblk_Properties, WritePropertiesBlock, out); + + // Write end of room file + out->WriteByte(kRoomFile_EOF); + return HRoomFileError::None(); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/game/room_file.h b/engines/ags/shared/game/room_file.h new file mode 100644 index 00000000000..ce894721ce9 --- /dev/null +++ b/engines/ags/shared/game/room_file.h @@ -0,0 +1,92 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// This unit provides functions for reading main game file into appropriate +// data structures. Main game file contains general game data, such as global +// options, lists of static game entities and compiled scripts modules. +// +//============================================================================= + +#ifndef __AGS_CN_GAME_ROOMFILE_H +#define __AGS_CN_GAME_ROOMFILE_H + +#include +#include +#include "game/room_version.h" +#include "util/error.h" +#include "util/stream.h" +#include "util/string.h" + +struct SpriteInfo; +namespace AGS +{ +namespace Common +{ + +class RoomStruct; + +enum RoomFileErrorType +{ + kRoomFileErr_NoError, + kRoomFileErr_FileOpenFailed, + kRoomFileErr_FormatNotSupported, + kRoomFileErr_UnexpectedEOF, + kRoomFileErr_UnknownBlockType, + kRoomFileErr_OldBlockNotSupported, + kRoomFileErr_BlockDataOverlapping, + kRoomFileErr_IncompatibleEngine, + kRoomFileErr_ScriptLoadFailed, + kRoomFileErr_InconsistentData, + kRoomFileErr_PropertiesBlockFormat, + kRoomFileErr_InvalidPropertyValues, + kRoomFileErr_BlockNotFound +}; + +String GetRoomFileErrorText(RoomFileErrorType err); + +typedef TypedCodeError RoomFileError; +typedef ErrorHandle HRoomFileError; +typedef std::shared_ptr PStream; + + +// RoomDataSource defines a successfully opened room file +struct RoomDataSource +{ + // Name of the asset file + String Filename; + // Room file format version + RoomFileVersion DataVersion; + // A ponter to the opened stream + PStream InputStream; + + RoomDataSource(); +}; + +// Opens room file for reading from an arbitrary file +HRoomFileError OpenRoomFile(const String &filename, RoomDataSource &src); +// Reads room data +HRoomFileError ReadRoomData(RoomStruct *room, Stream *in, RoomFileVersion data_ver); +// Applies necessary updates, conversions and fixups to the loaded data +// making it compatible with current engine +HRoomFileError UpdateRoomData(RoomStruct *room, RoomFileVersion data_ver, bool game_is_hires, const std::vector &sprinfos); +// Extracts text script from the room file, if it's available. +// Historically, text sources were kept inside packed room files before AGS 3.*. +HRoomFileError ExtractScriptText(String &script, Stream *in, RoomFileVersion data_ver); + +HRoomFileError WriteRoomData(const RoomStruct *room, Stream *out, RoomFileVersion data_ver); + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_GAME_ROOMFILE_H diff --git a/engines/ags/shared/game/room_file_deprecated.cpp b/engines/ags/shared/game/room_file_deprecated.cpp new file mode 100644 index 00000000000..4424ddbdc03 --- /dev/null +++ b/engines/ags/shared/game/room_file_deprecated.cpp @@ -0,0 +1,135 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Deprecated room stuff. Removed from room class and load routine because this +// data is no longer supported in the engine. Perhaps move to some legacy +// knowledge base; or restore when it's possible to support ancient games. +// +//============================================================================= + +#if defined (OBSOLETE) + +#include "ac/common.h" +#include "util/stream.h" + +using namespace AGS::Common; + +#define AE_WAITFLAG 0x80000000 +#define MAXANIMSTAGES 10 +struct AnimationStruct +{ + int x, y; + int data; + int object; + int speed; + char action; + char wait; + AnimationStruct() { action = 0; object = 0; wait = 1; speed = 5; } +}; + +struct FullAnimation +{ + AnimationStruct stage[MAXANIMSTAGES]; + int numstages; + FullAnimation() { numstages = 0; } +}; + +#define MAXPOINTS 30 +struct PolyPoints +{ + int x[MAXPOINTS]; + int y[MAXPOINTS]; + int numpoints; + void add_point(int x, int y); + PolyPoints() { numpoints = 0; } + + void Read(AGS::Common::Stream *in); +}; + + +#define MAXANIMS 10 +// Just a list of cut out data +struct DeprecatedRoomStruct +{ + // Full-room animations + int16_t numanims; + FullAnimation anims[MAXANIMS]; + // Polygonal walkable areas (unknown version) + int32_t numwalkareas; + PolyPoints wallpoints[MAX_WALK_AREAS]; + // Unknown flags + int16_t flagstates[MAX_FLAGS]; +}; + + + +void PolyPoints::add_point(int x, int y) +{ + x[numpoints] = x; + y[numpoints] = y; + numpoints++; + + if (numpoints >= MAXPOINTS) + quit("too many poly points added"); +} + +void PolyPoints::Read(Stream *in) +{ + in->ReadArrayOfInt32(x, MAXPOINTS); + in->ReadArrayOfInt32(y, MAXPOINTS); + numpoints = in->ReadInt32(); +} + + +// +// Pre-2.5 scripts (we don't know how to convert them for the modern engine) +// +#define SCRIPT_CONFIG_VERSION 1 +HRoomFileError ReadAncientScriptConfig(Stream *in) +{ + int fmt = in->ReadInt32(); + if (fmt != SCRIPT_CONFIG_VERSION) + return new RoomFileError(kRoomFileErr_FormatNotSupported, String::FromFormat("Invalid script configuration format (in room: %d, expected: %d).", fmt, SCRIPT_CONFIG_VERSION)); + + size_t var_count = in->ReadInt32(); + for (size_t i = 0; i < var_count; ++i) + { + size_t len = in->ReadByte(); + in->Seek(len); + } + return HRoomFileError::None(); +} + +HRoomFileError ReadAncientGraphicalScripts(Stream *in) +{ + do + { + int ct = in->ReadInt32(); + if (ct == -1 || in->EOS()) + break; + size_t len = in->ReadInt32(); + in->Seek(len); + } while (true); + return HRoomFileError::None(); +} + +HRoomFileError ReadPre250Scripts(Stream *in) +{ + HRoomFileError err = ReadAncientScriptConfig(in); + if (err) + err = ReadAncientGraphicalScripts(in); + return err; +} + +#endif // OBSOLETE diff --git a/engines/ags/shared/game/room_version.h b/engines/ags/shared/game/room_version.h new file mode 100644 index 00000000000..0fdd0ea6020 --- /dev/null +++ b/engines/ags/shared/game/room_version.h @@ -0,0 +1,86 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Room version constants and information +// +//============================================================================= + +#ifndef __AGS_CN_AC__ROOMVERSION_H +#define __AGS_CN_AC__ROOMVERSION_H + +/* room file versions history +8: final v1.14 release +9: intermediate v2 alpha releases +10: v2 alpha-7 release +11: final v2.00 release +12: v2.08, to add colour depth byte +13: v2.14, add walkarea light levels +14: v2.4, fixed so it saves walkable area 15 +15: v2.41, supports NewInteraction +16: v2.5 +17: v2.5 - just version change to force room re-compile for new charctr struct +18: v2.51 - vector scaling +19: v2.53 - interaction variables +20: v2.55 - shared palette backgrounds +21: v2.55 - regions +22: v2.61 - encrypt room messages +23: v2.62 - object flags +24: v2.7 - hotspot script names +25: v2.72 - game id embedded +26: v3.0 - new interaction format, and no script source +27: v3.0 - store Y of bottom of object, not top +28: v3.0.3 - remove hotspot name length limit +29: v3.0.3 - high-res coords for object x/y, edges and hotspot walk-to point +30: v3.4.0.4 - tint luminance for regions +31: v3.4.1.5 - removed room object and hotspot name length limits +32: v3.5.0 - 64-bit file offsets +33: v3.5.0.8 - deprecated room resolution, added mask resolution +*/ +enum RoomFileVersion +{ + kRoomVersion_Undefined = 0, + kRoomVersion_pre114_3 = 3, // exact version unknown + kRoomVersion_pre114_4 = 4, // exact version unknown + kRoomVersion_pre114_5 = 5, // exact version unknown + kRoomVersion_pre114_6 = 6, // exact version unknown + kRoomVersion_114 = 8, + kRoomVersion_200_alpha = 9, + kRoomVersion_200_alpha7 = 10, + kRoomVersion_200_final = 11, + kRoomVersion_208 = 12, + kRoomVersion_214 = 13, + kRoomVersion_240 = 14, + kRoomVersion_241 = 15, + kRoomVersion_250a = 16, + kRoomVersion_250b = 17, + kRoomVersion_251 = 18, + kRoomVersion_253 = 19, + kRoomVersion_255a = 20, + kRoomVersion_255b = 21, + kRoomVersion_261 = 22, + kRoomVersion_262 = 23, + kRoomVersion_270 = 24, + kRoomVersion_272 = 25, + kRoomVersion_300a = 26, + kRoomVersion_300b = 27, + kRoomVersion_303a = 28, + kRoomVersion_303b = 29, + kRoomVersion_3404 = 30, + kRoomVersion_3415 = 31, + kRoomVersion_350 = 32, + kRoomVersion_3508 = 33, + kRoomVersion_Current = kRoomVersion_3508 +}; + +#endif // __AGS_CN_AC__ROOMVERSION_H diff --git a/engines/ags/shared/game/roomstruct.cpp b/engines/ags/shared/game/roomstruct.cpp new file mode 100644 index 00000000000..168ed387514 --- /dev/null +++ b/engines/ags/shared/game/roomstruct.cpp @@ -0,0 +1,325 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" // update_polled_stuff_if_runtime +#include "game/room_file.h" +#include "game/roomstruct.h" +#include "gfx/bitmap.h" + +namespace AGS +{ +namespace Common +{ + +RoomOptions::RoomOptions() + : StartupMusic(0) + , SaveLoadDisabled(false) + , PlayerCharOff(false) + , PlayerView(0) + , MusicVolume(kRoomVolumeNormal) +{ +} + +RoomBgFrame::RoomBgFrame() + : IsPaletteShared(false) +{ + memset(Palette, 0, sizeof(Palette)); +} + +RoomEdges::RoomEdges() + : Left(0) + , Right(0) + , Top(0) + , Bottom(0) +{ +} + +RoomEdges::RoomEdges(int l, int r, int t, int b) + : Left(l) + , Right(r) + , Top(t) + , Bottom(b) +{ +} + +RoomObjectInfo::RoomObjectInfo() + : Sprite(0) + , X(0) + , Y(0) + , Room(-1) + , IsOn(false) + , Baseline(0xFF) + , Flags(0) +{ +} + +RoomRegion::RoomRegion() + : Light(0) + , Tint(0) +{ +} + +WalkArea::WalkArea() + : CharacterView(0) + , ScalingFar(0) + , ScalingNear(NOT_VECTOR_SCALED) + , Light(0) + , Top(-1) + , Bottom(-1) +{ +} + +WalkBehind::WalkBehind() + : Baseline(0) +{ +} + +MessageInfo::MessageInfo() + : DisplayAs(0) + , Flags(0) +{ +} + +RoomStruct::RoomStruct() +{ + InitDefaults(); +} + +RoomStruct::~RoomStruct() +{ + Free(); +} + +void RoomStruct::Free() +{ + for (size_t i = 0; i < (size_t)MAX_ROOM_BGFRAMES; ++i) + BgFrames[i].Graphic.reset(); + HotspotMask.reset(); + RegionMask.reset(); + WalkAreaMask.reset(); + WalkBehindMask.reset(); + + LocalVariables.clear(); + Interaction.reset(); + Properties.clear(); + for (size_t i = 0; i < (size_t)MAX_ROOM_HOTSPOTS; ++i) + { + Hotspots[i].Interaction.reset(); + Hotspots[i].Properties.clear(); + } + for (size_t i = 0; i < (size_t)MAX_ROOM_OBJECTS; ++i) + { + Objects[i].Interaction.reset(); + Objects[i].Properties.clear(); + } + for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i) + { + Regions[i].Interaction.reset(); + Regions[i].Properties.clear(); + } + + FreeMessages(); + FreeScripts(); +} + +void RoomStruct::FreeMessages() +{ + for (size_t i = 0; i < MessageCount; ++i) + { + Messages[i].Free(); + MessageInfos[i] = MessageInfo(); + } + MessageCount = 0; +} + +void RoomStruct::FreeScripts() +{ + CompiledScript.reset(); + + EventHandlers.reset(); + for (size_t i = 0; i < HotspotCount; ++i) + Hotspots[i].EventHandlers.reset(); + for (size_t i = 0; i < ObjectCount; ++i) + Objects[i].EventHandlers.reset(); + for (size_t i = 0; i < RegionCount; ++i) + Regions[i].EventHandlers.reset(); +} + +void RoomStruct::InitDefaults() +{ + DataVersion = kRoomVersion_Current; + GameID = NO_GAME_ID_IN_ROOM_FILE; + + _resolution = kRoomRealRes; + MaskResolution = 1; + Width = 320; + Height = 200; + + Options = RoomOptions(); + Edges = RoomEdges(0, 317, 40, 199); + + BgFrameCount = 1; + HotspotCount = 0; + ObjectCount = 0; + RegionCount = 0; + WalkAreaCount = 0; + WalkBehindCount = 0; + MessageCount = 0; + + for (size_t i = 0; i < (size_t)MAX_ROOM_HOTSPOTS; ++i) + { + Hotspots[i] = RoomHotspot(); + if (i == 0) + Hotspots[i].Name = "No hotspot"; + else + Hotspots[i].Name.Format("Hotspot %u", i); + } + for (size_t i = 0; i < (size_t)MAX_ROOM_OBJECTS; ++i) + Objects[i] = RoomObjectInfo(); + for (size_t i = 0; i < (size_t)MAX_ROOM_REGIONS; ++i) + Regions[i] = RoomRegion(); + for (size_t i = 0; i <= (size_t)MAX_WALK_AREAS; ++i) + WalkAreas[i] = WalkArea(); + for (size_t i = 0; i < (size_t)MAX_WALK_BEHINDS; ++i) + WalkBehinds[i] = WalkBehind(); + + BackgroundBPP = 1; + BgAnimSpeed = 5; + + memset(Palette, 0, sizeof(Palette)); +} + +void RoomStruct::SetResolution(RoomResolutionType type) +{ + _resolution = type; +} + +Bitmap *RoomStruct::GetMask(RoomAreaMask mask) const +{ + switch (mask) + { + case kRoomAreaHotspot: return HotspotMask.get(); + case kRoomAreaWalkBehind: return WalkBehindMask.get(); + case kRoomAreaWalkable: return WalkAreaMask.get(); + case kRoomAreaRegion: return RegionMask.get(); + } + return nullptr; +} + +float RoomStruct::GetMaskScale(RoomAreaMask mask) const +{ + switch (mask) + { + case kRoomAreaWalkBehind: return 1.f; // walk-behinds always 1:1 with room size + case kRoomAreaHotspot: + case kRoomAreaWalkable: + case kRoomAreaRegion: + return 1.f / MaskResolution; + } + return 0.f; +} + +bool RoomStruct::HasRegionLightLevel(int id) const +{ + if (id >= 0 && id < MAX_ROOM_REGIONS) + return Regions[id].Tint == 0; + return false; +} + +bool RoomStruct::HasRegionTint(int id) const +{ + if (id >= 0 && id < MAX_ROOM_REGIONS) + return Regions[id].Tint != 0; + return false; +} + +int RoomStruct::GetRegionLightLevel(int id) const +{ + if (id >= 0 && id < MAX_ROOM_REGIONS) + return HasRegionLightLevel(id) ? Regions[id].Light : 0; + return 0; +} + +int RoomStruct::GetRegionTintLuminance(int id) const +{ + if (id >= 0 && id < MAX_ROOM_REGIONS) + return HasRegionTint(id) ? (Regions[id].Light * 10) / 25 : 0; + return 0; +} + +void load_room(const char *filename, RoomStruct *room, bool game_is_hires, const std::vector &sprinfos) +{ + room->Free(); + room->InitDefaults(); + + update_polled_stuff_if_runtime(); + + RoomDataSource src; + HRoomFileError err = OpenRoomFile(filename, src); + if (err) + { + update_polled_stuff_if_runtime(); // it can take a while to load the file sometimes + err = ReadRoomData(room, src.InputStream.get(), src.DataVersion); + if (err) + err = UpdateRoomData(room, src.DataVersion, game_is_hires, sprinfos); + } + if (!err) + quitprintf("Unable to load the room file '%s'.\n%s.", filename, err->FullMessage().GetCStr()); +} + +PBitmap FixBitmap(PBitmap bmp, int width, int height) +{ + Bitmap *new_bmp = BitmapHelper::AdjustBitmapSize(bmp.get(), width, height); + if (new_bmp != bmp.get()) + return PBitmap(new_bmp); + return bmp; +} + +void UpscaleRoomBackground(RoomStruct *room, bool game_is_hires) +{ + if (room->DataVersion >= kRoomVersion_303b || !game_is_hires) + return; + for (size_t i = 0; i < room->BgFrameCount; ++i) + room->BgFrames[i].Graphic = FixBitmap(room->BgFrames[i].Graphic, room->Width, room->Height); + FixRoomMasks(room); +} + +void FixRoomMasks(RoomStruct *room) +{ + if (room->MaskResolution <= 0) + return; + Bitmap *bkg = room->BgFrames[0].Graphic.get(); + if (bkg == nullptr) + return; + // TODO: this issue is somewhat complicated. Original code was relying on + // room->Width and Height properties. But in the engine these are saved + // already converted to data resolution which may be "low-res". Since this + // function is shared between engine and editor we do not know if we need + // to upscale them. + // For now room width/height is always equal to background bitmap. + int base_width = bkg->GetWidth(); + int base_height = bkg->GetHeight(); + int low_width = base_width / room->MaskResolution; + int low_height = base_height / room->MaskResolution; + + // Walk-behinds are always 1:1 of the primary background. + // Other masks are 1:x where X is MaskResolution. + room->WalkBehindMask = FixBitmap(room->WalkBehindMask, base_width, base_height); + room->HotspotMask = FixBitmap(room->HotspotMask, low_width, low_height); + room->RegionMask = FixBitmap(room->RegionMask, low_width, low_height); + room->WalkAreaMask = FixBitmap(room->WalkAreaMask, low_width, low_height); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/game/roomstruct.h b/engines/ags/shared/game/roomstruct.h new file mode 100644 index 00000000000..95e2f151d15 --- /dev/null +++ b/engines/ags/shared/game/roomstruct.h @@ -0,0 +1,386 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// RoomStruct, a class describing initial room data. +// +// Because of the imperfect implementation there is inconsistency in how +// this data is interpreted at the runtime. +// Some of that data is never supposed to be changed at runtime. Another +// may be changed, but these changes are lost as soon as room is unloaded. +// The changes that must remain in memory are kept as separate classes: +// see RoomStatus, RoomObject etc. +// +// Partially this is because same class was used for both engine and editor, +// while runtime code was not available for the editor. +// +// This is also the reason why some classes here are named with the "Info" +// postfix. For example, RoomObjectInfo is the initial object data, and +// there is also RoomObject runtime-only class for mutable data. +// +// [ivan-mogilko] In my opinion, eventually there should be only one room class +// and one class per room entity, regardless of whether code is shared with +// the editor or not. But that would require extensive refactor/rewrite of +// the engine code, and savegame read/write code. +// +//============================================================================= +#ifndef __AGS_CN_GAME__ROOMINFO_H +#define __AGS_CN_GAME__ROOMINFO_H + +#include +#include "ac/common_defines.h" +#include "game/interactions.h" +#include "util/geometry.h" +#include "util/wgt2allg.h" // color (allegro RGB) + +struct ccScript; +struct SpriteInfo; +typedef std::shared_ptr PScript; + +// TODO: move the following enums under AGS::Common namespace +// later, when more engine source is put in AGS namespace and +// refactored. + +// Room's area mask type +enum RoomAreaMask +{ + kRoomAreaNone = 0, + kRoomAreaHotspot, + kRoomAreaWalkBehind, + kRoomAreaWalkable, + kRoomAreaRegion +}; + +// Room's audio volume modifier +enum RoomVolumeMod +{ + kRoomVolumeQuietest = -3, + kRoomVolumeQuieter = -2, + kRoomVolumeQuiet = -1, + kRoomVolumeNormal = 0, + kRoomVolumeLoud = 1, + kRoomVolumeLouder = 2, + kRoomVolumeLoudest = 3, + // These two options are only settable at runtime by SetMusicVolume() + kRoomVolumeExtra1 = 4, + kRoomVolumeExtra2 = 5, + + kRoomVolumeMin = kRoomVolumeQuietest, + kRoomVolumeMax = kRoomVolumeExtra2, +}; + +// Flag tells that walkable area does not have continious zoom +#define NOT_VECTOR_SCALED -10000 +// Flags tells that room is not linked to particular game ID +#define NO_GAME_ID_IN_ROOM_FILE 16325 + +#define MAX_ROOM_BGFRAMES 5 // max number of frames in animating bg scene + +#define MAX_ROOM_HOTSPOTS 50 // v2.62 increased from 20 to 30; v2.8 to 50 +#define MAX_ROOM_OBJECTS 40 +#define MAX_ROOM_REGIONS 16 +// TODO: this is remains of the older code, MAX_WALK_AREAS = real number - 1, where +// -1 is for the solid wall. When fixing this you need to be careful, because some +// walk-area indexes are 0-based and some 1-based (and some arrays have MAX_WALK_AREAS + 1 size) +#define MAX_WALK_AREAS 15 +#define MAX_WALK_BEHINDS 16 + +#define MAX_MESSAGES 100 + + +namespace AGS +{ +namespace Common +{ + +class Bitmap; +class Stream; + +typedef std::shared_ptr PBitmap; + +// Various room options +struct RoomOptions +{ + // Index of the startup music in the room + int StartupMusic; + // If saving and loading game is disabled in the room + bool SaveLoadDisabled; + // If player character is turned off in the room + bool PlayerCharOff; + // Apply player character's normal view when entering this room + int PlayerView; + // Room's music volume modifier + RoomVolumeMod MusicVolume; + + RoomOptions(); +}; + +// Single room background frame +struct RoomBgFrame +{ + PBitmap Graphic; + // Palette is only valid in 8-bit games + color Palette[256]; + // Tells if this frame should keep previous frame palette instead of using its own + bool IsPaletteShared; + + RoomBgFrame(); +}; + +// Describes room edges (coordinates of four edges) +struct RoomEdges +{ + int32_t Left; + int32_t Right; + int32_t Top; + int32_t Bottom; + + RoomEdges(); + RoomEdges(int l, int r, int t, int b); +}; + +// Room hotspot description +struct RoomHotspot +{ + String Name; + String ScriptName; + // Custom properties + StringIMap Properties; + // Old-style interactions + PInteraction Interaction; + // Event script links + PInteractionScripts EventHandlers; + + // Player will automatically walk here when interacting with hotspot + Point WalkTo; +}; + +// Room object description +struct RoomObjectInfo +{ + int32_t Room; + int32_t X; + int32_t Y; + int32_t Sprite; + bool IsOn; + // Object's z-order in the room, or -1 (use Y) + int32_t Baseline; + int32_t Flags; + String Name; + String ScriptName; + // Custom properties + StringIMap Properties; + // Old-style interactions + PInteraction Interaction; + // Event script links + PInteractionScripts EventHandlers; + + RoomObjectInfo(); +}; + +// Room region description +struct RoomRegion +{ + // Light level (-100 -> +100) or Tint luminance (0 - 255) + int32_t Light; + // Tint setting (R-B-G-S) + int32_t Tint; + // Custom properties + StringIMap Properties; + // Old-style interactions + PInteraction Interaction; + // Event script links + PInteractionScripts EventHandlers; + + RoomRegion(); +}; + +// Walkable area description +struct WalkArea +{ + // Apply player character's normal view on this area + int32_t CharacterView; + // Character's scaling (-100 -> +100 %) + // General scaling, or scaling at the farthest point + int32_t ScalingFar; + // Scaling at the nearest point, or NOT_VECTOR_SCALED for uniform scaling + int32_t ScalingNear; + // Light level (-100 -> +100) + int32_t Light; + // Top and bottom Y of the area + int32_t Top; + int32_t Bottom; + + WalkArea(); +}; + +// Walk-behind description +struct WalkBehind +{ + // Object's z-order in the room + int32_t Baseline; + + WalkBehind(); +}; + +// Room messages + +#define MSG_DISPLAYNEXT 0x01 // supercedes using alt-200 at end of message +#define MSG_TIMELIMIT 0x02 + +struct MessageInfo +{ + char DisplayAs; // 0 - std display window, >=1 - as character's speech + char Flags; // combination of MSG_xxx flags + + MessageInfo(); +}; + + +// Room's legacy resolution type +enum RoomResolutionType +{ + kRoomRealRes = 0, // room should always be treated as-is + kRoomLoRes = 1, // created for low-resolution game + kRoomHiRes = 2 // created for high-resolution game +}; + + +// +// Description of a single room. +// This class contains initial room data. Some of it may still be modified +// at the runtime, but then these changes get lost as soon as room is unloaded. +// +class RoomStruct +{ +public: + RoomStruct(); + ~RoomStruct(); + + // Gets if room should adjust its base size depending on game's resolution + inline bool IsRelativeRes() const { return _resolution != kRoomRealRes; } + // Gets if room belongs to high resolution + inline bool IsLegacyHiRes() const { return _resolution == kRoomHiRes; } + // Gets legacy resolution type + inline RoomResolutionType GetResolutionType() const { return _resolution; } + + // Releases room resources + void Free(); + // Release room messages and scripts correspondingly. These two functions are needed + // at very specific occasion when only part of the room resources has to be freed. + void FreeMessages(); + void FreeScripts(); + // Init default room state + void InitDefaults(); + // Set legacy resolution type + void SetResolution(RoomResolutionType type); + + // Gets bitmap of particular mask layer + Bitmap *GetMask(RoomAreaMask mask) const; + // Gets mask's scale relative to the room's background size + float GetMaskScale(RoomAreaMask mask) const; + + // TODO: see later whether it may be more convenient to move these to the Region class instead. + // Gets if the given region has light level set + bool HasRegionLightLevel(int id) const; + // Gets if the given region has a tint set + bool HasRegionTint(int id) const; + // Gets region's light level in -100 to 100 range value; returns 0 (default level) if region's tint is set + int GetRegionLightLevel(int id) const; + // Gets region's tint luminance in 0 to 100 range value; returns 0 if region's light level is set + int GetRegionTintLuminance(int id) const; + +// TODO: all members are currently public because they are used everywhere; hide them later +public: + // Game's unique ID, corresponds to GameSetupStructBase::uniqueid. + // If this field has a valid value and does not match actual game's id, + // then engine will refuse to start this room. + // May be set to NO_GAME_ID_IN_ROOM_FILE to let it run within any game. + int32_t GameID; + // Loaded room file's data version. This value may be used to know when + // the room must have behavior specific to certain version of AGS. + int32_t DataVersion; + + // Room region masks resolution. Defines the relation between room and mask units. + // Mask point is calculated as roompt / MaskResolution. Must be >= 1. + int32_t MaskResolution; + // Size of the room, in logical coordinates (= pixels) + int32_t Width; + int32_t Height; + // Primary room palette (8-bit games) + color Palette[256]; + + // Basic room options + RoomOptions Options; + + // Background frames + int32_t BackgroundBPP; // bytes per pixel + size_t BgFrameCount; + RoomBgFrame BgFrames[MAX_ROOM_BGFRAMES]; + // Speed at which background frames are changing, 0 - no auto animation + int32_t BgAnimSpeed; + // Edges + RoomEdges Edges; + // Region masks + PBitmap HotspotMask; + PBitmap RegionMask; + PBitmap WalkAreaMask; + PBitmap WalkBehindMask; + // Room entities + size_t HotspotCount; + RoomHotspot Hotspots[MAX_ROOM_HOTSPOTS]; + size_t ObjectCount; + RoomObjectInfo Objects[MAX_ROOM_OBJECTS]; + size_t RegionCount; + RoomRegion Regions[MAX_ROOM_REGIONS]; + size_t WalkAreaCount; + WalkArea WalkAreas[MAX_WALK_AREAS + 1]; + size_t WalkBehindCount; + WalkBehind WalkBehinds[MAX_WALK_BEHINDS]; + + // Old numbered room messages (used with DisplayMessage, etc) + size_t MessageCount; + String Messages[MAX_MESSAGES]; + MessageInfo MessageInfos[MAX_MESSAGES]; + + // Custom properties + StringIMap Properties; + // Old-style interactions + InterVarVector LocalVariables; + PInteraction Interaction; + // Event script links + PInteractionScripts EventHandlers; + // Compiled room script + PScript CompiledScript; + +private: + // Room's legacy resolution type, defines relation room and game's resolution + RoomResolutionType _resolution; +}; + + +// Loads new room data into the given RoomStruct object +void load_room(const char *filename, RoomStruct *room, bool game_is_hires, const std::vector &sprinfos); +// Checks if it's necessary and upscales low-res room backgrounds and masks for the high resolution game +// NOTE: it does not upscale object coordinates, because that is usually done when the room is loaded +void UpscaleRoomBackground(RoomStruct *room, bool game_is_hires); +// Ensures that all existing room masks match room background size and +// MaskResolution property, resizes mask bitmaps if necessary. +void FixRoomMasks(RoomStruct *room); +// Adjusts bitmap size if necessary and returns either new or old bitmap. +PBitmap FixBitmap(PBitmap bmp, int dst_width, int dst_height); + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_GAME__ROOMINFO_H diff --git a/engines/ags/shared/gfx/allegrobitmap.cpp b/engines/ags/shared/gfx/allegrobitmap.cpp new file mode 100644 index 00000000000..f5ac74787db --- /dev/null +++ b/engines/ags/shared/gfx/allegrobitmap.cpp @@ -0,0 +1,502 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "gfx/allegrobitmap.h" +#include "debug/assert.h" + +extern void __my_setcolor(int *ctset, int newcol, int wantColDep); + +namespace AGS +{ +namespace Common +{ + +Bitmap::Bitmap() + : _alBitmap(nullptr) + , _isDataOwner(false) +{ +} + +Bitmap::Bitmap(int width, int height, int color_depth) + : _alBitmap(nullptr) + , _isDataOwner(false) +{ + Create(width, height, color_depth); +} + +Bitmap::Bitmap(Bitmap *src, const Rect &rc) + : _alBitmap(nullptr) + , _isDataOwner(false) +{ + CreateSubBitmap(src, rc); +} + +Bitmap::Bitmap(BITMAP *al_bmp, bool shared_data) + : _alBitmap(nullptr) + , _isDataOwner(false) +{ + WrapAllegroBitmap(al_bmp, shared_data); +} + +Bitmap::~Bitmap() +{ + Destroy(); +} + +//============================================================================= +// Creation and destruction +//============================================================================= + +bool Bitmap::Create(int width, int height, int color_depth) +{ + Destroy(); + if (color_depth) + { + _alBitmap = create_bitmap_ex(color_depth, width, height); + } + else + { + _alBitmap = create_bitmap(width, height); + } + _isDataOwner = true; + return _alBitmap != nullptr; +} + +bool Bitmap::CreateTransparent(int width, int height, int color_depth) +{ + if (Create(width, height, color_depth)) + { + clear_to_color(_alBitmap, bitmap_mask_color(_alBitmap)); + return true; + } + return false; +} + +bool Bitmap::CreateSubBitmap(Bitmap *src, const Rect &rc) +{ + Destroy(); + _alBitmap = create_sub_bitmap(src->_alBitmap, rc.Left, rc.Top, rc.GetWidth(), rc.GetHeight()); + _isDataOwner = true; + return _alBitmap != nullptr; +} + +bool Bitmap::CreateCopy(Bitmap *src, int color_depth) +{ + if (Create(src->_alBitmap->w, src->_alBitmap->h, color_depth ? color_depth : bitmap_color_depth(src->_alBitmap))) + { + blit(src->_alBitmap, _alBitmap, 0, 0, 0, 0, _alBitmap->w, _alBitmap->h); + return true; + } + return false; +} + +bool Bitmap::WrapAllegroBitmap(BITMAP *al_bmp, bool shared_data) +{ + Destroy(); + _alBitmap = al_bmp; + _isDataOwner = !shared_data; + return _alBitmap != nullptr; +} + +void Bitmap::Destroy() +{ + if (_isDataOwner && _alBitmap) + { + destroy_bitmap(_alBitmap); + } + _alBitmap = nullptr; + _isDataOwner = false; +} + +bool Bitmap::LoadFromFile(const char *filename) +{ + Destroy(); + + BITMAP *al_bmp = load_bitmap(filename, nullptr); + if (al_bmp) + { + _alBitmap = al_bmp; + _isDataOwner = true; + } + return _alBitmap != nullptr; +} + +bool Bitmap::SaveToFile(const char *filename, const void *palette) +{ + return save_bitmap(filename, _alBitmap, (const RGB*)palette) == 0; +} + +void Bitmap::SetMaskColor(color_t color) +{ + // not supported? CHECKME +} + +void Bitmap::Acquire() +{ + acquire_bitmap(_alBitmap); +} + +void Bitmap::Release() +{ + release_bitmap(_alBitmap); +} + +color_t Bitmap::GetCompatibleColor(color_t color) +{ + color_t compat_color = 0; + __my_setcolor(&compat_color, color, bitmap_color_depth(_alBitmap)); + return compat_color; +} + +//============================================================================= +// Clipping +//============================================================================= + +void Bitmap::SetClip(const Rect &rc) +{ + set_clip_rect(_alBitmap, rc.Left, rc.Top, rc.Right, rc.Bottom); +} + +Rect Bitmap::GetClip() const +{ + Rect temp; + get_clip_rect(_alBitmap, &temp.Left, &temp.Top, &temp.Right, &temp.Bottom); + return temp; +} + +//============================================================================= +// Blitting operations (drawing one bitmap over another) +//============================================================================= + +void Bitmap::Blit(Bitmap *src, int dst_x, int dst_y, BitmapMaskOption mask) +{ + BITMAP *al_src_bmp = src->_alBitmap; + // WARNING: For some evil reason Allegro expects dest and src bitmaps in different order for blit and draw_sprite + if (mask == kBitmap_Transparency) + { + draw_sprite(_alBitmap, al_src_bmp, dst_x, dst_y); + } + else + { + blit(al_src_bmp, _alBitmap, 0, 0, dst_x, dst_y, al_src_bmp->w, al_src_bmp->h); + } +} + +void Bitmap::Blit(Bitmap *src, int src_x, int src_y, int dst_x, int dst_y, int width, int height, BitmapMaskOption mask) +{ + BITMAP *al_src_bmp = src->_alBitmap; + if (mask == kBitmap_Transparency) + { + masked_blit(al_src_bmp, _alBitmap, src_x, src_y, dst_x, dst_y, width, height); + } + else + { + blit(al_src_bmp, _alBitmap, src_x, src_y, dst_x, dst_y, width, height); + } +} + +void Bitmap::MaskedBlit(Bitmap *src, int dst_x, int dst_y) +{ + draw_sprite(_alBitmap, src->_alBitmap, dst_x, dst_y); +} + +void Bitmap::StretchBlt(Bitmap *src, const Rect &dst_rc, BitmapMaskOption mask) +{ + BITMAP *al_src_bmp = src->_alBitmap; + // WARNING: For some evil reason Allegro expects dest and src bitmaps in different order for blit and draw_sprite + if (mask == kBitmap_Transparency) + { + stretch_sprite(_alBitmap, al_src_bmp, + dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight()); + } + else + { + stretch_blit(al_src_bmp, _alBitmap, + 0, 0, al_src_bmp->w, al_src_bmp->h, + dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight()); + } +} + +void Bitmap::StretchBlt(Bitmap *src, const Rect &src_rc, const Rect &dst_rc, BitmapMaskOption mask) +{ + BITMAP *al_src_bmp = src->_alBitmap; + if (mask == kBitmap_Transparency) + { + masked_stretch_blit(al_src_bmp, _alBitmap, + src_rc.Left, src_rc.Top, src_rc.GetWidth(), src_rc.GetHeight(), + dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight()); + } + else + { + stretch_blit(al_src_bmp, _alBitmap, + src_rc.Left, src_rc.Top, src_rc.GetWidth(), src_rc.GetHeight(), + dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight()); + } +} + +void Bitmap::AAStretchBlt(Bitmap *src, const Rect &dst_rc, BitmapMaskOption mask) +{ + BITMAP *al_src_bmp = src->_alBitmap; + // WARNING: For some evil reason Allegro expects dest and src bitmaps in different order for blit and draw_sprite + if (mask == kBitmap_Transparency) + { + aa_stretch_sprite(_alBitmap, al_src_bmp, + dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight()); + } + else + { + aa_stretch_blit(al_src_bmp, _alBitmap, + 0, 0, al_src_bmp->w, al_src_bmp->h, + dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight()); + } +} + +void Bitmap::AAStretchBlt(Bitmap *src, const Rect &src_rc, const Rect &dst_rc, BitmapMaskOption mask) +{ + BITMAP *al_src_bmp = src->_alBitmap; + if (mask == kBitmap_Transparency) + { + // TODO: aastr lib does not expose method for masked stretch blit; should do that at some point since + // the source code is a gift-ware anyway + // aa_masked_blit(_alBitmap, al_src_bmp, src_rc.Left, src_rc.Top, src_rc.GetWidth(), src_rc.GetHeight(), dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight()); + throw "aa_masked_blit is not yet supported!"; + } + else + { + aa_stretch_blit(al_src_bmp, _alBitmap, + src_rc.Left, src_rc.Top, src_rc.GetWidth(), src_rc.GetHeight(), + dst_rc.Left, dst_rc.Top, dst_rc.GetWidth(), dst_rc.GetHeight()); + } +} + +void Bitmap::TransBlendBlt(Bitmap *src, int dst_x, int dst_y) +{ + BITMAP *al_src_bmp = src->_alBitmap; + draw_trans_sprite(_alBitmap, al_src_bmp, dst_x, dst_y); +} + +void Bitmap::LitBlendBlt(Bitmap *src, int dst_x, int dst_y, int light_amount) +{ + BITMAP *al_src_bmp = src->_alBitmap; + draw_lit_sprite(_alBitmap, al_src_bmp, dst_x, dst_y, light_amount); +} + +void Bitmap::FlipBlt(Bitmap *src, int dst_x, int dst_y, BitmapFlip flip) +{ + BITMAP *al_src_bmp = src->_alBitmap; + if (flip == kBitmap_HFlip) + { + draw_sprite_h_flip(_alBitmap, al_src_bmp, dst_x, dst_y); + } + else if (flip == kBitmap_VFlip) + { + draw_sprite_v_flip(_alBitmap, al_src_bmp, dst_x, dst_y); + } + else if (flip == kBitmap_HVFlip) + { + draw_sprite_vh_flip(_alBitmap, al_src_bmp, dst_x, dst_y); + } +} + +void Bitmap::RotateBlt(Bitmap *src, int dst_x, int dst_y, fixed_t angle) +{ + BITMAP *al_src_bmp = src->_alBitmap; + rotate_sprite(_alBitmap, al_src_bmp, dst_x, dst_y, angle); +} + +void Bitmap::RotateBlt(Bitmap *src, int dst_x, int dst_y, int pivot_x, int pivot_y, fixed_t angle) +{ + BITMAP *al_src_bmp = src->_alBitmap; + pivot_sprite(_alBitmap, al_src_bmp, dst_x, dst_y, pivot_x, pivot_y, angle); +} + +//============================================================================= +// Pixel operations +//============================================================================= + +void Bitmap::Clear(color_t color) +{ + if (color) + { + clear_to_color(_alBitmap, color); + } + else + { + clear_bitmap(_alBitmap); + } +} + +void Bitmap::ClearTransparent() +{ + clear_to_color(_alBitmap, bitmap_mask_color(_alBitmap)); +} + +void Bitmap::PutPixel(int x, int y, color_t color) +{ + if (x < 0 || x >= _alBitmap->w || y < 0 || y >= _alBitmap->h) + { + return; + } + + switch (bitmap_color_depth(_alBitmap)) + { + case 8: + return _putpixel(_alBitmap, x, y, color); + case 15: + return _putpixel15(_alBitmap, x, y, color); + case 16: + return _putpixel16(_alBitmap, x, y, color); + case 24: + return _putpixel24(_alBitmap, x, y, color); + case 32: + return _putpixel32(_alBitmap, x, y, color); + } + assert(0); // this should not normally happen + return putpixel(_alBitmap, x, y, color); +} + +int Bitmap::GetPixel(int x, int y) const +{ + if (x < 0 || x >= _alBitmap->w || y < 0 || y >= _alBitmap->h) + { + return -1; // Allegros getpixel() implementation returns -1 in this case + } + + switch (bitmap_color_depth(_alBitmap)) + { + case 8: + return _getpixel(_alBitmap, x, y); + case 15: + return _getpixel15(_alBitmap, x, y); + case 16: + return _getpixel16(_alBitmap, x, y); + case 24: + return _getpixel24(_alBitmap, x, y); + case 32: + return _getpixel32(_alBitmap, x, y); + } + assert(0); // this should not normally happen + return getpixel(_alBitmap, x, y); +} + +//============================================================================= +// Vector drawing operations +//============================================================================= + +void Bitmap::DrawLine(const Line &ln, color_t color) +{ + line(_alBitmap, ln.X1, ln.Y1, ln.X2, ln.Y2, color); +} + +void Bitmap::DrawTriangle(const Triangle &tr, color_t color) +{ + triangle(_alBitmap, + tr.X1, tr.Y1, tr.X2, tr.Y2, tr.X3, tr.Y3, color); +} + +void Bitmap::DrawRect(const Rect &rc, color_t color) +{ + rect(_alBitmap, rc.Left, rc.Top, rc.Right, rc.Bottom, color); +} + +void Bitmap::FillRect(const Rect &rc, color_t color) +{ + rectfill(_alBitmap, rc.Left, rc.Top, rc.Right, rc.Bottom, color); +} + +void Bitmap::FillCircle(const Circle &circle, color_t color) +{ + circlefill(_alBitmap, circle.X, circle.Y, circle.Radius, color); +} + +void Bitmap::Fill(color_t color) +{ + if (color) + { + clear_to_color(_alBitmap, color); + } + else + { + clear_bitmap(_alBitmap); + } +} + +void Bitmap::FillTransparent() +{ + clear_to_color(_alBitmap, bitmap_mask_color(_alBitmap)); +} + +void Bitmap::FloodFill(int x, int y, color_t color) +{ + floodfill(_alBitmap, x, y, color); +} + +//============================================================================= +// Direct access operations +//============================================================================= + +void Bitmap::SetScanLine(int index, unsigned char *data, int data_size) +{ + if (index < 0 || index >= GetHeight()) + { + return; + } + + int copy_length = data_size; + if (copy_length < 0) + { + copy_length = GetLineLength(); + } + else // TODO: use Math namespace here + if (copy_length > GetLineLength()) + { + copy_length = GetLineLength(); + } + + memcpy(_alBitmap->line[index], data, copy_length); +} + + + +namespace BitmapHelper +{ + +Bitmap *CreateRawBitmapOwner(BITMAP *al_bmp) +{ + Bitmap *bitmap = new Bitmap(); + if (!bitmap->WrapAllegroBitmap(al_bmp, false)) + { + delete bitmap; + bitmap = nullptr; + } + return bitmap; +} + +Bitmap *CreateRawBitmapWrapper(BITMAP *al_bmp) +{ + Bitmap *bitmap = new Bitmap(); + if (!bitmap->WrapAllegroBitmap(al_bmp, true)) + { + delete bitmap; + bitmap = nullptr; + } + return bitmap; +} + +} // namespace BitmapHelper + + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gfx/allegrobitmap.h b/engines/ags/shared/gfx/allegrobitmap.h new file mode 100644 index 00000000000..66adc149f4c --- /dev/null +++ b/engines/ags/shared/gfx/allegrobitmap.h @@ -0,0 +1,249 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Allegro lib based bitmap +// +// TODO: probably should be moved to the Engine; check again when (if) it is +// clear that AGS.Native does not need allegro for drawing. +// +//============================================================================= +#ifndef __AGS_CN_GFX__ALLEGROBITMAP_H +#define __AGS_CN_GFX__ALLEGROBITMAP_H + +#include +#include "core/types.h" +#include "gfx/bitmap.h" + +namespace AGS +{ +namespace Common +{ + +class Bitmap +{ +public: + Bitmap(); + Bitmap(int width, int height, int color_depth = 0); + Bitmap(Bitmap *src, const Rect &rc); + Bitmap(BITMAP *al_bmp, bool shared_data); + ~Bitmap(); + + // Allocate new bitmap + // CHECKME: color_depth = 0 is used to call Allegro's create_bitmap, which uses + // some global color depth setting; not sure if this is OK to use for generic class, + // revise this in future + bool Create(int width, int height, int color_depth = 0); + bool CreateTransparent(int width, int height, int color_depth = 0); + // Allow this object to share existing bitmap data + bool CreateSubBitmap(Bitmap *src, const Rect &rc); + // Create a copy of given bitmap + bool CreateCopy(Bitmap *src, int color_depth = 0); + // TODO: a temporary solution for plugin support + bool WrapAllegroBitmap(BITMAP *al_bmp, bool shared_data); + // Deallocate bitmap + void Destroy(); + + bool LoadFromFile(const char *filename); + bool SaveToFile(const char *filename, const void *palette); + + // TODO: This is temporary solution for cases when we cannot replace + // use of raw BITMAP struct with Bitmap + inline BITMAP *GetAllegroBitmap() + { + return _alBitmap; + } + + // Is this a "normal" bitmap created by application which data can be directly accessed for reading and writing + inline bool IsMemoryBitmap() const + { + return is_memory_bitmap(_alBitmap) != 0; + } + // Is this a video bitmap + inline bool IsVideoBitmap() const + { + return is_video_bitmap(_alBitmap) != 0; + } + // Is this a linear bitmap, the one that can be accessed linearly within each scanline + inline bool IsLinearBitmap() const + { + return is_linear_bitmap(_alBitmap) != 0; + } + + // Checks if bitmap cannot be used + inline bool IsNull() const + { + return !_alBitmap; + } + // Checks if bitmap has zero size: either width or height (or both) is zero + inline bool IsEmpty() const + { + return GetWidth() == 0 || GetHeight() == 0; + } + inline int GetWidth() const + { + return _alBitmap->w; + } + inline int GetHeight() const + { + return _alBitmap->h; + } + inline Size GetSize() const + { + return Size(_alBitmap->w, _alBitmap->h); + } + inline int GetColorDepth() const + { + return bitmap_color_depth(_alBitmap); + } + // BPP: bytes per pixel + inline int GetBPP() const + { + return (GetColorDepth() + 7) / 8; + } + + // CHECKME: probably should not be exposed, see comment to GetData() + inline int GetDataSize() const + { + return GetWidth() * GetHeight() * GetBPP(); + } + // Gets scanline length in bytes (is the same for any scanline) + inline int GetLineLength() const + { + return GetWidth() * GetBPP(); + } + + // TODO: replace with byte * + // Gets a pointer to underlying graphic data + // FIXME: actually not a very good idea, since there's no 100% guarantee the scanline positions in memory are sequential + inline const unsigned char *GetData() const + { + return _alBitmap->line[0]; + } + + // Get scanline for direct reading + inline const unsigned char *GetScanLine(int index) const + { + return (index >= 0 && index < GetHeight()) ? _alBitmap->line[index] : nullptr; + } + + void SetMaskColor(color_t color); + inline color_t GetMaskColor() const + { + return bitmap_mask_color(_alBitmap); + } + + // FIXME: allegro manual states these should not be used externally; + // should hide or totally remove those later + void Acquire(); + void Release(); + + // Converts AGS color-index into RGB color according to the bitmap format. + // TODO: this method was added to the Bitmap class during large refactoring, + // but that's a mistake, because in retrospect is has nothing to do with + // bitmap itself and should rather be a part of the game data logic. + color_t GetCompatibleColor(color_t color); + + //========================================================================= + // Clipping + //========================================================================= + void SetClip(const Rect &rc); + Rect GetClip() const; + + //========================================================================= + // Blitting operations (drawing one bitmap over another) + //========================================================================= + // Draw other bitmap over current one + void Blit(Bitmap *src, int dst_x = 0, int dst_y = 0, BitmapMaskOption mask = kBitmap_Copy); + void Blit(Bitmap *src, int src_x, int src_y, int dst_x, int dst_y, int width, int height, BitmapMaskOption mask = kBitmap_Copy); + // Draw other bitmap in a masked mode (kBitmap_Transparency) + void MaskedBlit(Bitmap *src, int dst_x, int dst_y); + // Draw other bitmap, stretching or shrinking its size to given values + void StretchBlt(Bitmap *src, const Rect &dst_rc, BitmapMaskOption mask = kBitmap_Copy); + void StretchBlt(Bitmap *src, const Rect &src_rc, const Rect &dst_rc, BitmapMaskOption mask = kBitmap_Copy); + // Antia-aliased stretch-blit + void AAStretchBlt(Bitmap *src, const Rect &dst_rc, BitmapMaskOption mask = kBitmap_Copy); + void AAStretchBlt(Bitmap *src, const Rect &src_rc, const Rect &dst_rc, BitmapMaskOption mask = kBitmap_Copy); + // TODO: find more general way to call these operations, probably require pointer to Blending data struct? + // Draw bitmap using translucency preset + void TransBlendBlt(Bitmap *src, int dst_x, int dst_y); + // Draw bitmap using lighting preset + void LitBlendBlt(Bitmap *src, int dst_x, int dst_y, int light_amount); + // TODO: generic "draw transformed" function? What about mask option? + void FlipBlt(Bitmap *src, int dst_x, int dst_y, BitmapFlip flip); + void RotateBlt(Bitmap *src, int dst_x, int dst_y, fixed_t angle); + void RotateBlt(Bitmap *src, int dst_x, int dst_y, int pivot_x, int pivot_y, fixed_t angle); + + //========================================================================= + // Pixel operations + //========================================================================= + // Fills the whole bitmap with given color (black by default) + void Clear(color_t color = 0); + void ClearTransparent(); + // The PutPixel and GetPixel are supposed to be safe and therefore + // relatively slow operations. They should not be used for changing large + // blocks of bitmap memory - reading/writing from/to scan lines should be + // done in such cases. + void PutPixel(int x, int y, color_t color); + int GetPixel(int x, int y) const; + + //========================================================================= + // Vector drawing operations + //========================================================================= + void DrawLine(const Line &ln, color_t color); + void DrawTriangle(const Triangle &tr, color_t color); + void DrawRect(const Rect &rc, color_t color); + void FillRect(const Rect &rc, color_t color); + void FillCircle(const Circle &circle, color_t color); + // Fills the whole bitmap with given color + void Fill(color_t color); + void FillTransparent(); + // Floodfills an enclosed area, starting at point + void FloodFill(int x, int y, color_t color); + + //========================================================================= + // Direct access operations + //========================================================================= + // TODO: think how to increase safety over this (some fixed memory buffer class with iterator?) + // Gets scanline for directly writing into it + inline unsigned char *GetScanLineForWriting(int index) + { + return (index >= 0 && index < GetHeight()) ? _alBitmap->line[index] : nullptr; + } + inline unsigned char *GetDataForWriting() + { + return _alBitmap->line[0]; + } + // Copies buffer contents into scanline + void SetScanLine(int index, unsigned char *data, int data_size = -1); + +private: + BITMAP *_alBitmap; + bool _isDataOwner; +}; + + + +namespace BitmapHelper +{ + // TODO: revise those functions later (currently needed in a few very specific cases) + // NOTE: the resulting object __owns__ bitmap data from now on + Bitmap *CreateRawBitmapOwner(BITMAP *al_bmp); + // NOTE: the resulting object __does not own__ bitmap data + Bitmap *CreateRawBitmapWrapper(BITMAP *al_bmp); +} // namespace BitmapHelper + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_GFX__ALLEGROBITMAP_H diff --git a/engines/ags/shared/gfx/bitmap.cpp b/engines/ags/shared/gfx/bitmap.cpp new file mode 100644 index 00000000000..c5dbcf843e7 --- /dev/null +++ b/engines/ags/shared/gfx/bitmap.cpp @@ -0,0 +1,195 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "gfx/bitmap.h" +#include "util/memory.h" + +namespace AGS +{ +namespace Common +{ + +// TODO: revise this construction later +namespace BitmapHelper +{ + +Bitmap *CreateBitmap(int width, int height, int color_depth) +{ + Bitmap *bitmap = new Bitmap(); + if (!bitmap->Create(width, height, color_depth)) + { + delete bitmap; + bitmap = nullptr; + } + return bitmap; +} + +Bitmap *CreateTransparentBitmap(int width, int height, int color_depth) +{ + Bitmap *bitmap = new Bitmap(); + if (!bitmap->CreateTransparent(width, height, color_depth)) + { + delete bitmap; + bitmap = nullptr; + } + return bitmap; +} + +Bitmap *CreateSubBitmap(Bitmap *src, const Rect &rc) +{ + Bitmap *bitmap = new Bitmap(); + if (!bitmap->CreateSubBitmap(src, rc)) + { + delete bitmap; + bitmap = nullptr; + } + return bitmap; +} + +Bitmap *CreateBitmapCopy(Bitmap *src, int color_depth) +{ + Bitmap *bitmap = new Bitmap(); + if (!bitmap->CreateCopy(src, color_depth)) + { + delete bitmap; + bitmap = nullptr; + } + return bitmap; +} + +Bitmap *LoadFromFile(const char *filename) +{ + Bitmap *bitmap = new Bitmap(); + if (!bitmap->LoadFromFile(filename)) + { + delete bitmap; + bitmap = nullptr; + } + return bitmap; +} + +Bitmap *AdjustBitmapSize(Bitmap *src, int width, int height) +{ + int oldw = src->GetWidth(), oldh = src->GetHeight(); + if ((oldw == width) && (oldh == height)) + return src; + Bitmap *bmp = BitmapHelper::CreateBitmap(width, height, src->GetColorDepth()); + bmp->StretchBlt(src, RectWH(0, 0, oldw, oldh), RectWH(0, 0, width, height)); + return bmp; +} + +template +struct PixelTransCpy +{ + static const size_t BPP = BPP_; + inline void operator ()(uint8_t *dst, const uint8_t *src, color_t mask_color, bool use_alpha) const + { + if (*(TPx*)src == mask_color) + *(TPx*)dst = mask_color; + } +}; + +struct PixelNoSkip +{ + inline bool operator ()(uint8_t *data, color_t mask_color, bool use_alpha) const + { + return false; + } +}; + +typedef PixelTransCpy PixelTransCpy8; +typedef PixelTransCpy PixelTransCpy16; + +struct PixelTransCpy24 +{ + static const size_t BPP = 3; + inline void operator ()(uint8_t *dst, const uint8_t *src, color_t mask_color, bool use_alpha) const + { + const uint8_t *mcol_ptr = (const uint8_t*)&mask_color; + if (src[0] == mcol_ptr[0] && src[1] == mcol_ptr[1] && src[2] == mcol_ptr[2]) + { + dst[0] = mcol_ptr[0]; + dst[1] = mcol_ptr[1]; + dst[2] = mcol_ptr[2]; + } + } +}; + +struct PixelTransCpy32 +{ + static const size_t BPP = 4; + inline void operator ()(uint8_t *dst, const uint8_t *src, color_t mask_color, bool use_alpha) const + { + if (*(const uint32_t*)src == mask_color) + *(uint32_t*)dst = mask_color; + else if (use_alpha) + dst[3] = src[3]; // copy alpha channel + else + dst[3] = 0xFF; // set the alpha channel byte to opaque + } +}; + +struct PixelTransSkip32 +{ + inline bool operator ()(uint8_t *data, color_t mask_color, bool use_alpha) const + { + return *(uint32_t*)data == mask_color || (use_alpha && data[3] == 0); + } +}; + +template +void ApplyMask(uint8_t *dst, const uint8_t *src, size_t pitch, size_t height, FnPxProc proc, FnSkip skip, color_t mask_color, bool dst_has_alpha, bool mask_has_alpha) +{ + for (size_t y = 0; y < height; ++y) + { + for (size_t x = 0; x < pitch; x += FnPxProc::BPP, src += FnPxProc::BPP, dst += FnPxProc::BPP) + { + if (!skip(dst, mask_color, dst_has_alpha)) + proc(dst, src, mask_color, mask_has_alpha); + } + } +} + +void CopyTransparency(Bitmap *dst, const Bitmap *mask, bool dst_has_alpha, bool mask_has_alpha) +{ + color_t mask_color = mask->GetMaskColor(); + uint8_t *dst_ptr = dst->GetDataForWriting(); + const uint8_t *src_ptr = mask->GetData(); + const size_t bpp = mask->GetBPP(); + const size_t pitch = mask->GetLineLength(); + const size_t height = mask->GetHeight(); + + if (bpp == 1) + ApplyMask(dst_ptr, src_ptr, pitch, height, PixelTransCpy8(), PixelNoSkip(), mask_color, dst_has_alpha, mask_has_alpha); + else if (bpp == 2) + ApplyMask(dst_ptr, src_ptr, pitch, height, PixelTransCpy16(), PixelNoSkip(), mask_color, dst_has_alpha, mask_has_alpha); + else if (bpp == 3) + ApplyMask(dst_ptr, src_ptr, pitch, height, PixelTransCpy24(), PixelNoSkip(), mask_color, dst_has_alpha, mask_has_alpha); + else + ApplyMask(dst_ptr, src_ptr, pitch, height, PixelTransCpy32(), PixelTransSkip32(), mask_color, dst_has_alpha, mask_has_alpha); +} + +void ReadPixelsFromMemory(Bitmap *dst, const uint8_t *src_buffer, const size_t src_pitch, const size_t src_px_offset) +{ + const size_t bpp = dst->GetBPP(); + const size_t src_px_pitch = src_pitch / bpp; + if (src_px_offset >= src_px_pitch) + return; // nothing to copy + Memory::BlockCopy(dst->GetDataForWriting(), dst->GetLineLength(), 0, src_buffer, src_pitch, src_px_offset * bpp, dst->GetHeight()); +} + +} // namespace BitmapHelper + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gfx/bitmap.h b/engines/ags/shared/gfx/bitmap.h new file mode 100644 index 00000000000..12eaca7d443 --- /dev/null +++ b/engines/ags/shared/gfx/bitmap.h @@ -0,0 +1,87 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Base bitmap header +// +//============================================================================= +#ifndef __AGS_CN_GFX__BITMAP_H +#define __AGS_CN_GFX__BITMAP_H + +#include "util/geometry.h" + +namespace AGS +{ +namespace Common +{ + +// Mask option for blitting one bitmap on another +enum BitmapMaskOption +{ + // Plain copies bitmap pixels + kBitmap_Copy, + // Consider mask color fully transparent and do not copy pixels having it + kBitmap_Transparency +}; + +enum BitmapFlip +{ + kBitmap_HFlip, + kBitmap_VFlip, + kBitmap_HVFlip +}; + +} // namespace Common +} // namespace AGS + + +// Declare the actual bitmap class +#include "gfx/allegrobitmap.h" + +namespace AGS +{ +namespace Common +{ + +class Bitmap; + +// TODO: revise this construction later +namespace BitmapHelper +{ + // Helper functions, that delete faulty bitmaps automatically, and return + // NULL if bitmap could not be created. + Bitmap *CreateBitmap(int width, int height, int color_depth = 0); + Bitmap *CreateTransparentBitmap(int width, int height, int color_depth = 0); + Bitmap *CreateSubBitmap(Bitmap *src, const Rect &rc); + Bitmap *CreateBitmapCopy(Bitmap *src, int color_depth = 0); + Bitmap *LoadFromFile(const char *filename); + + // Stretches bitmap to the requested size. The new bitmap will have same + // colour depth. Returns original bitmap if no changes are necessary. + Bitmap *AdjustBitmapSize(Bitmap *src, int width, int height); + // Copy transparency mask and/or alpha channel from one bitmap into another. + // Destination and mask bitmaps must be of the same pixel format. + // Transparency is merged, meaning that fully transparent pixels on + // destination should remain such regardless of mask pixel values. + void CopyTransparency(Bitmap *dst, const Bitmap *mask, bool dst_has_alpha, bool mask_has_alpha); + // Copy pixel data into bitmap from memory buffer. It is required that the + // source matches bitmap format and has enough data. + // Pitch is given in bytes and defines the length of the source scan line. + // Offset is optional and defines horizontal offset, in pixels. + void ReadPixelsFromMemory(Bitmap *dst, const uint8_t *src_buffer, const size_t src_pitch, const size_t src_px_offset = 0); +} // namespace BitmapHelper + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_GFX__BITMAP_H diff --git a/engines/ags/shared/gfx/gfx_def.h b/engines/ags/shared/gfx/gfx_def.h new file mode 100644 index 00000000000..e883c1695e9 --- /dev/null +++ b/engines/ags/shared/gfx/gfx_def.h @@ -0,0 +1,117 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Graphic definitions and type/unit conversions. +// +//============================================================================= +#ifndef __AGS_CN_GFX__GFXDEF_H +#define __AGS_CN_GFX__GFXDEF_H + +namespace AGS +{ +namespace Common +{ + +enum BlendMode +{ + // free blending (ARGB -> ARGB) modes + kBlendMode_NoAlpha = 0, // ignore alpha channel + kBlendMode_Alpha, // alpha-blend src to dest, combining src & dest alphas + // NOTE: add new modes here + + kNumBlendModes +}; + +namespace GfxDef +{ + inline int Trans100ToAlpha255(int transparency) + { + return ((100 - transparency) * 255) / 100; + } + + inline int Alpha255ToTrans100(int alpha) + { + return 100 - ((alpha * 100) / 255); + } + + // Special formulae to reduce precision loss and support flawless forth & + // reverse conversion for multiplies of 10% + inline int Trans100ToAlpha250(int transparency) + { + return ((100 - transparency) * 25) / 10; + } + + inline int Alpha250ToTrans100(int alpha) + { + return 100 - ((alpha * 10) / 25); + } + + // Convert correct 100-ranged transparency into legacy 255-ranged + // transparency; legacy inconsistent transparency value range: + // 0 = opaque, + // 255 = invisible, + // 1 -to- 254 = barely visible -to- mostly visible (as proper alpha) + inline int Trans100ToLegacyTrans255(int transparency) + { + if (transparency == 0) + { + return 0; // this means opaque + } + else if (transparency == 100) + { + return 255; // this means invisible + } + // the rest of the range works as alpha + return Trans100ToAlpha250(transparency); + } + + // Convert legacy 255-ranged "incorrect" transparency into proper + // 100-ranged transparency. + inline int LegacyTrans255ToTrans100(int legacy_transparency) + { + if (legacy_transparency == 0) + { + return 0; // this means opaque + } + else if (legacy_transparency == 255) + { + return 100; // this means invisible + } + // the rest of the range works as alpha + return Alpha250ToTrans100(legacy_transparency); + } + + // Convert legacy 100-ranged transparency into proper 255-ranged alpha + // 0 => alpha 255 + // 100 => alpha 0 + // 1 - 99 => alpha 1 - 244 + inline int LegacyTrans100ToAlpha255(int legacy_transparency) + { + if (legacy_transparency == 0) + { + return 255; // this means opaque + } + else if (legacy_transparency == 100) + { + return 0; // this means invisible + } + // the rest of the range works as alpha (only 100-ranged) + return legacy_transparency * 255 / 100; + } +} // namespace GfxDef + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_GFX__GFXDEF_H diff --git a/engines/ags/shared/gui/guibutton.cpp b/engines/ags/shared/gui/guibutton.cpp new file mode 100644 index 00000000000..3ea836cdcb4 --- /dev/null +++ b/engines/ags/shared/gui/guibutton.cpp @@ -0,0 +1,384 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/spritecache.h" +#include "gui/guibutton.h" +#include "gui/guimain.h" // TODO: extract helper functions +#include "util/stream.h" +#include "util/string_utils.h" + +std::vector guibuts; +int numguibuts = 0; + +namespace AGS +{ +namespace Common +{ + +FrameAlignment ConvertLegacyButtonAlignment(LegacyButtonAlignment align) +{ + switch (align) + { + case kLegacyButtonAlign_TopCenter: + return kAlignTopCenter; + case kLegacyButtonAlign_TopLeft: + return kAlignTopLeft; + case kLegacyButtonAlign_TopRight: + return kAlignTopRight; + case kLegacyButtonAlign_CenterLeft: + return kAlignMiddleLeft; + case kLegacyButtonAlign_Centered: + return kAlignMiddleCenter; + case kLegacyButtonAlign_CenterRight: + return kAlignMiddleRight; + case kLegacyButtonAlign_BottomLeft: + return kAlignBottomLeft; + case kLegacyButtonAlign_BottomCenter: + return kAlignBottomCenter; + case kLegacyButtonAlign_BottomRight: + return kAlignBottomRight; + } + return kAlignNone; +} + + +GUIButton::GUIButton() +{ + Image = -1; + MouseOverImage = -1; + PushedImage = -1; + CurrentImage = -1; + Font = 0; + TextColor = 0; + TextAlignment = kAlignTopCenter; + ClickAction[kMouseLeft] = kGUIAction_RunScript; + ClickAction[kMouseRight] = kGUIAction_RunScript; + ClickData[kMouseLeft] = 0; + ClickData[kMouseRight] = 0; + + IsPushed = false; + IsMouseOver = false; + _placeholder = kButtonPlace_None; + _unnamed = false; + + _scEventCount = 1; + _scEventNames[0] = "Click"; + _scEventArgs[0] = "GUIControl *control, MouseButton button"; +} + +const String &GUIButton::GetText() const +{ + return _text; +} + +bool GUIButton::IsClippingImage() const +{ + return (Flags & kGUICtrl_Clip) != 0; +} + +void GUIButton::Draw(Bitmap *ds) +{ + bool draw_disabled = !IsGUIEnabled(this); + + check_font(&Font); + // if it's "Unchanged when disabled" or "GUI Off", don't grey out + if (gui_disabled_style == GUIDIS_UNCHANGED || + gui_disabled_style == GUIDIS_GUIOFF) + { + draw_disabled = false; + } + // TODO: should only change properties in reaction to particular events + if (CurrentImage <= 0 || draw_disabled) + CurrentImage = Image; + + if (draw_disabled && gui_disabled_style == GUIDIS_BLACKOUT) + // buttons off when disabled - no point carrying on + return; + + // CHECKME: why testing both CurrentImage and Image? + if (CurrentImage > 0 && Image > 0) + DrawImageButton(ds, draw_disabled); + // CHECKME: why don't draw frame if no Text? this will make button completely invisible! + else if (!_text.IsEmpty()) + DrawTextButton(ds, draw_disabled); +} + +void GUIButton::SetClipImage(bool on) +{ + if (on) + Flags |= kGUICtrl_Clip; + else + Flags &= ~kGUICtrl_Clip; +} + +void GUIButton::SetText(const String &text) +{ + _text = text; + // Active inventory item placeholders + if (_text.CompareNoCase("(INV)") == 0) + // Stretch to fit button + _placeholder = kButtonPlace_InvItemStretch; + else if (_text.CompareNoCase("(INVNS)") == 0) + // Draw at actual size + _placeholder = kButtonPlace_InvItemCenter; + else if (_text.CompareNoCase("(INVSHR)") == 0) + // Stretch if too big, actual size if not + _placeholder = kButtonPlace_InvItemAuto; + else + _placeholder = kButtonPlace_None; + + // TODO: find a way to remove this bogus limitation ("New Button" is a valid Text too) + _unnamed = _text.Compare("New Button") == 0; +} + +bool GUIButton::OnMouseDown() +{ + if (PushedImage > 0) + CurrentImage = PushedImage; + IsPushed = true; + return false; +} + +void GUIButton::OnMouseEnter() +{ + CurrentImage = IsPushed ? PushedImage : MouseOverImage; + IsMouseOver = true; +} + +void GUIButton::OnMouseLeave() +{ + CurrentImage = Image; + IsMouseOver = false; +} + +void GUIButton::OnMouseUp() +{ + if (IsMouseOver) + { + CurrentImage = MouseOverImage; + if (IsGUIEnabled(this) && IsClickable()) + IsActivated = true; + } + else + { + CurrentImage = Image; + } + + IsPushed = false; +} + +// TODO: replace string serialization with StrUtil::ReadString and WriteString +// methods in the future, to keep this organized. +void GUIButton::WriteToFile(Stream *out) const +{ + GUIObject::WriteToFile(out); + + out->WriteInt32(Image); + out->WriteInt32(MouseOverImage); + out->WriteInt32(PushedImage); + out->WriteInt32(Font); + out->WriteInt32(TextColor); + out->WriteInt32(ClickAction[kMouseLeft]); + out->WriteInt32(ClickAction[kMouseRight]); + out->WriteInt32(ClickData[kMouseLeft]); + out->WriteInt32(ClickData[kMouseRight]); + + StrUtil::WriteString(_text, out); + out->WriteInt32(TextAlignment); +} + +void GUIButton::ReadFromFile(Stream *in, GuiVersion gui_version) +{ + GUIObject::ReadFromFile(in, gui_version); + + Image = in->ReadInt32(); + MouseOverImage = in->ReadInt32(); + PushedImage = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + { // NOTE: reading into actual variables only for old savegame support + CurrentImage = in->ReadInt32(); + IsPushed = in->ReadInt32() != 0; + IsMouseOver = in->ReadInt32() != 0; + } + Font = in->ReadInt32(); + TextColor = in->ReadInt32(); + ClickAction[kMouseLeft] = (GUIClickAction)in->ReadInt32(); + ClickAction[kMouseRight] = (GUIClickAction)in->ReadInt32(); + ClickData[kMouseLeft] = in->ReadInt32(); + ClickData[kMouseRight] = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + SetText(String::FromStreamCount(in, GUIBUTTON_LEGACY_TEXTLENGTH)); + else + SetText(StrUtil::ReadString(in)); + + if (gui_version >= kGuiVersion_272a) + { + if (gui_version < kGuiVersion_350) + { + TextAlignment = ConvertLegacyButtonAlignment((LegacyButtonAlignment)in->ReadInt32()); + in->ReadInt32(); // reserved1 + } + else + { + TextAlignment = (FrameAlignment)in->ReadInt32(); + } + } + else + { + TextAlignment = kAlignTopCenter; + } + + if (TextColor == 0) + TextColor = 16; + CurrentImage = Image; + // All buttons are translated at the moment + Flags |= kGUICtrl_Translated; +} + +void GUIButton::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) +{ + GUIObject::ReadFromSavegame(in, svg_ver); + // Properties + Image = in->ReadInt32(); + MouseOverImage = in->ReadInt32(); + PushedImage = in->ReadInt32(); + Font = in->ReadInt32(); + TextColor = in->ReadInt32(); + SetText(StrUtil::ReadString(in)); + if (svg_ver >= kGuiSvgVersion_350) + TextAlignment = (FrameAlignment)in->ReadInt32(); + // Dynamic state + Image = in->ReadInt32(); +} + +void GUIButton::WriteToSavegame(Stream *out) const +{ + // Properties + GUIObject::WriteToSavegame(out); + out->WriteInt32(Image); + out->WriteInt32(MouseOverImage); + out->WriteInt32(PushedImage); + out->WriteInt32(Font); + out->WriteInt32(TextColor); + StrUtil::WriteString(GetText(), out); + out->WriteInt32(TextAlignment); + // Dynamic state + out->WriteInt32(Image); +} + +void GUIButton::DrawImageButton(Bitmap *ds, bool draw_disabled) +{ + // NOTE: the CLIP flag only clips the image, not the text + if (IsClippingImage()) + ds->SetClip(Rect(X, Y, X + Width - 1, Y + Height - 1)); + if (spriteset[CurrentImage] != nullptr) + draw_gui_sprite(ds, CurrentImage, X, Y, true); + + // Draw active inventory item + if (_placeholder != kButtonPlace_None && gui_inv_pic >= 0) + { + GUIButtonPlaceholder place = _placeholder; + if (place == kButtonPlace_InvItemAuto) + { + if ((get_adjusted_spritewidth(gui_inv_pic) > Width - 6) || + (get_adjusted_spriteheight(gui_inv_pic) > Height - 6)) + { + place = kButtonPlace_InvItemStretch; + } + else + { + place = kButtonPlace_InvItemCenter; + } + } + + if (place == kButtonPlace_InvItemStretch) + { + ds->StretchBlt(spriteset[gui_inv_pic], RectWH(X + 3, Y + 3, Width - 6, Height - 6), Common::kBitmap_Transparency); + } + else if (place == kButtonPlace_InvItemCenter) + { + draw_gui_sprite(ds, gui_inv_pic, + X + Width / 2 - get_adjusted_spritewidth(gui_inv_pic) / 2, + Y + Height / 2 - get_adjusted_spriteheight(gui_inv_pic) / 2, + true); + } + } + + if ((draw_disabled) && (gui_disabled_style == GUIDIS_GREYOUT)) + { + // darken the button when disabled + GUI::DrawDisabledEffect(ds, RectWH(X, Y, + spriteset[CurrentImage]->GetWidth(), + spriteset[CurrentImage]->GetHeight())); + } + ds->SetClip(Rect(0, 0, ds->GetWidth() - 1, ds->GetHeight() - 1)); + + // Don't print Text of (INV) (INVSHR) (INVNS) + if (_placeholder == kButtonPlace_None && !_unnamed) + DrawText(ds, draw_disabled); +} + +void GUIButton::DrawText(Bitmap *ds, bool draw_disabled) +{ + if (_text.IsEmpty()) + return; + // TODO: need to find a way to cache Text prior to drawing; + // but that will require to update all gui controls when translation is changed in game + PrepareTextToDraw(); + + Rect frame = RectWH(X + 2, Y + 2, Width - 4, Height - 4); + if (IsPushed && IsMouseOver) + { + // move the Text a bit while pushed + frame.Left++; + frame.Top++; + } + color_t text_color = ds->GetCompatibleColor(TextColor); + if (draw_disabled) + text_color = ds->GetCompatibleColor(8); + GUI::DrawTextAligned(ds, _textToDraw, Font, text_color, frame, TextAlignment); +} + +void GUIButton::DrawTextButton(Bitmap *ds, bool draw_disabled) +{ + color_t draw_color = ds->GetCompatibleColor(7); + ds->FillRect(Rect(X, Y, X + Width - 1, Y + Height - 1), draw_color); + if (Flags & kGUICtrl_Default) + { + draw_color = ds->GetCompatibleColor(16); + ds->DrawRect(Rect(X - 1, Y - 1, X + Width, Y + Height), draw_color); + } + + // TODO: use color constants instead of literal numbers + if (!draw_disabled && IsMouseOver && IsPushed) + draw_color = ds->GetCompatibleColor(15); + else + draw_color = ds->GetCompatibleColor(8); + + ds->DrawLine(Line(X, Y + Height - 1, X + Width - 1, Y + Height - 1), draw_color); + ds->DrawLine(Line(X + Width - 1, Y, X + Width - 1, Y + Height - 1), draw_color); + + if (draw_disabled || (IsMouseOver && IsPushed)) + draw_color = ds->GetCompatibleColor(8); + else + draw_color = ds->GetCompatibleColor(15); + + ds->DrawLine(Line(X, Y, X + Width - 1, Y), draw_color); + ds->DrawLine(Line(X, Y, X, Y + Height - 1), draw_color); + + DrawText(ds, draw_disabled); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gui/guibutton.h b/engines/ags/shared/gui/guibutton.h new file mode 100644 index 00000000000..1ff172b2031 --- /dev/null +++ b/engines/ags/shared/gui/guibutton.h @@ -0,0 +1,136 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GUIBUTTON_H +#define __AC_GUIBUTTON_H + +#include +#include "gui/guiobject.h" +#include "util/string.h" + +#define GUIBUTTON_LEGACY_TEXTLENGTH 50 + +namespace AGS +{ +namespace Common +{ + +enum MouseButton +{ + kMouseNone = -1, + kMouseLeft = 0, + kMouseRight = 1, +}; + +enum GUIClickAction +{ + kGUIAction_None = 0, + kGUIAction_SetMode = 1, + kGUIAction_RunScript = 2, +}; + +enum LegacyButtonAlignment +{ + kLegacyButtonAlign_TopCenter = 0, + kLegacyButtonAlign_TopLeft = 1, + kLegacyButtonAlign_TopRight = 2, + kLegacyButtonAlign_CenterLeft = 3, + kLegacyButtonAlign_Centered = 4, + kLegacyButtonAlign_CenterRight = 5, + kLegacyButtonAlign_BottomLeft = 6, + kLegacyButtonAlign_BottomCenter = 7, + kLegacyButtonAlign_BottomRight = 8, +}; + +class GUIButton : public GUIObject +{ +public: + GUIButton(); + + const String &GetText() const; + bool IsClippingImage() const; + + // Operations + void Draw(Bitmap *ds) override; + void SetClipImage(bool on); + void SetText(const String &text); + + // Events + bool OnMouseDown() override; + void OnMouseEnter() override; + void OnMouseLeave() override; + void OnMouseUp() override; + + // Serialization + void ReadFromFile(Stream *in, GuiVersion gui_version) override; + void WriteToFile(Stream *out) const override; + void ReadFromSavegame(Common::Stream *in, GuiSvgVersion svg_ver) override; + void WriteToSavegame(Common::Stream *out) const override; + +// TODO: these members are currently public; hide them later +public: + int32_t Image; + int32_t MouseOverImage; + int32_t PushedImage; + int32_t CurrentImage; + int32_t Font; + color_t TextColor; + FrameAlignment TextAlignment; + // Click actions for left and right mouse buttons + // NOTE: only left click is currently in use + static const int ClickCount = kMouseRight + 1; + GUIClickAction ClickAction[ClickCount]; + int32_t ClickData[ClickCount]; + + bool IsPushed; + bool IsMouseOver; + +private: + void DrawImageButton(Bitmap *ds, bool draw_disabled); + void DrawText(Bitmap *ds, bool draw_disabled); + void DrawTextButton(Bitmap *ds, bool draw_disabled); + void PrepareTextToDraw(); + + // Defines button placeholder mode; the mode is set + // depending on special tags found in button text + enum GUIButtonPlaceholder + { + kButtonPlace_None, + kButtonPlace_InvItemStretch, + kButtonPlace_InvItemCenter, + kButtonPlace_InvItemAuto + }; + + // Text property set by user + String _text; + // type of content placeholder, if any + GUIButtonPlaceholder _placeholder; + // A flag indicating unnamed button; this is a convenience trick: + // buttons are created named "New Button" in the editor, and users + // often do not clear text when they want a graphic button. + bool _unnamed; + // Prepared text buffer/cache + String _textToDraw; +}; + +} // namespace Common +} // namespace AGS + +extern std::vector guibuts; +extern int numguibuts; + +int UpdateAnimatingButton(int bu); +void StopButtonAnimation(int idxn); + +#endif // __AC_GUIBUTTON_H diff --git a/engines/ags/shared/gui/guidefines.h b/engines/ags/shared/gui/guidefines.h new file mode 100644 index 00000000000..e57451d6991 --- /dev/null +++ b/engines/ags/shared/gui/guidefines.h @@ -0,0 +1,185 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GUIDEFINES_H +#define __AC_GUIDEFINES_H + +#define GUIMAGIC 0xcafebeef +#define MAX_GUIOBJ_EVENTS 10 +#define TEXTWINDOW_PADDING_DEFAULT 3 +//#define MAX_OBJ_EACH_TYPE 251 + +// TODO: find out more about gui version history +//============================================================================= +// GUI Version history +//----------------------------------------------------------------------------- +// +// 2.1.4..... (100): Introduced Slider gui control. Gui count is now serialized +// in game file. +// 2.2.2..... (101): Introduced TextBox gui control. +// 2.3.0..... (102): Introduced ListBox gui control. +// 2.6.0..... (105): GUI custom Z-order support. +// 2.7.0..... (110): Added GUI OnClick handler. +// 2.7.2.???? (111): Added text alignment property to buttons. +// 2.7.2.???? (112): Added text alignment property to list boxes. +// 2.7.2.???? (113): Increased permitted length of GUI label text from 200 to +// 2048 characters. +// 2.7.2.???? (114): Obsoleted savegameindex[] array, and added +// ListBox.SaveGameSlots[] array instead. +// 2.7.2.???? (115): Added GUI Control z-order support. +// +// 3.3.0.1132 (116): Added kGUICtrl_Translated flag. +// 3.3.1.???? (117): Added padding variable for text window GUIs. +// 3.4.0 (118): Removed GUI limits +// 3.5.0 (119): Game data contains GUI properties that previously +// could be set only at runtime. +// +//============================================================================= + +enum GuiVersion +{ + // TODO: find out all corresponding engine version numbers + kGuiVersion_Initial = 0, + kGuiVersion_214 = 100, + kGuiVersion_222 = 101, + kGuiVersion_230 = 102, + kGuiVersion_unkn_103 = 103, + kGuiVersion_unkn_104 = 104, + kGuiVersion_260 = 105, + kGuiVersion_unkn_106 = 106, + kGuiVersion_unkn_107 = 107, + kGuiVersion_unkn_108 = 108, + kGuiVersion_unkn_109 = 109, + kGuiVersion_270 = 110, + kGuiVersion_272a = 111, + kGuiVersion_272b = 112, + kGuiVersion_272c = 113, + kGuiVersion_272d = 114, + kGuiVersion_272e = 115, + + kGuiVersion_330 = 116, + kGuiVersion_331 = 117, + kGuiVersion_340 = 118, + kGuiVersion_350 = 119, + kGuiVersion_Current = kGuiVersion_350, +}; + +namespace AGS +{ +namespace Common +{ + +// GUIMain's style and behavior flags +enum GUIMainFlags +{ + kGUIMain_Clickable = 0x0001, + kGUIMain_TextWindow = 0x0002, + kGUIMain_Visible = 0x0004, + kGUIMain_Concealed = 0x0008, + + // NOTE: currently default state is Visible to keep this backwards compatible; + // check later if this is still a wanted behavior + kGUIMain_DefFlags = kGUIMain_Clickable | kGUIMain_Visible, + // flags that had inverse meaning in old formats + kGUIMain_OldFmtXorMask = kGUIMain_Clickable +}; + +// GUIMain's legacy flags, now converted to GUIMainFlags on load +enum GUIMainLegacyFlags +{ + kGUIMain_LegacyTextWindow = 5 +}; + +// GUIMain's style of getting displayed on screen +enum GUIPopupStyle +{ + // Normal GUI + kGUIPopupNormal = 0, + // Shown when the mouse cursor moves to the top of the screen + kGUIPopupMouseY = 1, + // Same as Normal, but pauses the game when shown + kGUIPopupModal = 2, + // Same as Normal, but is not removed when interface is off + kGUIPopupNoAutoRemove = 3, + // (legacy option) Normal GUI, initially off + // converts to kGUIPopupNormal with Visible = false + kGUIPopupLegacyNormalOff = 4 +}; + +// The type of GUIControl +enum GUIControlType +{ + kGUIControlUndefined = -1, + kGUIButton = 1, + kGUILabel = 2, + kGUIInvWindow = 3, + kGUISlider = 4, + kGUITextBox = 5, + kGUIListBox = 6 +}; + +// GUIControl general style and behavior flags +enum GUIControlFlags +{ + kGUICtrl_Default = 0x0001, // only button + kGUICtrl_Cancel = 0x0002, // unused + kGUICtrl_Enabled = 0x0004, + kGUICtrl_TabStop = 0x0008, // unused + kGUICtrl_Visible = 0x0010, + kGUICtrl_Clip = 0x0020, // only button + kGUICtrl_Clickable = 0x0040, + kGUICtrl_Translated = 0x0080, // 3.3.0.1132 + kGUICtrl_Deleted = 0x8000, // unused (probably remains from the old editor?) + + kGUICtrl_DefFlags = kGUICtrl_Enabled | kGUICtrl_Visible | kGUICtrl_Clickable, + // flags that had inverse meaning in old formats + kGUICtrl_OldFmtXorMask = kGUICtrl_Enabled | kGUICtrl_Visible | kGUICtrl_Clickable +}; + +// GUIListBox style and behavior flags +enum GUIListBoxFlags +{ + kListBox_ShowBorder = 0x01, + kListBox_ShowArrows = 0x02, + kListBox_SvgIndex = 0x04, + + kListBox_DefFlags = kListBox_ShowBorder | kListBox_ShowArrows, + // flags that had inverse meaning in old formats + kListBox_OldFmtXorMask = kListBox_ShowBorder | kListBox_ShowArrows +}; + +// GUITextBox style and behavior flags +enum GUITextBoxFlags +{ + kTextBox_ShowBorder = 0x0001, + + kTextBox_DefFlags = kTextBox_ShowBorder, + // flags that had inverse meaning in old formats + kTextBox_OldFmtXorMask = kTextBox_ShowBorder +}; + +// Savegame data format +// TODO: move to the engine code +enum GuiSvgVersion +{ + kGuiSvgVersion_Initial = 0, + kGuiSvgVersion_350 = 1 +}; + +} // namespace Common +} // namespace AGS + +extern int guis_need_update; + +#endif // __AC_GUIDEFINES_H diff --git a/engines/ags/shared/gui/guiinv.cpp b/engines/ags/shared/gui/guiinv.cpp new file mode 100644 index 00000000000..662ccb479b8 --- /dev/null +++ b/engines/ags/shared/gui/guiinv.cpp @@ -0,0 +1,144 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/game_version.h" +#include "gui/guiinv.h" +#include "gui/guimain.h" +#include "util/stream.h" + +std::vector guiinv; +int numguiinv = 0; + +namespace AGS +{ +namespace Common +{ + +GUIInvWindow::GUIInvWindow() +{ + IsMouseOver = false; + CharId = -1; + ItemWidth = 40; + ItemHeight = 22; + ColCount = 0; + RowCount = 0; + TopItem = 0; + CalculateNumCells(); + + _scEventCount = 0; +} + +void GUIInvWindow::OnMouseEnter() +{ + IsMouseOver = true; +} + +void GUIInvWindow::OnMouseLeave() +{ + IsMouseOver = false; +} + +void GUIInvWindow::OnMouseUp() +{ + if (IsMouseOver) + IsActivated = true; +} + +void GUIInvWindow::OnResized() +{ + CalculateNumCells(); +} + +void GUIInvWindow::WriteToFile(Stream *out) const +{ + GUIObject::WriteToFile(out); + out->WriteInt32(CharId); + out->WriteInt32(ItemWidth); + out->WriteInt32(ItemHeight); +} + +void GUIInvWindow::ReadFromFile(Stream *in, GuiVersion gui_version) +{ + GUIObject::ReadFromFile(in, gui_version); + if (gui_version >= kGuiVersion_unkn_109) + { + CharId = in->ReadInt32(); + ItemWidth = in->ReadInt32(); + ItemHeight = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + { // NOTE: reading into actual variables only for old savegame support + TopItem = in->ReadInt32(); + } + } + else + { + CharId = -1; + ItemWidth = 40; + ItemHeight = 22; + TopItem = 0; + } + + if (loaded_game_file_version >= kGameVersion_270) + { + // ensure that some items are visible + if (ItemWidth > Width) + ItemWidth = Width; + if (ItemHeight > Height) + ItemHeight = Height; + } + + CalculateNumCells(); +} + +void GUIInvWindow::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) +{ + GUIObject::ReadFromSavegame(in, svg_ver); + ItemWidth = in->ReadInt32(); + ItemHeight = in->ReadInt32(); + CharId = in->ReadInt32(); + TopItem = in->ReadInt32(); + CalculateNumCells(); +} + +void GUIInvWindow::WriteToSavegame(Stream *out) const +{ + GUIObject::WriteToSavegame(out); + out->WriteInt32(ItemWidth); + out->WriteInt32(ItemHeight); + out->WriteInt32(CharId); + out->WriteInt32(TopItem); +} + +void GUIInvWindow::CalculateNumCells() +{ + if (ItemWidth <= 0 || ItemHeight <= 0) + { + ColCount = 0; + RowCount = 0; + } + else if (loaded_game_file_version >= kGameVersion_270) + { + ColCount = Width / data_to_game_coord(ItemWidth); + RowCount = Height / data_to_game_coord(ItemHeight); + } + else + { + ColCount = floor((float)Width / (float)data_to_game_coord(ItemWidth) + 0.5f); + RowCount = floor((float)Height / (float)data_to_game_coord(ItemHeight) + 0.5f); + } +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gui/guiinv.h b/engines/ags/shared/gui/guiinv.h new file mode 100644 index 00000000000..2fdad85a2c0 --- /dev/null +++ b/engines/ags/shared/gui/guiinv.h @@ -0,0 +1,70 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GUIINV_H +#define __AC_GUIINV_H + +#include +#include "gui/guiobject.h" + +namespace AGS +{ +namespace Common +{ + +class GUIInvWindow : public GUIObject +{ +public: + GUIInvWindow(); + + // This function has distinct implementations in Engine and Editor + int GetCharacterId() const; + + // Operations + // This function has distinct implementations in Engine and Editor + void Draw(Bitmap *ds) override; + + // Events + void OnMouseEnter() override; + void OnMouseLeave() override; + void OnMouseUp() override; + void OnResized() override; + + // Serialization + void ReadFromFile(Stream *in, GuiVersion gui_version) override; + void WriteToFile(Stream *out) const override; + void ReadFromSavegame(Common::Stream *in, GuiSvgVersion svg_ver) override; + void WriteToSavegame(Common::Stream *out) const override; + +// TODO: these members are currently public; hide them later +public: + bool IsMouseOver; + int32_t CharId; // whose inventory (-1 = current player) + int32_t ItemWidth; + int32_t ItemHeight; + int32_t ColCount; + int32_t RowCount; + int32_t TopItem; + +private: + void CalculateNumCells(); +}; + +} // namespace Common +} // namespace AGS + +extern std::vector guiinv; +extern int numguiinv; + +#endif // __AC_GUIINV_H diff --git a/engines/ags/shared/gui/guilabel.cpp b/engines/ags/shared/gui/guilabel.cpp new file mode 100644 index 00000000000..698a9abc47f --- /dev/null +++ b/engines/ags/shared/gui/guilabel.cpp @@ -0,0 +1,128 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/game_version.h" +#include "font/fonts.h" +#include "gui/guilabel.h" +#include "gui/guimain.h" +#include "util/stream.h" +#include "util/string_utils.h" + +std::vector guilabels; +int numguilabels = 0; + +#define GUILABEL_TEXTLENGTH_PRE272 200 + +namespace AGS +{ +namespace Common +{ + +GUILabel::GUILabel() +{ + Font = 0; + TextColor = 0; + TextAlignment = kHAlignLeft; + + _scEventCount = 0; +} + +String GUILabel::GetText() const +{ + return Text; +} + +void GUILabel::Draw(Common::Bitmap *ds) +{ + check_font(&Font); + + // TODO: need to find a way to cache text prior to drawing; + // but that will require to update all gui controls when translation is changed in game + PrepareTextToDraw(); + if (SplitLinesForDrawing(Lines) == 0) + return; + + color_t text_color = ds->GetCompatibleColor(TextColor); + const int linespacing = getfontlinespacing(Font) + 1; + // < 2.72 labels did not limit vertical size of text + const bool limit_by_label_frame = loaded_game_file_version >= kGameVersion_272; + int at_y = Y; + for (size_t i = 0; + i < Lines.Count() && (!limit_by_label_frame || at_y <= Y + Height); + ++i, at_y += linespacing) + { + GUI::DrawTextAlignedHor(ds, Lines[i], Font, text_color, X, X + Width - 1, at_y, + (FrameAlignment)TextAlignment); + } +} + +void GUILabel::SetText(const String &text) +{ + Text = text; +} + +// TODO: replace string serialization with StrUtil::ReadString and WriteString +// methods in the future, to keep this organized. +void GUILabel::WriteToFile(Stream *out) const +{ + GUIObject::WriteToFile(out); + StrUtil::WriteString(Text, out); + out->WriteInt32(Font); + out->WriteInt32(TextColor); + out->WriteInt32(TextAlignment); +} + +void GUILabel::ReadFromFile(Stream *in, GuiVersion gui_version) +{ + GUIObject::ReadFromFile(in, gui_version); + + if (gui_version < kGuiVersion_272c) + Text.ReadCount(in, GUILABEL_TEXTLENGTH_PRE272); + else + Text = StrUtil::ReadString(in); + + Font = in->ReadInt32(); + TextColor = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + TextAlignment = ConvertLegacyGUIAlignment((LegacyGUIAlignment)in->ReadInt32()); + else + TextAlignment = (HorAlignment)in->ReadInt32(); + + if (TextColor == 0) + TextColor = 16; + // All labels are translated at the moment + Flags |= kGUICtrl_Translated; +} + +void GUILabel::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) +{ + GUIObject::ReadFromSavegame(in, svg_ver); + Font = in->ReadInt32(); + TextColor = in->ReadInt32(); + Text = StrUtil::ReadString(in); + if (svg_ver >= kGuiSvgVersion_350) + TextAlignment = (HorAlignment)in->ReadInt32(); +} + +void GUILabel::WriteToSavegame(Stream *out) const +{ + GUIObject::WriteToSavegame(out); + out->WriteInt32(Font); + out->WriteInt32(TextColor); + StrUtil::WriteString(Text, out); + out->WriteInt32(TextAlignment); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gui/guilabel.h b/engines/ags/shared/gui/guilabel.h new file mode 100644 index 00000000000..97665d65ff0 --- /dev/null +++ b/engines/ags/shared/gui/guilabel.h @@ -0,0 +1,67 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GUILABEL_H +#define __AC_GUILABEL_H + +#include +#include "gui/guiobject.h" +#include "util/string.h" + +class SplitLines; + +namespace AGS +{ +namespace Common +{ + +class GUILabel:public GUIObject +{ +public: + GUILabel(); + + String GetText() const; + + // Operations + void Draw(Bitmap *ds) override; + void SetText(const String &text); + + // Serialization + void ReadFromFile(Stream *in, GuiVersion gui_version) override; + void WriteToFile(Stream *out) const override; + void ReadFromSavegame(Common::Stream *in, GuiSvgVersion svg_ver) override; + void WriteToSavegame(Common::Stream *out) const override; + +// TODO: these members are currently public; hide them later +public: + String Text; + int32_t Font; + color_t TextColor; + HorAlignment TextAlignment; + +private: + void PrepareTextToDraw(); + size_t SplitLinesForDrawing(SplitLines &lines); + + // prepared text buffer/cache + String _textToDraw; +}; + +} // namespace Common +} // namespace AGS + +extern std::vector guilabels; +extern int numguilabels; + +#endif // __AC_GUILABEL_H diff --git a/engines/ags/shared/gui/guilistbox.cpp b/engines/ags/shared/gui/guilistbox.cpp new file mode 100644 index 00000000000..aa57ca0b3e2 --- /dev/null +++ b/engines/ags/shared/gui/guilistbox.cpp @@ -0,0 +1,433 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "font/fonts.h" +#include "gui/guilistbox.h" +#include "gui/guimain.h" +#include "util/stream.h" +#include "util/string_utils.h" + +std::vector guilist; +int numguilist = 0; + +namespace AGS +{ +namespace Common +{ + +GUIListBox::GUIListBox() +{ + ItemCount = 0; + SelectedItem = 0; + TopItem = 0; + RowHeight = 0; + VisibleItemCount = 0; + Font = 0; + TextColor = 0; + SelectedTextColor = 7; + ListBoxFlags = kListBox_DefFlags; + SelectedBgColor = 16; + TextAlignment = kHAlignLeft; + + _scEventCount = 1; + _scEventNames[0] = "SelectionChanged"; + _scEventArgs[0] = "GUIControl *control"; +} + +int GUIListBox::GetItemAt(int x, int y) const +{ + if (RowHeight <= 0 || IsInRightMargin(x)) + return -1; + + int index = y / RowHeight + TopItem; + if (index < 0 || index >= ItemCount) + return -1; + return index; +} + +bool GUIListBox::AreArrowsShown() const +{ + return (ListBoxFlags & kListBox_ShowArrows) != 0; +} + +bool GUIListBox::IsBorderShown() const +{ + return (ListBoxFlags & kListBox_ShowBorder) != 0; +} + +bool GUIListBox::IsSvgIndex() const +{ + return (ListBoxFlags & kListBox_SvgIndex) != 0; +} + +bool GUIListBox::IsInRightMargin(int x) const +{ + if (x >= (Width - get_fixed_pixel_size(6)) && IsBorderShown() && AreArrowsShown()) + return 1; + return 0; +} + +int GUIListBox::AddItem(const String &text) +{ + guis_need_update = 1; + Items.push_back(text); + SavedGameIndex.push_back(-1); + ItemCount++; + return ItemCount - 1; +} + +void GUIListBox::Clear() +{ + Items.clear(); + SavedGameIndex.clear(); + ItemCount = 0; + SelectedItem = 0; + TopItem = 0; + guis_need_update = 1; +} + +void GUIListBox::Draw(Common::Bitmap *ds) +{ + const int width = Width - 1; + const int height = Height - 1; + const int pixel_size = get_fixed_pixel_size(1); + + check_font(&Font); + color_t text_color = ds->GetCompatibleColor(TextColor); + color_t draw_color = ds->GetCompatibleColor(TextColor); + if (IsBorderShown()) + { + ds->DrawRect(Rect(X, Y, X + width + (pixel_size - 1), Y + height + (pixel_size - 1)), draw_color); + if (pixel_size > 1) + ds->DrawRect(Rect(X + 1, Y + 1, X + width, Y + height), draw_color); + } + + int right_hand_edge = (X + width) - pixel_size - 1; + + // use SetFont to update the RowHeight and VisibleItemCount + SetFont(Font); + + // draw the scroll bar in if necessary + if (ItemCount > VisibleItemCount && IsBorderShown() && AreArrowsShown()) + { + int xstrt, ystrt; + ds->DrawRect(Rect(X + width - get_fixed_pixel_size(7), Y, (X + (pixel_size - 1) + width) - get_fixed_pixel_size(7), Y + height), draw_color); + ds->DrawRect(Rect(X + width - get_fixed_pixel_size(7), Y + height / 2, X + width, Y + height / 2 + (pixel_size - 1)), draw_color); + + xstrt = (X + width - get_fixed_pixel_size(6)) + (pixel_size - 1); + ystrt = (Y + height - 3) - get_fixed_pixel_size(5); + + draw_color = ds->GetCompatibleColor(TextColor); + ds->DrawTriangle(Triangle(xstrt, ystrt, xstrt + get_fixed_pixel_size(4), ystrt, + xstrt + get_fixed_pixel_size(2), + ystrt + get_fixed_pixel_size(5)), draw_color); + + ystrt = Y + 3; + ds->DrawTriangle(Triangle(xstrt, ystrt + get_fixed_pixel_size(5), + xstrt + get_fixed_pixel_size(4), + ystrt + get_fixed_pixel_size(5), + xstrt + get_fixed_pixel_size(2), ystrt), draw_color); + + right_hand_edge -= get_fixed_pixel_size(7); + } + + DrawItemsFix(); + + for (int item = 0; item < VisibleItemCount; ++item) + { + if (item + TopItem >= ItemCount) + break; + + int at_y = Y + pixel_size + item * RowHeight; + if (item + TopItem == SelectedItem) + { + text_color = ds->GetCompatibleColor(SelectedTextColor); + if (SelectedBgColor > 0) + { + int stretch_to = (X + width) - pixel_size; + // draw the SelectedItem item bar (if colour not transparent) + draw_color = ds->GetCompatibleColor(SelectedBgColor); + if ((VisibleItemCount < ItemCount) && IsBorderShown() && AreArrowsShown()) + stretch_to -= get_fixed_pixel_size(7); + + ds->FillRect(Rect(X + pixel_size, at_y, stretch_to, at_y + RowHeight - pixel_size), draw_color); + } + } + else + text_color = ds->GetCompatibleColor(TextColor); + + int item_index = item + TopItem; + PrepareTextToDraw(Items[item_index]); + + GUI::DrawTextAlignedHor(ds, _textToDraw, Font, text_color, X + 1 + pixel_size, right_hand_edge, at_y + 1, + (FrameAlignment)TextAlignment); + } + + DrawItemsUnfix(); +} + +int GUIListBox::InsertItem(int index, const String &text) +{ + if (index < 0 || index > ItemCount) + return -1; + + Items.insert(Items.begin() + index, text); + SavedGameIndex.insert(SavedGameIndex.begin() + index, -1); + if (SelectedItem >= index) + SelectedItem++; + + ItemCount++; + guis_need_update = 1; + return ItemCount - 1; +} + +void GUIListBox::RemoveItem(int index) +{ + if (index < 0 || index >= ItemCount) + return; + + Items.erase(Items.begin() + index); + SavedGameIndex.erase(SavedGameIndex.begin() + index); + ItemCount--; + + if (SelectedItem > index) + SelectedItem--; + if (SelectedItem >= ItemCount) + SelectedItem = -1; + guis_need_update = 1; +} + +void GUIListBox::SetShowArrows(bool on) +{ + if (on) + ListBoxFlags |= kListBox_ShowArrows; + else + ListBoxFlags &= ~kListBox_ShowArrows; +} + +void GUIListBox::SetShowBorder(bool on) +{ + if (on) + ListBoxFlags |= kListBox_ShowBorder; + else + ListBoxFlags &= ~kListBox_ShowBorder; +} + +void GUIListBox::SetSvgIndex(bool on) +{ + if (on) + ListBoxFlags |= kListBox_SvgIndex; + else + ListBoxFlags &= ~kListBox_SvgIndex; +} + +void GUIListBox::SetFont(int font) +{ + Font = font; + RowHeight = getfontheight(Font) + get_fixed_pixel_size(2); + VisibleItemCount = Height / RowHeight; +} + +void GUIListBox::SetItemText(int index, const String &text) +{ + if (index >= 0 && index < ItemCount) + { + guis_need_update = 1; + Items[index] = text; + } +} + +bool GUIListBox::OnMouseDown() +{ + if (IsInRightMargin(MousePos.X)) + { + if (MousePos.Y < Height / 2 && TopItem > 0) + TopItem--; + if (MousePos.Y >= Height / 2 && ItemCount > TopItem + VisibleItemCount) + TopItem++; + return false; + } + + int sel = GetItemAt(MousePos.X, MousePos.Y); + if (sel < 0) + return false; + SelectedItem = sel; + IsActivated = true; + return false; +} + +void GUIListBox::OnMouseMove(int x_, int y_) +{ + MousePos.X = x_ - X; + MousePos.Y = y_ - Y; +} + +void GUIListBox::OnResized() +{ + if (RowHeight == 0) + { + check_font(&Font); + SetFont(Font); + } + if (RowHeight > 0) + VisibleItemCount = Height / RowHeight; +} + +// TODO: replace string serialization with StrUtil::ReadString and WriteString +// methods in the future, to keep this organized. +void GUIListBox::WriteToFile(Stream *out) const +{ + GUIObject::WriteToFile(out); + out->WriteInt32(ItemCount); + out->WriteInt32(Font); + out->WriteInt32(TextColor); + out->WriteInt32(SelectedTextColor); + out->WriteInt32(ListBoxFlags); + out->WriteInt32(TextAlignment); + out->WriteInt32(SelectedBgColor); + for (int i = 0; i < ItemCount; ++i) + Items[i].Write(out); +} + +void GUIListBox::ReadFromFile(Stream *in, GuiVersion gui_version) +{ + Clear(); + + GUIObject::ReadFromFile(in, gui_version); + ItemCount = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + { // NOTE: reading into actual variables only for old savegame support + SelectedItem = in->ReadInt32(); + TopItem = in->ReadInt32(); + MousePos.X = in->ReadInt32(); + MousePos.Y = in->ReadInt32(); + RowHeight = in->ReadInt32(); + VisibleItemCount = in->ReadInt32(); + } + Font = in->ReadInt32(); + TextColor = in->ReadInt32(); + SelectedTextColor = in->ReadInt32(); + ListBoxFlags = in->ReadInt32(); + // reverse particular flags from older format + if (gui_version < kGuiVersion_350) + ListBoxFlags ^= kListBox_OldFmtXorMask; + + if (gui_version >= kGuiVersion_272b) + { + if (gui_version < kGuiVersion_350) + { + TextAlignment = ConvertLegacyGUIAlignment((LegacyGUIAlignment)in->ReadInt32()); + in->ReadInt32(); // reserved1 + } + else + { + TextAlignment = (HorAlignment)in->ReadInt32(); + } + } + else + { + TextAlignment = kHAlignLeft; + } + + if (gui_version >= kGuiVersion_unkn_107) + { + SelectedBgColor = in->ReadInt32(); + } + else + { + SelectedBgColor = TextColor; + if (SelectedBgColor == 0) + SelectedBgColor = 16; + } + + // NOTE: we leave items in game data format as a potential support for defining + // ListBox contents at design-time, although Editor does not support it as of 3.5.0. + Items.resize(ItemCount); + SavedGameIndex.resize(ItemCount, -1); + for (int i = 0; i < ItemCount; ++i) + { + Items[i].Read(in); + } + + if (gui_version >= kGuiVersion_272d && gui_version < kGuiVersion_350 && + (ListBoxFlags & kListBox_SvgIndex)) + { // NOTE: reading into actual variables only for old savegame support + for (int i = 0; i < ItemCount; ++i) + SavedGameIndex[i] = in->ReadInt16(); + } + + if (TextColor == 0) + TextColor = 16; +} + +void GUIListBox::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) +{ + GUIObject::ReadFromSavegame(in, svg_ver); + // Properties + ListBoxFlags = in->ReadInt32(); + Font = in->ReadInt32(); + if (svg_ver < kGuiSvgVersion_350) + { + // reverse particular flags from older format + ListBoxFlags ^= kListBox_OldFmtXorMask; + } + else + { + SelectedBgColor = in->ReadInt32(); + SelectedTextColor = in->ReadInt32(); + TextAlignment = (HorAlignment)in->ReadInt32(); + TextColor = in->ReadInt32(); + } + + // Items + ItemCount = in->ReadInt32(); + Items.resize(ItemCount); + SavedGameIndex.resize(ItemCount); + for (int i = 0; i < ItemCount; ++i) + Items[i] = StrUtil::ReadString(in); + // TODO: investigate this, it might be unreasonable to save and read + // savegame index like that because list of savegames may easily change + // in between writing and restoring the game. Perhaps clearing and forcing + // this list to update on load somehow may make more sense. + if (ListBoxFlags & kListBox_SvgIndex) + for (int i = 0; i < ItemCount; ++i) + SavedGameIndex[i] = in->ReadInt16(); + TopItem = in->ReadInt32(); + SelectedItem = in->ReadInt32(); +} + +void GUIListBox::WriteToSavegame(Stream *out) const +{ + GUIObject::WriteToSavegame(out); + // Properties + out->WriteInt32(ListBoxFlags); + out->WriteInt32(Font); + out->WriteInt32(SelectedBgColor); + out->WriteInt32(SelectedTextColor); + out->WriteInt32(TextAlignment); + out->WriteInt32(TextColor); + + // Items + out->WriteInt32(ItemCount); + for (int i = 0; i < ItemCount; ++i) + StrUtil::WriteString(Items[i], out); + if (ListBoxFlags & kListBox_SvgIndex) + for (int i = 0; i < ItemCount; ++i) + out->WriteInt16(SavedGameIndex[i]); + out->WriteInt32(TopItem); + out->WriteInt32(SelectedItem); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gui/guilistbox.h b/engines/ags/shared/gui/guilistbox.h new file mode 100644 index 00000000000..c919ac2d5a4 --- /dev/null +++ b/engines/ags/shared/gui/guilistbox.h @@ -0,0 +1,98 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GUILISTBOX_H +#define __AC_GUILISTBOX_H + +#include +#include "gui/guiobject.h" +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +class GUIListBox : public GUIObject +{ +public: + GUIListBox(); + + bool AreArrowsShown() const; + bool IsBorderShown() const; + bool IsSvgIndex() const; + bool IsInRightMargin(int x) const; + int GetItemAt(int x, int y) const; + + // Operations + int AddItem(const String &text); + void Clear(); + void Draw(Bitmap *ds) override; + int InsertItem(int index, const String &text); + void RemoveItem(int index); + void SetShowArrows(bool on); + void SetShowBorder(bool on); + void SetSvgIndex(bool on); // TODO: work around this + void SetFont(int font); + void SetItemText(int index, const String &textt); + + // Events + bool OnMouseDown() override; + void OnMouseMove(int x, int y) override; + void OnResized() override; + + // Serialization + void ReadFromFile(Stream *in, GuiVersion gui_version) override; + void WriteToFile(Stream *out) const override; + void ReadFromSavegame(Common::Stream *in, GuiSvgVersion svg_ver) override; + void WriteToSavegame(Common::Stream *out) const override; + +// TODO: these members are currently public; hide them later +public: + int32_t Font; + color_t TextColor; + HorAlignment TextAlignment; + color_t SelectedBgColor; + color_t SelectedTextColor; + int32_t RowHeight; + int32_t VisibleItemCount; + + std::vector Items; + std::vector SavedGameIndex; + int32_t SelectedItem; + int32_t TopItem; + Point MousePos; + + // TODO: remove these later + int32_t ItemCount; + +private: + int32_t ListBoxFlags; + + // A temporary solution for special drawing in the Editor + void DrawItemsFix(); + void DrawItemsUnfix(); + void PrepareTextToDraw(const String &text); + + // prepared text buffer/cache + String _textToDraw; +}; + +} // namespace Common +} // namespace AGS + +extern std::vector guilist; +extern int numguilist; + +#endif // __AC_GUILISTBOX_H diff --git a/engines/ags/shared/gui/guimain.cpp b/engines/ags/shared/gui/guimain.cpp new file mode 100644 index 00000000000..3e08125a64b --- /dev/null +++ b/engines/ags/shared/gui/guimain.cpp @@ -0,0 +1,919 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "ac/game_version.h" +#include "ac/spritecache.h" +#include "debug/out.h" +#include "font/fonts.h" +#include "gui/guibutton.h" +#include "gui/guiinv.h" +#include "gui/guilabel.h" +#include "gui/guilistbox.h" +#include "gui/guimain.h" +#include "gui/guislider.h" +#include "gui/guitextbox.h" +#include "util/stream.h" +#include "util/string_utils.h" + +using namespace AGS::Common; + +#define MOVER_MOUSEDOWNLOCKED -4000 + +int guis_need_update = 1; +int all_buttons_disabled = 0, gui_inv_pic = -1; +int gui_disabled_style = 0; + +namespace AGS +{ +namespace Common +{ + +/* static */ String GUIMain::FixupGUIName(const String &name) +{ + if (name.GetLength() > 0 && name[0u] != 'g') + return String::FromFormat("g%c%s", name[0u], name.Mid(1).Lower().GetCStr()); + return name; +} + +GUIMain::GUIMain() +{ + InitDefaults(); +} + +void GUIMain::InitDefaults() +{ + ID = 0; + Name.Empty(); + _flags = kGUIMain_DefFlags; + + X = 0; + Y = 0; + Width = 0; + Height = 0; + BgColor = 8; + BgImage = 0; + FgColor = 1; + Padding = TEXTWINDOW_PADDING_DEFAULT; + PopupStyle = kGUIPopupNormal; + PopupAtMouseY = -1; + Transparency = 0; + ZOrder = -1; + + FocusCtrl = 0; + HighlightCtrl = -1; + MouseOverCtrl = -1; + MouseDownCtrl = -1; + MouseWasAt.X = -1; + MouseWasAt.Y = -1; + + OnClickHandler.Empty(); + + _controls.clear(); + _ctrlRefs.clear(); + _ctrlDrawOrder.clear(); +} + +int GUIMain::FindControlUnderMouse(int leeway, bool must_be_clickable) const +{ + if (loaded_game_file_version <= kGameVersion_262) + { + // Ignore draw order On 2.6.2 and lower + for (size_t i = 0; i < _controls.size(); ++i) + { + if (!_controls[i]->IsVisible()) + continue; + if (!_controls[i]->IsClickable() && must_be_clickable) + continue; + if (_controls[i]->IsOverControl(mousex, mousey, leeway)) + return i; + } + } + else + { + for (size_t i = _controls.size(); i-- > 0;) + { + const int ctrl_index = _ctrlDrawOrder[i]; + if (!_controls[ctrl_index]->IsVisible()) + continue; + if (!_controls[ctrl_index]->IsClickable() && must_be_clickable) + continue; + if (_controls[ctrl_index]->IsOverControl(mousex, mousey, leeway)) + return ctrl_index; + } + } + return -1; +} + +int GUIMain::FindControlUnderMouse() const +{ + return FindControlUnderMouse(0, true); +} + +int GUIMain::FindControlUnderMouse(int leeway) const +{ + return FindControlUnderMouse(leeway, true); +} + +int GUIMain::GetControlCount() const +{ + return (int32_t)_controls.size(); +} + +GUIObject *GUIMain::GetControl(int index) const +{ + if (index < 0 || (size_t)index >= _controls.size()) + return nullptr; + return _controls[index]; +} + +GUIControlType GUIMain::GetControlType(int index) const +{ + if (index < 0 || (size_t)index >= _ctrlRefs.size()) + return kGUIControlUndefined; + return _ctrlRefs[index].first; +} + +int32_t GUIMain::GetControlID(int index) const +{ + if (index < 0 || (size_t)index >= _ctrlRefs.size()) + return -1; + return _ctrlRefs[index].second; +} + +bool GUIMain::IsClickable() const +{ + return (_flags & kGUIMain_Clickable) != 0; +} + +bool GUIMain::IsConcealed() const +{ + return (_flags & kGUIMain_Concealed) != 0; +} + +bool GUIMain::IsDisplayed() const +{ + if(!IsVisible()) return false; + if(IsConcealed()) return false; + if(Transparency == 255) return false; + return true; +} + +bool GUIMain::IsInteractableAt(int x, int y) const +{ + if (!IsDisplayed()) + return false; + if (!IsClickable()) + return false; + if ((x >= X) & (y >= Y) & (x < X + Width) & (y < Y + Height)) + return true; + return false; +} + +bool GUIMain::IsTextWindow() const +{ + return (_flags & kGUIMain_TextWindow) != 0; +} + +bool GUIMain::IsVisible() const +{ + return (_flags & kGUIMain_Visible) != 0; +} + +void GUIMain::AddControl(GUIControlType type, int id, GUIObject *control) +{ + _ctrlRefs.push_back(std::make_pair(type, id)); + _controls.push_back(control); +} + +void GUIMain::RemoveAllControls() +{ + _ctrlRefs.clear(); + _controls.clear(); +} + +bool GUIMain::BringControlToFront(int index) +{ + return SetControlZOrder(index, (int)_controls.size() - 1); +} + +void GUIMain::Draw(Bitmap *ds) +{ + DrawAt(ds, X, Y); +} + +void GUIMain::DrawAt(Bitmap *ds, int x, int y) +{ + SET_EIP(375) + + if ((Width < 1) || (Height < 1)) + return; + + Bitmap subbmp; + subbmp.CreateSubBitmap(ds, RectWH(x, y, Width, Height)); + + SET_EIP(376) + // stop border being transparent, if the whole GUI isn't + if ((FgColor == 0) && (BgColor != 0)) + FgColor = 16; + + if (BgColor != 0) + subbmp.Fill(subbmp.GetCompatibleColor(BgColor)); + + SET_EIP(377) + + color_t draw_color; + if (FgColor != BgColor) + { + draw_color = subbmp.GetCompatibleColor(FgColor); + subbmp.DrawRect(Rect(0, 0, subbmp.GetWidth() - 1, subbmp.GetHeight() - 1), draw_color); + if (get_fixed_pixel_size(1) > 1) + subbmp.DrawRect(Rect(1, 1, subbmp.GetWidth() - 2, subbmp.GetHeight() - 2), draw_color); + } + + SET_EIP(378) + + if (BgImage > 0 && spriteset[BgImage] != nullptr) + draw_gui_sprite(&subbmp, BgImage, 0, 0, false); + + SET_EIP(379) + + if (all_buttons_disabled && gui_disabled_style == GUIDIS_BLACKOUT) + return; // don't draw GUI controls + + for (size_t ctrl_index = 0; ctrl_index < _controls.size(); ++ctrl_index) + { + set_eip_guiobj(_ctrlDrawOrder[ctrl_index]); + + GUIObject *objToDraw = _controls[_ctrlDrawOrder[ctrl_index]]; + + if (!objToDraw->IsEnabled() && gui_disabled_style == GUIDIS_BLACKOUT) + continue; + if (!objToDraw->IsVisible()) + continue; + + objToDraw->Draw(&subbmp); + + int selectedColour = 14; + + if (HighlightCtrl == _ctrlDrawOrder[ctrl_index]) + { + if (outlineGuiObjects) + selectedColour = 13; + draw_color = subbmp.GetCompatibleColor(selectedColour); + DrawBlob(&subbmp, objToDraw->X + objToDraw->Width - get_fixed_pixel_size(1) - 1, objToDraw->Y, draw_color); + DrawBlob(&subbmp, objToDraw->X, objToDraw->Y + objToDraw->Height - get_fixed_pixel_size(1) - 1, draw_color); + DrawBlob(&subbmp, objToDraw->X, objToDraw->Y, draw_color); + DrawBlob(&subbmp, objToDraw->X + objToDraw->Width - get_fixed_pixel_size(1) - 1, + objToDraw->Y + objToDraw->Height - get_fixed_pixel_size(1) - 1, draw_color); + } + if (outlineGuiObjects) + { + // draw a dotted outline round all objects + draw_color = subbmp.GetCompatibleColor(selectedColour); + for (int i = 0; i < objToDraw->Width; i += 2) + { + subbmp.PutPixel(i + objToDraw->X, objToDraw->Y, draw_color); + subbmp.PutPixel(i + objToDraw->X, objToDraw->Y + objToDraw->Height - 1, draw_color); + } + for (int i = 0; i < objToDraw->Height; i += 2) + { + subbmp.PutPixel(objToDraw->X, i + objToDraw->Y, draw_color); + subbmp.PutPixel(objToDraw->X + objToDraw->Width - 1, i + objToDraw->Y, draw_color); + } + } + } + + SET_EIP(380) +} + +void GUIMain::DrawBlob(Bitmap *ds, int x, int y, color_t draw_color) +{ + ds->FillRect(Rect(x, y, x + get_fixed_pixel_size(1), y + get_fixed_pixel_size(1)), draw_color); +} + +void GUIMain::Poll() +{ + int mxwas = mousex, mywas = mousey; + + mousex -= X; + mousey -= Y; + if (mousex != MouseWasAt.X || mousey != MouseWasAt.Y) + { + int ctrl_index = FindControlUnderMouse(); + + if (MouseOverCtrl == MOVER_MOUSEDOWNLOCKED) + _controls[MouseDownCtrl]->OnMouseMove(mousex, mousey); + else if (ctrl_index != MouseOverCtrl) + { + if (MouseOverCtrl >= 0) + _controls[MouseOverCtrl]->OnMouseLeave(); + + if (ctrl_index >= 0 && !IsGUIEnabled(_controls[ctrl_index])) + // the control is disabled - ignore it + MouseOverCtrl = -1; + else if (ctrl_index >= 0 && !_controls[ctrl_index]->IsClickable()) + // the control is not clickable - ignore it + MouseOverCtrl = -1; + else + { + // over a different control + MouseOverCtrl = ctrl_index; + if (MouseOverCtrl >= 0) + { + _controls[MouseOverCtrl]->OnMouseEnter(); + _controls[MouseOverCtrl]->OnMouseMove(mousex, mousey); + } + } + guis_need_update = 1; + } + else if (MouseOverCtrl >= 0) + _controls[MouseOverCtrl]->OnMouseMove(mousex, mousey); + } + + MouseWasAt.X = mousex; + MouseWasAt.Y = mousey; + mousex = mxwas; + mousey = mywas; +} + +HError GUIMain::RebuildArray() +{ + GUIControlType thistype; + int32_t thisnum; + + _controls.resize(_ctrlRefs.size()); + for (size_t i = 0; i < _controls.size(); ++i) + { + thistype = _ctrlRefs[i].first; + thisnum = _ctrlRefs[i].second; + + if (thisnum < 0) + return new Error(String::FromFormat("GUIMain (%d): invalid control ID %d in ref #%d", ID, thisnum, i)); + + if (thistype == kGUIButton) + _controls[i] = &guibuts[thisnum]; + else if (thistype == kGUILabel) + _controls[i] = &guilabels[thisnum]; + else if (thistype == kGUIInvWindow) + _controls[i] = &guiinv[thisnum]; + else if (thistype == kGUISlider) + _controls[i] = &guislider[thisnum]; + else if (thistype == kGUITextBox) + _controls[i] = &guitext[thisnum]; + else if (thistype == kGUIListBox) + _controls[i] = &guilist[thisnum]; + else + return new Error(String::FromFormat("GUIMain (%d): unknown control type %d in ref #%d", ID, thistype, i)); + + _controls[i]->ParentId = ID; + _controls[i]->Id = i; + } + + ResortZOrder(); + return HError::None(); +} + +bool GUIControlZOrder(const GUIObject *e1, const GUIObject *e2) +{ + return e1->ZOrder < e2->ZOrder; +} + +void GUIMain::ResortZOrder() +{ + std::vector ctrl_sort = _controls; + std::sort(ctrl_sort.begin(), ctrl_sort.end(), GUIControlZOrder); + + _ctrlDrawOrder.resize(ctrl_sort.size()); + for (size_t i = 0; i < ctrl_sort.size(); ++i) + _ctrlDrawOrder[i] = ctrl_sort[i]->Id; +} + +void GUIMain::SetClickable(bool on) +{ + if (on) + _flags |= kGUIMain_Clickable; + else + _flags &= ~kGUIMain_Clickable; +} + +void GUIMain::SetConceal(bool on) +{ + if (on) + _flags |= kGUIMain_Concealed; + else + _flags &= ~kGUIMain_Concealed; +} + +bool GUIMain::SendControlToBack(int index) +{ + return SetControlZOrder(index, 0); +} + +bool GUIMain::SetControlZOrder(int index, int zorder) +{ + if (index < 0 || (size_t)index >= _controls.size()) + return false; // no such control + + zorder = Math::Clamp(zorder, 0, (int)_controls.size() - 1); + const int old_zorder = _controls[index]->ZOrder; + if (old_zorder == zorder) + return false; // no change + + const bool move_back = zorder < old_zorder; // back is at zero index + const int left = move_back ? zorder : old_zorder; + const int right = move_back ? old_zorder : zorder; + for (size_t i = 0; i < _controls.size(); ++i) + { + const int i_zorder = _controls[i]->ZOrder; + if (i_zorder == old_zorder) + _controls[i]->ZOrder = zorder; // the control we are moving + else if (i_zorder >= left && i_zorder <= right) + { + // controls in between old and new positions shift towards free place + if (move_back) + _controls[i]->ZOrder++; // move to front + else + _controls[i]->ZOrder--; // move to back + } + } + ResortZOrder(); + OnControlPositionChanged(); + return true; +} + +void GUIMain::SetTextWindow(bool on) +{ + if (on) + _flags |= kGUIMain_TextWindow; + else + _flags &= ~kGUIMain_TextWindow; +} + +void GUIMain::SetTransparencyAsPercentage(int percent) +{ + Transparency = GfxDef::Trans100ToLegacyTrans255(percent); + guis_need_update = 1; +} + +void GUIMain::SetVisible(bool on) +{ + if (on) + _flags |= kGUIMain_Visible; + else + _flags &= ~kGUIMain_Visible; +} + +void GUIMain::OnControlPositionChanged() +{ + // force it to re-check for which control is under the mouse + MouseWasAt.X = -1; + MouseWasAt.Y = -1; +} + +void GUIMain::OnMouseButtonDown() +{ + if (MouseOverCtrl < 0) + return; + + // don't activate disabled buttons + if (!IsGUIEnabled(_controls[MouseOverCtrl]) || !_controls[MouseOverCtrl]->IsVisible() || + !_controls[MouseOverCtrl]->IsClickable()) + return; + + MouseDownCtrl = MouseOverCtrl; + if (_controls[MouseOverCtrl]->OnMouseDown()) + MouseOverCtrl = MOVER_MOUSEDOWNLOCKED; + _controls[MouseDownCtrl]->OnMouseMove(mousex - X, mousey - Y); + guis_need_update = 1; +} + +void GUIMain::OnMouseButtonUp() +{ + // FocusCtrl was locked - reset it back to normal, but On the + // locked object so that a OnMouseLeave gets fired if necessary + if (MouseOverCtrl == MOVER_MOUSEDOWNLOCKED) + { + MouseOverCtrl = MouseDownCtrl; + MouseWasAt.X = -1; // force update + } + + if (MouseDownCtrl < 0) + return; + + _controls[MouseDownCtrl]->OnMouseUp(); + MouseDownCtrl = -1; + guis_need_update = 1; +} + +void GUIMain::ReadFromFile(Stream *in, GuiVersion gui_version) +{ + // Legacy text window tag + char tw_flags[GUIMAIN_LEGACY_TW_FLAGS_SIZE] = {0}; + if (gui_version < kGuiVersion_350) + in->Read(tw_flags, sizeof(tw_flags)); + + if (gui_version < kGuiVersion_340) + { + Name.ReadCount(in, GUIMAIN_LEGACY_NAME_LENGTH); + OnClickHandler.ReadCount(in, GUIMAIN_LEGACY_EVENTHANDLER_LENGTH); + } + else + { + Name = StrUtil::ReadString(in); + OnClickHandler = StrUtil::ReadString(in); + } + X = in->ReadInt32(); + Y = in->ReadInt32(); + Width = in->ReadInt32(); + Height = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + { // NOTE: reading into actual variables only for old savegame support + FocusCtrl = in->ReadInt32(); + } + const size_t ctrl_count = in->ReadInt32(); + PopupStyle = (GUIPopupStyle)in->ReadInt32(); + PopupAtMouseY = in->ReadInt32(); + BgColor = in->ReadInt32(); + BgImage = in->ReadInt32(); + FgColor = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + { // NOTE: reading into actual variables only for old savegame support + MouseOverCtrl = in->ReadInt32(); + MouseWasAt.X = in->ReadInt32(); + MouseWasAt.Y = in->ReadInt32(); + MouseDownCtrl = in->ReadInt32(); + HighlightCtrl = in->ReadInt32(); + } + _flags = in->ReadInt32(); + Transparency = in->ReadInt32(); + ZOrder = in->ReadInt32(); + ID = in->ReadInt32(); + Padding = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + in->Seek(sizeof(int32_t) * GUIMAIN_LEGACY_RESERVED_INTS); + + if (gui_version < kGuiVersion_350) + { + if (tw_flags[0] == kGUIMain_LegacyTextWindow) + _flags |= kGUIMain_TextWindow; + // reverse particular flags from older format + _flags ^= kGUIMain_OldFmtXorMask; + GUI::ApplyLegacyVisibility(*this, (LegacyGUIVisState)in->ReadInt32()); + } + + // pre-3.4.0 games contained array of 32-bit pointers; these values are unused + // TODO: error if ctrl_count > LEGACY_MAX_OBJS_ON_GUI + if (gui_version < kGuiVersion_340) + in->Seek(LEGACY_MAX_OBJS_ON_GUI * sizeof(int32_t)); + if (ctrl_count > 0) + { + _ctrlRefs.resize(ctrl_count); + for (size_t i = 0; i < ctrl_count; ++i) + { + const int32_t ref_packed = in->ReadInt32(); + _ctrlRefs[i].first = (GUIControlType)((ref_packed >> 16) & 0xFFFF); + _ctrlRefs[i].second = ref_packed & 0xFFFF; + } + } + // Skip unused control slots in pre-3.4.0 games + if (gui_version < kGuiVersion_340 && ctrl_count < LEGACY_MAX_OBJS_ON_GUI) + in->Seek((LEGACY_MAX_OBJS_ON_GUI - ctrl_count) * sizeof(int32_t)); +} + +void GUIMain::WriteToFile(Stream *out) const +{ + StrUtil::WriteString(Name, out); + StrUtil::WriteString(OnClickHandler, out); + out->WriteInt32(X); + out->WriteInt32(Y); + out->WriteInt32(Width); + out->WriteInt32(Height); + out->WriteInt32(_ctrlRefs.size()); + out->WriteInt32(PopupStyle); + out->WriteInt32(PopupAtMouseY); + out->WriteInt32(BgColor); + out->WriteInt32(BgImage); + out->WriteInt32(FgColor); + out->WriteInt32(_flags); + out->WriteInt32(Transparency); + out->WriteInt32(ZOrder); + out->WriteInt32(ID); + out->WriteInt32(Padding); + for (size_t i = 0; i < _ctrlRefs.size(); ++i) + { + int32_t ref_packed = ((_ctrlRefs[i].first & 0xFFFF) << 16) | (_ctrlRefs[i].second & 0xFFFF); + out->WriteInt32(ref_packed); + } +} + +void GUIMain::ReadFromSavegame(Common::Stream *in, GuiSvgVersion svg_version) +{ + // Properties + _flags = in->ReadInt32(); + X = in->ReadInt32(); + Y = in->ReadInt32(); + Width = in->ReadInt32(); + Height = in->ReadInt32(); + BgImage = in->ReadInt32(); + Transparency = in->ReadInt32(); + if (svg_version < kGuiSvgVersion_350) + { + // reverse particular flags from older format + _flags ^= kGUIMain_OldFmtXorMask; + GUI::ApplyLegacyVisibility(*this, (LegacyGUIVisState)in->ReadInt32()); + } + ZOrder = in->ReadInt32(); + + if (svg_version >= kGuiSvgVersion_350) + { + BgColor = in->ReadInt32(); + FgColor = in->ReadInt32(); + Padding = in->ReadInt32(); + PopupAtMouseY = in->ReadInt32(); + } + + // Dynamic values + FocusCtrl = in->ReadInt32(); + HighlightCtrl = in->ReadInt32(); + MouseOverCtrl = in->ReadInt32(); + MouseDownCtrl = in->ReadInt32(); + MouseWasAt.X = in->ReadInt32(); + MouseWasAt.Y = in->ReadInt32(); +} + +void GUIMain::WriteToSavegame(Common::Stream *out) const +{ + // Properties + out->WriteInt32(_flags); + out->WriteInt32(X); + out->WriteInt32(Y); + out->WriteInt32(Width); + out->WriteInt32(Height); + out->WriteInt32(BgImage); + out->WriteInt32(Transparency); + out->WriteInt32(ZOrder); + out->WriteInt32(BgColor); + out->WriteInt32(FgColor); + out->WriteInt32(Padding); + out->WriteInt32(PopupAtMouseY); + // Dynamic values + out->WriteInt32(FocusCtrl); + out->WriteInt32(HighlightCtrl); + out->WriteInt32(MouseOverCtrl); + out->WriteInt32(MouseDownCtrl); + out->WriteInt32(MouseWasAt.X); + out->WriteInt32(MouseWasAt.Y); +} + + +namespace GUI +{ + +GuiVersion GameGuiVersion = kGuiVersion_Initial; + +void DrawDisabledEffect(Bitmap *ds, const Rect &rc) +{ + color_t draw_color = ds->GetCompatibleColor(8); + for (int at_x = rc.Left; at_x <= rc.Right; ++at_x) + { + for (int at_y = rc.Top + at_x % 2; at_y <= rc.Bottom; at_y += 2) + { + ds->PutPixel(at_x, at_y, draw_color); + } + } +} + +void DrawTextAligned(Bitmap *ds, const char *text, int font, color_t text_color, const Rect &frame, FrameAlignment align) +{ + int text_height = wgettextheight(text, font); + if (align & kMAlignVCenter) + text_height++; // CHECKME + Rect item = AlignInRect(frame, RectWH(0, 0, wgettextwidth(text, font), text_height), align); + wouttext_outline(ds, item.Left, item.Top, font, text_color, text); +} + +void DrawTextAlignedHor(Bitmap *ds, const char *text, int font, color_t text_color, int x1, int x2, int y, FrameAlignment align) +{ + int x = AlignInHRange(x1, x2, 0, wgettextwidth(text, font), align); + wouttext_outline(ds, x, y, font, text_color, text); +} + +HError ResortGUI(std::vector &guis, bool bwcompat_ctrl_zorder = false) +{ + // set up the reverse-lookup array + for (size_t gui_index = 0; gui_index < guis.size(); ++gui_index) + { + GUIMain &gui = guis[gui_index]; + HError err = gui.RebuildArray(); + if (!err) + return err; + for (int ctrl_index = 0; ctrl_index < gui.GetControlCount(); ++ctrl_index) + { + GUIObject *gui_ctrl = gui.GetControl(ctrl_index); + gui_ctrl->ParentId = gui_index; + gui_ctrl->Id = ctrl_index; + if (bwcompat_ctrl_zorder) + gui_ctrl->ZOrder = ctrl_index; + } + gui.ResortZOrder(); + } + guis_need_update = 1; + return HError::None(); +} + +HError ReadGUI(std::vector &guis, Stream *in, bool is_savegame) +{ + if (in->ReadInt32() != (int)GUIMAGIC) + return new Error("ReadGUI: unknown format or file is corrupt"); + + GameGuiVersion = (GuiVersion)in->ReadInt32(); + Debug::Printf(kDbgMsg_Info, "Game GUI version: %d", GameGuiVersion); + size_t gui_count; + if (GameGuiVersion < kGuiVersion_214) + { + gui_count = (size_t)GameGuiVersion; + GameGuiVersion = kGuiVersion_Initial; + } + else if (GameGuiVersion > kGuiVersion_Current) + return new Error(String::FromFormat("ReadGUI: format version not supported (required %d, supported %d - %d)", + GameGuiVersion, kGuiVersion_Initial, kGuiVersion_Current)); + else + gui_count = in->ReadInt32(); + guis.resize(gui_count); + + // import the main GUI elements + for (size_t i = 0; i < gui_count; ++i) + { + GUIMain &gui = guis[i]; + gui.InitDefaults(); + gui.ReadFromFile(in, GameGuiVersion); + + // perform fixups + if (gui.Height < 2) + gui.Height = 2; + if (GameGuiVersion < kGuiVersion_unkn_103) + gui.Name.Format("GUI%d", i); + if (GameGuiVersion < kGuiVersion_260) + gui.ZOrder = i; + if (GameGuiVersion < kGuiVersion_270) + gui.OnClickHandler.Empty(); + if (GameGuiVersion < kGuiVersion_331) + gui.Padding = TEXTWINDOW_PADDING_DEFAULT; + // fix names for 2.x: "GUI" -> "gGui" + if (loaded_game_file_version <= kGameVersion_272) + gui.Name = GUIMain::FixupGUIName(gui.Name); + + // GUI popup style and visibility + if (GameGuiVersion < kGuiVersion_350 && !is_savegame) + { + // Convert legacy normal-off style into normal one + if (gui.PopupStyle == kGUIPopupLegacyNormalOff) + { + gui.PopupStyle = kGUIPopupNormal; + gui.SetVisible(false); + } + // Normal GUIs and PopupMouseY GUIs should start with Visible = true + else + { + gui.SetVisible(gui.PopupStyle != kGUIPopupModal); + } + } + + // PopupMouseY GUIs should be initially concealed + if (gui.PopupStyle == kGUIPopupMouseY) + gui.SetConceal(true); + // Assign ID to order in array + gui.ID = i; + } + + // buttons + numguibuts = in->ReadInt32(); + guibuts.resize(numguibuts); + for (int i = 0; i < numguibuts; ++i) + { + guibuts[i].ReadFromFile(in, GameGuiVersion); + } + // labels + numguilabels = in->ReadInt32(); + guilabels.resize(numguilabels); + for (int i = 0; i < numguilabels; ++i) + { + guilabels[i].ReadFromFile(in, GameGuiVersion); + } + // inv controls + numguiinv = in->ReadInt32(); + guiinv.resize(numguiinv); + for (int i = 0; i < numguiinv; ++i) + { + guiinv[i].ReadFromFile(in, GameGuiVersion); + } + + if (GameGuiVersion >= kGuiVersion_214) + { + // sliders + numguislider = in->ReadInt32(); + guislider.resize(numguislider); + for (int i = 0; i < numguislider; ++i) + { + guislider[i].ReadFromFile(in, GameGuiVersion); + } + } + if (GameGuiVersion >= kGuiVersion_222) + { + // text boxes + numguitext = in->ReadInt32(); + guitext.resize(numguitext); + for (int i = 0; i < numguitext; ++i) + { + guitext[i].ReadFromFile(in, GameGuiVersion); + } + } + if (GameGuiVersion >= kGuiVersion_230) + { + // list boxes + numguilist = in->ReadInt32(); + guilist.resize(numguilist); + for (int i = 0; i < numguilist; ++i) + { + guilist[i].ReadFromFile(in, GameGuiVersion); + } + } + return ResortGUI(guis, GameGuiVersion < kGuiVersion_272e); +} + +void WriteGUI(const std::vector &guis, Stream *out) +{ + out->WriteInt32(GUIMAGIC); + out->WriteInt32(kGuiVersion_Current); + out->WriteInt32(guis.size()); + + for (size_t i = 0; i < guis.size(); ++i) + { + guis[i].WriteToFile(out); + } + out->WriteInt32(numguibuts); + for (int i = 0; i < numguibuts; ++i) + { + guibuts[i].WriteToFile(out); + } + out->WriteInt32(numguilabels); + for (int i = 0; i < numguilabels; ++i) + { + guilabels[i].WriteToFile(out); + } + out->WriteInt32(numguiinv); + for (int i = 0; i < numguiinv; ++i) + { + guiinv[i].WriteToFile(out); + } + out->WriteInt32(numguislider); + for (int i = 0; i < numguislider; ++i) + { + guislider[i].WriteToFile(out); + } + out->WriteInt32(numguitext); + for (int i = 0; i < numguitext; ++i) + { + guitext[i].WriteToFile(out); + } + out->WriteInt32(numguilist); + for (int i = 0; i < numguilist; ++i) + { + guilist[i].WriteToFile(out); + } +} + +void ApplyLegacyVisibility(GUIMain &gui, LegacyGUIVisState vis) +{ + // kGUIPopupMouseY had its own rules, which we practically reverted now + if (gui.PopupStyle == kGUIPopupMouseY) + { + // it was only !Visible if the legacy Visibility was Concealed + gui.SetVisible(vis != kGUIVisibility_Concealed); + // and you could tell it's overridden by behavior when legacy Visibility is Off + gui.SetConceal(vis == kGUIVisibility_Off); + } + // Other GUI styles were simple + else + { + gui.SetVisible(vis != kGUIVisibility_Off); + gui.SetConceal(false); + } +} + +} // namespace GUI + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gui/guimain.h b/engines/ags/shared/gui/guimain.h new file mode 100644 index 00000000000..b7aeb5b9140 --- /dev/null +++ b/engines/ags/shared/gui/guimain.h @@ -0,0 +1,241 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GUIMAIN_H +#define __AC_GUIMAIN_H + +#include +#include "ac/common_defines.h" // TODO: split out gui drawing helpers +#include "gfx/gfx_def.h" // TODO: split out gui drawing helpers +#include "gui/guidefines.h" +#include "util/error.h" +#include "util/geometry.h" +#include "util/string.h" + +// Forward declaration +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +// There were issues when including header caused conflicts +struct GameSetupStruct; + +#define LEGACY_MAX_OBJS_ON_GUI 30 + +#define GUIMAIN_LEGACY_RESERVED_INTS 5 +#define GUIMAIN_LEGACY_NAME_LENGTH 16 +#define GUIMAIN_LEGACY_EVENTHANDLER_LENGTH 20 +#define GUIMAIN_LEGACY_TW_FLAGS_SIZE 4 + +namespace AGS +{ +namespace Common +{ + +// Legacy GUIMain visibility state, which combined Visible property and override factor +enum LegacyGUIVisState +{ + kGUIVisibility_Concealed = -1, // gui is hidden by command + kGUIVisibility_Off = 0, // gui is disabled (won't show up by command) + kGUIVisibility_On = 1 // gui is shown by command +}; + +class Bitmap; +class GUIObject; + + +class GUIMain +{ +public: + static String FixupGUIName(const String &name); + +public: + GUIMain(); + + void InitDefaults(); + + // Tells if the gui background supports alpha channel + bool HasAlphaChannel() const; + // Tells if GUI will react on clicking on it + bool IsClickable() const; + // Tells if GUI's visibility is overridden and it won't be displayed on + // screen regardless of Visible property (until concealed mode is off). + bool IsConcealed() const; + // Tells if gui is actually displayed on screen. Normally Visible property + // determines whether GUI is allowed to be seen, but there may be other + // settings that override GUI's visibility. + bool IsDisplayed() const; + // Tells if given coordinates are within interactable area of gui + // NOTE: this currently tests for actual visibility and Clickable property + bool IsInteractableAt(int x, int y) const; + // Tells if gui is a text window + bool IsTextWindow() const; + // Tells if GUI is *allowed* to be displayed and interacted with. + // This does not necessarily mean that it is displayed right now, because + // GUI may be hidden for other reasons, including overriding behavior. + // For example GUI with kGUIPopupMouseY style will not be shown unless + // mouse cursor is at certain position on screen. + bool IsVisible() const; + + int32_t FindControlUnderMouse() const; + // this version allows some extra leeway in the Editor so that + // the user can grab tiny controls + int32_t FindControlUnderMouse(int leeway) const; + int32_t FindControlUnderMouse(int leeway, bool must_be_clickable) const; + // Gets the number of the GUI child controls + int32_t GetControlCount() const; + // Gets control by its child's index + GUIObject *GetControl(int index) const; + // Gets child control's type, looks up with child's index + GUIControlType GetControlType(int index) const; + // Gets child control's global ID, looks up with child's index + int32_t GetControlID(int index) const; + + // Child control management + // Note that currently GUIMain does not own controls (should not delete them) + void AddControl(GUIControlType type, int id, GUIObject *control); + void RemoveAllControls(); + + // Operations + bool BringControlToFront(int index); + void Draw(Bitmap *ds); + void DrawAt(Bitmap *ds, int x, int y); + void Poll(); + HError RebuildArray(); + void ResortZOrder(); + bool SendControlToBack(int index); + // Sets whether GUI should react to player clicking on it + void SetClickable(bool on); + // Override GUI visibility; when in concealed mode GUI won't show up + // even if Visible = true + void SetConceal(bool on); + // Attempts to change control's zorder; returns if zorder changed + bool SetControlZOrder(int index, int zorder); + // Changes GUI style to the text window or back + void SetTextWindow(bool on); + // Sets GUI transparency as a percentage (0 - 100) where 100 = invisible + void SetTransparencyAsPercentage(int percent); + // Sets whether GUI is allowed to be displayed on screen + void SetVisible(bool on); + + // Events + void OnMouseButtonDown(); + void OnMouseButtonUp(); + void OnControlPositionChanged(); + + // Serialization + void ReadFromFile(Stream *in, GuiVersion gui_version); + void WriteToFile(Stream *out) const; + // TODO: move to engine, into gui savegame component unit + // (should read/write GUI properties accessing them by interface) + void ReadFromSavegame(Stream *in, GuiSvgVersion svg_version); + void WriteToSavegame(Stream *out) const; + +private: + void DrawBlob(Bitmap *ds, int x, int y, color_t draw_color); + + // TODO: all members are currently public; hide them later +public: + int32_t ID; // GUI identifier + String Name; // the name of the GUI + + int32_t X; + int32_t Y; + int32_t Width; + int32_t Height; + color_t BgColor; // background color + int32_t BgImage; // background sprite index + color_t FgColor; // foreground color (used as border color in normal GUIs, + // and text color in text windows) + int32_t Padding; // padding surrounding a GUI text window + GUIPopupStyle PopupStyle; // GUI popup behavior + int32_t PopupAtMouseY; // popup when mousey < this + int32_t Transparency; // "incorrect" alpha (in legacy 255-range units) + int32_t ZOrder; + + int32_t FocusCtrl; // which control has the focus + int32_t HighlightCtrl; // which control has the bounding selection rect + int32_t MouseOverCtrl; // which control has the mouse cursor over it + int32_t MouseDownCtrl; // which control has the mouse button pressed on it + Point MouseWasAt; // last mouse cursor position + + String OnClickHandler; // script function name + +private: + int32_t _flags; // style and behavior flags + + // Array of types and control indexes in global GUI object arrays; + // maps GUI child slots to actual controls and used for rebuilding Controls array + typedef std::pair ControlRef; + std::vector _ctrlRefs; + // Array of child control references (not exclusively owned!) + std::vector _controls; + // Sorted array of controls in z-order. + std::vector _ctrlDrawOrder; +}; + + +namespace GUI +{ + extern GuiVersion GameGuiVersion; + + // Draw standart "shading" effect over rectangle + void DrawDisabledEffect(Bitmap *ds, const Rect &rc); + // Draw text aligned inside rectangle + void DrawTextAligned(Bitmap *ds, const char *text, int font, color_t text_color, const Rect &frame, FrameAlignment align); + // Draw text aligned horizontally inside given bounds + void DrawTextAlignedHor(Bitmap *ds, const char *text, int font, color_t text_color, int x1, int x2, int y, FrameAlignment align); + + // TODO: remove is_savegame param after dropping support for old saves + // because only they use ReadGUI to read runtime GUI data + HError ReadGUI(std::vector &guis, Stream *in, bool is_savegame = false); + void WriteGUI(const std::vector &guis, Stream *out); + // Converts legacy GUIVisibility into appropriate GUIMain properties + void ApplyLegacyVisibility(GUIMain &gui, LegacyGUIVisState vis); +} + +} // namespace Common +} // namespace AGS + +extern std::vector guis; +extern int all_buttons_disabled, gui_inv_pic; +extern int gui_disabled_style; + +extern int mousex, mousey; + +extern int get_adjusted_spritewidth(int spr); +extern int get_adjusted_spriteheight(int spr); +extern bool is_sprite_alpha(int spr); + +// This function has distinct implementations in Engine and Editor +extern void draw_gui_sprite(Common::Bitmap *ds, int spr, int x, int y, bool use_alpha = true, + Common::BlendMode blend_mode = Common::kBlendMode_Alpha); + +extern AGS_INLINE int game_to_data_coord(int coord); +extern AGS_INLINE int data_to_game_coord(int coord); +extern AGS_INLINE void data_to_game_coords(int *x, int *y); +extern AGS_INLINE int get_fixed_pixel_size(int pixels); + +// Those function have distinct implementations in Engine and Editor +extern void wouttext_outline(Common::Bitmap *ds, int xxp, int yyp, int usingfont, color_t text_color, const char *texx); +extern int wgettextwidth_compensate(Common::Bitmap *ds, const char *tex, int font) ; +extern void check_font(int *fontnum); + +extern void set_our_eip(int eip); +#define SET_EIP(x) set_our_eip(x); +extern void set_eip_guiobj(int eip); +extern int get_eip_guiobj(); + +extern bool outlineGuiObjects; + +#endif // __AC_GUIMAIN_H diff --git a/engines/ags/shared/gui/guiobject.cpp b/engines/ags/shared/gui/guiobject.cpp new file mode 100644 index 00000000000..0e14b0ce590 --- /dev/null +++ b/engines/ags/shared/gui/guiobject.cpp @@ -0,0 +1,214 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/common.h" // quit +#include "gui/guimain.h" +#include "gui/guiobject.h" +#include "util/stream.h" + +namespace AGS +{ +namespace Common +{ + +GUIObject::GUIObject() +{ + Id = 0; + ParentId = 0; + Flags = kGUICtrl_DefFlags; + X = 0; + Y = 0; + Width = 0; + Height = 0; + ZOrder = -1; + IsActivated = false; + _scEventCount = 0; +} + +int GUIObject::GetEventCount() const +{ + return _scEventCount; +} + +String GUIObject::GetEventName(int event) const +{ + if (event < 0 || event >= _scEventCount) + return ""; + return _scEventNames[event]; +} + +String GUIObject::GetEventArgs(int event) const +{ + if (event < 0 || event >= _scEventCount) + return ""; + return _scEventArgs[event]; +} + +bool GUIObject::IsDeleted() const +{ + return (Flags & kGUICtrl_Deleted) != 0; +} + +bool GUIObject::IsEnabled() const +{ + return (Flags & kGUICtrl_Enabled) != 0; +} + +bool GUIObject::IsOverControl(int x, int y, int leeway) const +{ + return x >= X && y >= Y && x < (X + Width + leeway) && y < (Y + Height + leeway); +} + +bool GUIObject::IsTranslated() const +{ + return (Flags & kGUICtrl_Translated) != 0; +} + +bool GUIObject::IsVisible() const +{ + return (Flags & kGUICtrl_Visible) != 0; +} + +void GUIObject::SetClickable(bool on) +{ + if (on) + Flags |= kGUICtrl_Clickable; + else + Flags &= ~kGUICtrl_Clickable; +} + +void GUIObject::SetEnabled(bool on) +{ + if (on) + Flags |= kGUICtrl_Enabled; + else + Flags &= ~kGUICtrl_Enabled; +} + +void GUIObject::SetTranslated(bool on) +{ + if (on) + Flags |= kGUICtrl_Translated; + else + Flags &= ~kGUICtrl_Translated; +} + +void GUIObject::SetVisible(bool on) +{ + if (on) + Flags |= kGUICtrl_Visible; + else + Flags &= ~kGUICtrl_Visible; +} + +// TODO: replace string serialization with StrUtil::ReadString and WriteString +// methods in the future, to keep this organized. +void GUIObject::WriteToFile(Stream *out) const +{ + out->WriteInt32(Flags); + out->WriteInt32(X); + out->WriteInt32(Y); + out->WriteInt32(Width); + out->WriteInt32(Height); + out->WriteInt32(ZOrder); + Name.Write(out); + out->WriteInt32(_scEventCount); + for (int i = 0; i < _scEventCount; ++i) + EventHandlers[i].Write(out); +} + +void GUIObject::ReadFromFile(Stream *in, GuiVersion gui_version) +{ + Flags = in->ReadInt32(); + // reverse particular flags from older format + if (gui_version < kGuiVersion_350) + Flags ^= kGUICtrl_OldFmtXorMask; + X = in->ReadInt32(); + Y = in->ReadInt32(); + Width = in->ReadInt32(); + Height = in->ReadInt32(); + ZOrder = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + { // NOTE: reading into actual variables only for old savegame support + IsActivated = in->ReadInt32() != 0; + } + + if (gui_version >= kGuiVersion_unkn_106) + Name.Read(in); + else + Name.Free(); + + for (int i = 0; i < _scEventCount; ++i) + { + EventHandlers[i].Free(); + } + + if (gui_version >= kGuiVersion_unkn_108) + { + int evt_count = in->ReadInt32(); + if (evt_count > _scEventCount) + quit("Error: too many control events, need newer version"); + for (int i = 0; i < evt_count; ++i) + { + EventHandlers[i].Read(in); + } + } +} + +void GUIObject::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) +{ + // Properties + Flags = in->ReadInt32(); + // reverse particular flags from older format + if (svg_ver < kGuiSvgVersion_350) + Flags ^= kGUICtrl_OldFmtXorMask; + X = in->ReadInt32(); + Y = in->ReadInt32(); + Width = in->ReadInt32(); + Height = in->ReadInt32(); + ZOrder = in->ReadInt32(); + // Dynamic state + IsActivated = in->ReadBool() ? 1 : 0; +} + +void GUIObject::WriteToSavegame(Stream *out) const +{ + // Properties + out->WriteInt32(Flags); + out->WriteInt32(X); + out->WriteInt32(Y); + out->WriteInt32(Width); + out->WriteInt32(Height); + out->WriteInt32(ZOrder); + // Dynamic state + out->WriteBool(IsActivated != 0); +} + + +HorAlignment ConvertLegacyGUIAlignment(LegacyGUIAlignment align) +{ + switch (align) + { + case kLegacyGUIAlign_Left: + return kHAlignLeft; + case kLegacyGUIAlign_Right: + return kHAlignRight; + case kLegacyGUIAlign_Center: + return kHAlignCenter; + } + return kHAlignNone; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gui/guiobject.h b/engines/ags/shared/gui/guiobject.h new file mode 100644 index 00000000000..4067a1b8985 --- /dev/null +++ b/engines/ags/shared/gui/guiobject.h @@ -0,0 +1,126 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GUIOBJECT_H +#define __AC_GUIOBJECT_H + +#include "core/types.h" +#include "gfx/bitmap.h" +#include "gui/guidefines.h" +#include "util/string.h" + +#define GUIDIS_GREYOUT 1 +#define GUIDIS_BLACKOUT 2 +#define GUIDIS_UNCHANGED 4 +#define GUIDIS_GUIOFF 0x80 + + + + +namespace AGS +{ +namespace Common +{ + +enum LegacyGUIAlignment +{ + kLegacyGUIAlign_Left = 0, + kLegacyGUIAlign_Right = 1, + kLegacyGUIAlign_Center = 2 +}; + +class GUIObject +{ +public: + GUIObject(); + virtual ~GUIObject() = default; + + String GetEventArgs(int event) const; + int GetEventCount() const; + String GetEventName(int event) const; + bool IsDeleted() const; + // tells if control itself is enabled + bool IsEnabled() const; + // overridable routine to determine whether the mouse is over the control + virtual bool IsOverControl(int x, int y, int leeway) const; + bool IsTranslated() const; + bool IsVisible() const; + // implemented separately in engine and editor + bool IsClickable() const; + + // Operations + virtual void Draw(Bitmap *ds) { } + void SetClickable(bool on); + void SetEnabled(bool on); + void SetTranslated(bool on); + void SetVisible(bool on); + + // Events + // Key pressed for control + virtual void OnKeyPress(int keycode) { } + // Mouse button down - return 'True' to lock focus + virtual bool OnMouseDown() { return false; } + // Mouse moves onto control + virtual void OnMouseEnter() { } + // Mouse moves off control + virtual void OnMouseLeave() { } + // Mouse moves over control - x,y relative to gui + virtual void OnMouseMove(int x, int y) { } + // Mouse button up + virtual void OnMouseUp() { } + // Control was resized + virtual void OnResized() { } + + // Serialization + virtual void ReadFromFile(Common::Stream *in, GuiVersion gui_version); + virtual void WriteToFile(Common::Stream *out) const; + virtual void ReadFromSavegame(Common::Stream *in, GuiSvgVersion svg_ver); + virtual void WriteToSavegame(Common::Stream *out) const; + +// TODO: these members are currently public; hide them later +public: + int32_t Id; // GUI object's identifier + int32_t ParentId; // id of parent GUI + String Name; // script name + + int32_t X; + int32_t Y; + int32_t Width; + int32_t Height; + int32_t ZOrder; + bool IsActivated; // signals user interaction + + String EventHandlers[MAX_GUIOBJ_EVENTS]; // script function names + +protected: + uint32_t Flags; // generic style and behavior flags + + // TODO: explicit event names & handlers for every event + int32_t _scEventCount; // number of supported script events + String _scEventNames[MAX_GUIOBJ_EVENTS]; // script event names + String _scEventArgs[MAX_GUIOBJ_EVENTS]; // script handler params +}; + +// Converts legacy alignment type used in GUI Label/ListBox data (only left/right/center) +HorAlignment ConvertLegacyGUIAlignment(LegacyGUIAlignment align); + +} // namespace Common +} // namespace AGS + +// Tells if all controls are disabled +extern int all_buttons_disabled; +// Tells if the given control is considered enabled, taking global flag into account +inline bool IsGUIEnabled(AGS::Common::GUIObject *g) { return !all_buttons_disabled && g->IsEnabled(); } + +#endif // __AC_GUIOBJECT_H diff --git a/engines/ags/shared/gui/guislider.cpp b/engines/ags/shared/gui/guislider.cpp new file mode 100644 index 00000000000..492f70b41f0 --- /dev/null +++ b/engines/ags/shared/gui/guislider.cpp @@ -0,0 +1,267 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "ac/spritecache.h" +#include "gui/guimain.h" +#include "gui/guislider.h" +#include "util/stream.h" + +std::vector guislider; +int numguislider = 0; + +namespace AGS +{ +namespace Common +{ + +GUISlider::GUISlider() +{ + MinValue = 0; + MaxValue = 10; + Value = 0; + BgImage = 0; + HandleImage = 0; + HandleOffset = 0; + IsMousePressed = false; + + _scEventCount = 1; + _scEventNames[0] = "Change"; + _scEventArgs[0] = "GUIControl *control"; +} + +bool GUISlider::IsHorizontal() const +{ + return Width > Height; +} + +bool GUISlider::IsOverControl(int X, int Y, int leeway) const +{ + // check the overall boundary + if (GUIObject::IsOverControl(X, Y, leeway)) + return true; + // now check the handle too + return _cachedHandle.IsInside(Point(X, Y)); +} + +void GUISlider::Draw(Common::Bitmap *ds) +{ + Rect bar; + Rect handle; + int thickness; + + if (MinValue >= MaxValue) + MaxValue = MinValue + 1; + Value = Math::Clamp(Value, MinValue, MaxValue); + + // it's a horizontal slider + if (IsHorizontal()) + { + thickness = Height / 3; + bar.Left = X + 1; + bar.Top = Y + Height / 2 - thickness; + bar.Right = X + Width - 1; + bar.Bottom = Y + Height / 2 + thickness + 1; + handle.Left = (int)(((float)(Value - MinValue) / (float)(MaxValue - MinValue)) * (float)(Width - 4) - 2) + bar.Left + 1; + handle.Top = bar.Top - (thickness - 1); + handle.Right = handle.Left + get_fixed_pixel_size(4); + handle.Bottom = bar.Bottom + (thickness - 1); + if (HandleImage > 0) + { + // store the centre of the pic rather than the top + handle.Top = bar.Top + (bar.Bottom - bar.Top) / 2 + get_fixed_pixel_size(1); + handle.Left += get_fixed_pixel_size(2); + } + handle.Top += data_to_game_coord(HandleOffset); + handle.Bottom += data_to_game_coord(HandleOffset); + } + // vertical slider + else + { + thickness = Width / 3; + bar.Left = X + Width / 2 - thickness; + bar.Top = Y + 1; + bar.Right = X + Width / 2 + thickness + 1; + bar.Bottom = Y + Height - 1; + handle.Top = (int)(((float)(MaxValue - Value) / (float)(MaxValue - MinValue)) * (float)(Height - 4) - 2) + bar.Top + 1; + handle.Left = bar.Left - (thickness - 1); + handle.Bottom = handle.Top + get_fixed_pixel_size(4); + handle.Right = bar.Right + (thickness - 1); + if (HandleImage > 0) + { + // store the centre of the pic rather than the left + handle.Left = bar.Left + (bar.Right - bar.Left) / 2 + get_fixed_pixel_size(1); + handle.Top += get_fixed_pixel_size(2); + } + handle.Left += data_to_game_coord(HandleOffset); + handle.Right += data_to_game_coord(HandleOffset); + } + + color_t draw_color; + if (BgImage > 0) + { + // tiled image as slider background + int x_inc = 0; + int y_inc = 0; + if (IsHorizontal()) + { + x_inc = get_adjusted_spritewidth(BgImage); + // centre the image vertically + bar.Top = Y + (Height / 2) - get_adjusted_spriteheight(BgImage) / 2; + } + else + { + y_inc = get_adjusted_spriteheight(BgImage); + // centre the image horizontally + bar.Left = X + (Width / 2) - get_adjusted_spritewidth(BgImage) / 2; + } + int cx = bar.Left; + int cy = bar.Top; + // draw the tiled background image + do + { + draw_gui_sprite(ds, BgImage, cx, cy, true); + cx += x_inc; + cy += y_inc; + // done as a do..while so that at least one of the image is drawn + } + while ((cx + x_inc <= bar.Right) && (cy + y_inc <= bar.Bottom)); + } + else + { + // normal grey background + draw_color = ds->GetCompatibleColor(16); + ds->FillRect(Rect(bar.Left + 1, bar.Top + 1, bar.Right - 1, bar.Bottom - 1), draw_color); + draw_color = ds->GetCompatibleColor(8); + ds->DrawLine(Line(bar.Left, bar.Top, bar.Left, bar.Bottom), draw_color); + ds->DrawLine(Line(bar.Left, bar.Top, bar.Right, bar.Top), draw_color); + draw_color = ds->GetCompatibleColor(15); + ds->DrawLine(Line(bar.Right, bar.Top + 1, bar.Right, bar.Bottom), draw_color); + ds->DrawLine(Line(bar.Left, bar.Bottom, bar.Right, bar.Bottom), draw_color); + } + + if (HandleImage > 0) + { + // an image for the slider handle + // TODO: react to sprites initialization/deletion instead! + if (spriteset[HandleImage] == nullptr) + HandleImage = 0; + + handle.Left -= get_adjusted_spritewidth(HandleImage) / 2; + handle.Top -= get_adjusted_spriteheight(HandleImage) / 2; + draw_gui_sprite(ds, HandleImage, handle.Left, handle.Top, true); + handle.Right = handle.Left + get_adjusted_spritewidth(HandleImage); + handle.Bottom = handle.Top + get_adjusted_spriteheight(HandleImage); + } + else + { + // normal grey tracker handle + draw_color = ds->GetCompatibleColor(7); + ds->FillRect(Rect(handle.Left, handle.Top, handle.Right, handle.Bottom), draw_color); + draw_color = ds->GetCompatibleColor(15); + ds->DrawLine(Line(handle.Left, handle.Top, handle.Right, handle.Top), draw_color); + ds->DrawLine(Line(handle.Left, handle.Top, handle.Left, handle.Bottom), draw_color); + draw_color = ds->GetCompatibleColor(16); + ds->DrawLine(Line(handle.Right, handle.Top + 1, handle.Right, handle.Bottom), draw_color); + ds->DrawLine(Line(handle.Left + 1, handle.Bottom, handle.Right, handle.Bottom), draw_color); + } + + _cachedHandle = handle; +} + +bool GUISlider::OnMouseDown() +{ + IsMousePressed = true; + // lock focus to ourselves + return true; +} + +void GUISlider::OnMouseMove(int x, int y) +{ + if (!IsMousePressed) + return; + + if (IsHorizontal()) + Value = (int)(((float)((x - X) - 2) / (float)(Width - 4)) * (float)(MaxValue - MinValue)) + MinValue; + else + Value = (int)(((float)(((Y + Height) - y) - 2) / (float)(Height - 4)) * (float)(MaxValue - MinValue)) + MinValue; + + Value = Math::Clamp(Value, MinValue, MaxValue); + guis_need_update = 1; + IsActivated = true; +} + +void GUISlider::OnMouseUp() +{ + IsMousePressed = false; +} + +void GUISlider::ReadFromFile(Stream *in, GuiVersion gui_version) +{ + GUIObject::ReadFromFile(in, gui_version); + MinValue = in->ReadInt32(); + MaxValue = in->ReadInt32(); + Value = in->ReadInt32(); + if (gui_version < kGuiVersion_350) + { // NOTE: reading into actual variables only for old savegame support + IsMousePressed = in->ReadInt32() != 0; + } + if (gui_version >= kGuiVersion_unkn_104) + { + HandleImage = in->ReadInt32(); + HandleOffset = in->ReadInt32(); + BgImage = in->ReadInt32(); + } + else + { + HandleImage = -1; + HandleOffset = 0; + BgImage = 0; + } +} + +void GUISlider::WriteToFile(Stream *out) const +{ + GUIObject::WriteToFile(out); + out->WriteInt32(MinValue); + out->WriteInt32(MaxValue); + out->WriteInt32(Value); + out->WriteInt32(HandleImage); + out->WriteInt32(HandleOffset); + out->WriteInt32(BgImage); +} + +void GUISlider::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) +{ + GUIObject::ReadFromSavegame(in, svg_ver); + BgImage = in->ReadInt32(); + HandleImage = in->ReadInt32(); + HandleOffset = in->ReadInt32(); + MinValue = in->ReadInt32(); + MaxValue = in->ReadInt32(); + Value = in->ReadInt32(); +} + +void GUISlider::WriteToSavegame(Stream *out) const +{ + GUIObject::WriteToSavegame(out); + out->WriteInt32(BgImage); + out->WriteInt32(HandleImage); + out->WriteInt32(HandleOffset); + out->WriteInt32(MinValue); + out->WriteInt32(MaxValue); + out->WriteInt32(Value); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gui/guislider.h b/engines/ags/shared/gui/guislider.h new file mode 100644 index 00000000000..86078a29e05 --- /dev/null +++ b/engines/ags/shared/gui/guislider.h @@ -0,0 +1,71 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GUISLIDER_H +#define __AC_GUISLIDER_H + +#include +#include "gui/guiobject.h" + +namespace AGS +{ +namespace Common +{ + +class GUISlider : public GUIObject +{ +public: + GUISlider(); + + // Tells if the slider is horizontal (otherwise - vertical) + bool IsHorizontal() const; + bool IsOverControl(int x, int y, int leeway) const override; + + // Operations + void Draw(Bitmap *ds) override; + + // Events + bool OnMouseDown() override; + void OnMouseMove(int xp, int yp) override; + void OnMouseUp() override; + + // Serialization + void ReadFromFile(Stream *in, GuiVersion gui_version) override; + void WriteToFile(Stream *out) const override; + void ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) override; + void WriteToSavegame(Stream *out) const override; + +// TODO: these members are currently public; hide them later +public: + int32_t MinValue; + int32_t MaxValue; + int32_t Value; + int32_t BgImage; + int32_t HandleImage; + int32_t HandleOffset; + bool IsMousePressed; + +private: + // The following variables are not persisted on disk + // Cached coordinates of slider handle + Rect _cachedHandle; +}; + +} // namespace Common +} // namespace AGS + +extern std::vector guislider; +extern int numguislider; + +#endif // __AC_GUISLIDER_H diff --git a/engines/ags/shared/gui/guitextbox.cpp b/engines/ags/shared/gui/guitextbox.cpp new file mode 100644 index 00000000000..5e05e289d6a --- /dev/null +++ b/engines/ags/shared/gui/guitextbox.cpp @@ -0,0 +1,146 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "font/fonts.h" +#include "gui/guimain.h" +#include "gui/guitextbox.h" +#include "util/stream.h" +#include "util/string_utils.h" + +#define GUITEXTBOX_LEGACY_TEXTLEN 200 + +std::vector guitext; +int numguitext = 0; + +namespace AGS +{ +namespace Common +{ + +GUITextBox::GUITextBox() +{ + Font = 0; + TextColor = 0; + TextBoxFlags = kTextBox_DefFlags; + + _scEventCount = 1; + _scEventNames[0] = "Activate"; + _scEventArgs[0] = "GUIControl *control"; +} + +bool GUITextBox::IsBorderShown() const +{ + return (TextBoxFlags & kTextBox_ShowBorder) != 0; +} + +void GUITextBox::Draw(Bitmap *ds) +{ + check_font(&Font); + color_t text_color = ds->GetCompatibleColor(TextColor); + color_t draw_color = ds->GetCompatibleColor(TextColor); + if (IsBorderShown()) + { + ds->DrawRect(RectWH(X, Y, Width, Height), draw_color); + if (get_fixed_pixel_size(1) > 1) + { + ds->DrawRect(Rect(X + 1, Y + 1, X + Width - get_fixed_pixel_size(1), Y + Height - get_fixed_pixel_size(1)), draw_color); + } + } + DrawTextBoxContents(ds, text_color); +} + +void GUITextBox::OnKeyPress(int keycode) +{ + guis_need_update = 1; + // TODO: use keycode constants + // backspace, remove character + if (keycode == 8) + { + Text.ClipRight(1); + return; + } + // other key, continue + if ((keycode >= 128) && (!font_supports_extended_characters(Font))) + return; + // return/enter + if (keycode == 13) + { + IsActivated = true; + return; + } + + Text.AppendChar(keycode); + // if the new string is too long, remove the new character + if (wgettextwidth(Text, Font) > (Width - (6 + get_fixed_pixel_size(5)))) + Text.ClipRight(1); +} + +void GUITextBox::SetShowBorder(bool on) +{ + if (on) + TextBoxFlags |= kTextBox_ShowBorder; + else + TextBoxFlags &= ~kTextBox_ShowBorder; +} + +// TODO: replace string serialization with StrUtil::ReadString and WriteString +// methods in the future, to keep this organized. +void GUITextBox::WriteToFile(Stream *out) const +{ + GUIObject::WriteToFile(out); + StrUtil::WriteString(Text, out); + out->WriteInt32(Font); + out->WriteInt32(TextColor); + out->WriteInt32(TextBoxFlags); +} + +void GUITextBox::ReadFromFile(Stream *in, GuiVersion gui_version) +{ + GUIObject::ReadFromFile(in, gui_version); + if (gui_version < kGuiVersion_350) + Text.ReadCount(in, GUITEXTBOX_LEGACY_TEXTLEN); + else + Text = StrUtil::ReadString(in); + Font = in->ReadInt32(); + TextColor = in->ReadInt32(); + TextBoxFlags = in->ReadInt32(); + // reverse particular flags from older format + if (gui_version < kGuiVersion_350) + TextBoxFlags ^= kTextBox_OldFmtXorMask; + + if (TextColor == 0) + TextColor = 16; +} + +void GUITextBox::ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) +{ + GUIObject::ReadFromSavegame(in, svg_ver); + Font = in->ReadInt32(); + TextColor = in->ReadInt32(); + Text = StrUtil::ReadString(in); + if (svg_ver >= kGuiSvgVersion_350) + TextBoxFlags = in->ReadInt32(); +} + +void GUITextBox::WriteToSavegame(Stream *out) const +{ + GUIObject::WriteToSavegame(out); + out->WriteInt32(Font); + out->WriteInt32(TextColor); + StrUtil::WriteString(Text, out); + out->WriteInt32(TextBoxFlags); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/gui/guitextbox.h b/engines/ags/shared/gui/guitextbox.h new file mode 100644 index 00000000000..cdbad55ad85 --- /dev/null +++ b/engines/ags/shared/gui/guitextbox.h @@ -0,0 +1,65 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_GUITEXTBOX_H +#define __AC_GUITEXTBOX_H + +#include +#include "gui/guiobject.h" +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +class GUITextBox : public GUIObject +{ +public: + GUITextBox(); + + bool IsBorderShown() const; + + // Operations + void Draw(Bitmap *ds) override; + void SetShowBorder(bool on); + + // Events + void OnKeyPress(int keycode) override; + + // Serialization + void ReadFromFile(Stream *in, GuiVersion gui_version) override; + void WriteToFile(Stream *out) const override; + void ReadFromSavegame(Stream *in, GuiSvgVersion svg_ver) override; + void WriteToSavegame(Stream *out) const override; + +// TODO: these members are currently public; hide them later +public: + int32_t Font; + String Text; + color_t TextColor; + +private: + int32_t TextBoxFlags; + + void DrawTextBoxContents(Bitmap *ds, color_t text_color); +}; + +} // namespace Common +} // namespace AGS + +extern std::vector guitext; +extern int numguitext; + +#endif // __AC_GUITEXTBOX_H diff --git a/engines/ags/shared/script/cc_error.cpp b/engines/ags/shared/script/cc_error.cpp new file mode 100644 index 00000000000..8f420b90589 --- /dev/null +++ b/engines/ags/shared/script/cc_error.cpp @@ -0,0 +1,63 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "script/script_common.h" // current_line +#include "util/string.h" + +using namespace AGS::Common; + +// Returns full script error message and callstack (if possible) +extern std::pair cc_error_at_line(const char *error_msg); +// Returns script error message without location or callstack +extern String cc_error_without_line(const char *error_msg); + +int ccError = 0; +int ccErrorLine = 0; +String ccErrorString; +String ccErrorCallStack; +bool ccErrorIsUserError = false; +const char *ccCurScriptName = ""; + +void cc_error(const char *descr, ...) +{ + ccErrorIsUserError = false; + if (descr[0] == '!') + { + ccErrorIsUserError = true; + descr++; + } + + va_list ap; + va_start(ap, descr); + String displbuf = String::FromFormatV(descr, ap); + va_end(ap); + + if (currentline > 0) + { + // [IKM] Implementation is project-specific + std::pair errinfo = cc_error_at_line(displbuf); + ccErrorString = errinfo.first; + ccErrorCallStack = errinfo.second; + } + else + { + ccErrorString = cc_error_without_line(displbuf); + ccErrorCallStack = ""; + } + + ccError = 1; + ccErrorLine = currentline; +} diff --git a/engines/ags/shared/script/cc_error.h b/engines/ags/shared/script/cc_error.h new file mode 100644 index 00000000000..4b322379d10 --- /dev/null +++ b/engines/ags/shared/script/cc_error.h @@ -0,0 +1,34 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// 'C'-style script compiler +// +//============================================================================= + +#ifndef __CC_ERROR_H +#define __CC_ERROR_H + +#include "util/string.h" + +extern void cc_error(const char *, ...); + +// error reporting +extern int ccError; // set to non-zero if error occurs +extern int ccErrorLine; // line number of the error +extern AGS::Common::String ccErrorString; // description of the error +extern AGS::Common::String ccErrorCallStack; // callstack where error happened +extern bool ccErrorIsUserError; +extern const char *ccCurScriptName; // name of currently compiling script + +#endif // __CC_ERROR_H diff --git a/engines/ags/shared/script/cc_options.cpp b/engines/ags/shared/script/cc_options.cpp new file mode 100644 index 00000000000..556fdc7e798 --- /dev/null +++ b/engines/ags/shared/script/cc_options.cpp @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "cc_options.h" + +int ccCompOptions = SCOPT_LEFTTORIGHT; + +void ccSetOption(int optbit, int onoroff) +{ + if (onoroff) + ccCompOptions |= optbit; + else + ccCompOptions &= ~optbit; +} + +int ccGetOption(int optbit) +{ + if (ccCompOptions & optbit) + return 1; + + return 0; +} diff --git a/engines/ags/shared/script/cc_options.h b/engines/ags/shared/script/cc_options.h new file mode 100644 index 00000000000..fd179605d4d --- /dev/null +++ b/engines/ags/shared/script/cc_options.h @@ -0,0 +1,34 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// 'C'-style script compiler +// +//============================================================================= + +#ifndef __CC_OPTIONS_H +#define __CC_OPTIONS_H + +#define SCOPT_EXPORTALL 1 // export all functions automatically +#define SCOPT_SHOWWARNINGS 2 // printf warnings to console +#define SCOPT_LINENUMBERS 4 // include line numbers in compiled code +#define SCOPT_AUTOIMPORT 8 // when creating instance, export funcs to other scripts +#define SCOPT_DEBUGRUN 0x10 // write instructions as they are procssed to log file +#define SCOPT_NOIMPORTOVERRIDE 0x20 // do not allow an import to be re-declared +#define SCOPT_LEFTTORIGHT 0x40 // left-to-right operator precedance +#define SCOPT_OLDSTRINGS 0x80 // allow old-style strings + +extern void ccSetOption(int, int); +extern int ccGetOption(int); + +#endif diff --git a/engines/ags/shared/script/cc_script.cpp b/engines/ags/shared/script/cc_script.cpp new file mode 100644 index 00000000000..b2383e21c2d --- /dev/null +++ b/engines/ags/shared/script/cc_script.cpp @@ -0,0 +1,404 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "script/cc_error.h" +#include "script/cc_script.h" +#include "script/script_common.h" +#include "util/stream.h" +#include "util/string_compat.h" + +using AGS::Common::Stream; + +// currently executed line +int currentline; +// script file format signature +const char scfilesig[5] = "SCOM"; + +// [IKM] I reckon this function is almost identical to fgetstring in string_utils +void freadstring(char **strptr, Stream *in) +{ + static char ibuffer[300]; + int idxx = 0; + + while ((ibuffer[idxx] = in->ReadInt8()) != 0) + idxx++; + + if (ibuffer[0] == 0) { + strptr[0] = nullptr; + return; + } + + strptr[0] = (char *)malloc(strlen(ibuffer) + 1); + strcpy(strptr[0], ibuffer); +} + +void fwritestring(const char *strptr, Stream *out) +{ + if(strptr == nullptr){ + out->WriteByte(0); + } else { + out->Write(strptr, strlen(strptr) + 1); + } +} + +ccScript *ccScript::CreateFromStream(Stream *in) +{ + ccScript *scri = new ccScript(); + if (!scri->Read(in)) + { + delete scri; + return nullptr; + } + return scri; +} + +ccScript::ccScript() +{ + globaldata = nullptr; + globaldatasize = 0; + code = nullptr; + codesize = 0; + strings = nullptr; + stringssize = 0; + fixuptypes = nullptr; + fixups = nullptr; + numfixups = 0; + importsCapacity = 0; + imports = nullptr; + numimports = 0; + exportsCapacity = 0; + exports = nullptr; + export_addr = nullptr; + numexports = 0; + instances = 0; + sectionNames = nullptr; + sectionOffsets = nullptr; + numSections = 0; + capacitySections = 0; +} + +ccScript::ccScript(const ccScript &src) +{ + globaldatasize = src.globaldatasize; + if (globaldatasize > 0) + { + globaldata = (char*)malloc(globaldatasize); + memcpy(globaldata, src.globaldata, globaldatasize); + } + else + { + globaldata = nullptr; + } + + codesize = src.codesize; + if (codesize > 0) + { + code = (int32_t*)malloc(codesize * sizeof(int32_t)); + memcpy(code, src.code, sizeof(int32_t) * codesize); + } + else + { + code = nullptr; + } + + stringssize = src.stringssize; + if (stringssize > 0) + { + strings = (char*)malloc(stringssize); + memcpy(strings, src.strings, stringssize); + } + else + { + strings = nullptr; + } + + numfixups = src.numfixups; + if (numfixups > 0) + { + fixuptypes = (char*)malloc(numfixups); + fixups = (int32_t*)malloc(numfixups * sizeof(int32_t)); + memcpy(fixuptypes, src.fixuptypes, numfixups); + memcpy(fixups, src.fixups, numfixups * sizeof(int32_t)); + } + else + { + fixups = nullptr; + fixuptypes = nullptr; + } + + importsCapacity = src.numimports; + numimports = src.numimports; + if (numimports > 0) + { + imports = (char**)malloc(sizeof(char*) * numimports); + for (int i = 0; i < numimports; ++i) + imports[i] = ags_strdup(src.imports[i]); + } + else + { + imports = nullptr; + } + + exportsCapacity = src.numexports; + numexports = src.numexports; + if (numexports > 0) + { + exports = (char**)malloc(sizeof(char*) * numexports); + export_addr = (int32_t*)malloc(sizeof(int32_t) * numexports); + for (int i = 0; i < numexports; ++i) + { + exports[i] = ags_strdup(src.exports[i]); + export_addr[i] = src.export_addr[i]; + } + } + else + { + exports = nullptr; + export_addr = nullptr; + } + + capacitySections = src.numSections; + numSections = src.numSections; + if (numSections > 0) + { + sectionNames = (char**)malloc(numSections * sizeof(char*)); + sectionOffsets = (int32_t*)malloc(numSections * sizeof(int32_t)); + for (int i = 0; i < numSections; ++i) + { + sectionNames[i] = ags_strdup(src.sectionNames[i]); + sectionOffsets[i] = src.sectionOffsets[i]; + } + } + else + { + numSections = 0; + sectionNames = nullptr; + sectionOffsets = nullptr; + } + + instances = 0; +} + +ccScript::~ccScript() +{ + Free(); +} + +void ccScript::Write(Stream *out) { + int n; + out->Write(scfilesig,4); + out->WriteInt32(SCOM_VERSION); + out->WriteInt32(globaldatasize); + out->WriteInt32(codesize); + out->WriteInt32(stringssize); + if (globaldatasize > 0) + out->WriteArray(globaldata,globaldatasize,1); + if (codesize > 0) + out->WriteArrayOfInt32(code,codesize); + if (stringssize > 0) + out->WriteArray(strings,stringssize,1); + out->WriteInt32(numfixups); + if (numfixups > 0) { + out->WriteArray(fixuptypes,numfixups,1); + out->WriteArrayOfInt32(fixups,numfixups); + } + out->WriteInt32(numimports); + for (n=0;nWriteInt32(numexports); + for (n=0;nWriteInt32(export_addr[n]); + } + out->WriteInt32(numSections); + for (n = 0; n < numSections; n++) { + fwritestring(sectionNames[n], out); + out->WriteInt32(sectionOffsets[n]); + } + out->WriteInt32(ENDFILESIG); +} + +bool ccScript::Read(Stream *in) +{ + instances = 0; + int n; + char gotsig[5]; + currentline = -1; + // MACPORT FIX: swap 'size' and 'nmemb' + in->Read(gotsig, 4); + gotsig[4] = 0; + + int fileVer = in->ReadInt32(); + + if ((strcmp(gotsig, scfilesig) != 0) || (fileVer > SCOM_VERSION)) { + cc_error("file was not written by ccScript::Write or seek position is incorrect"); + return false; + } + + globaldatasize = in->ReadInt32(); + codesize = in->ReadInt32(); + stringssize = in->ReadInt32(); + + if (globaldatasize > 0) { + globaldata = (char *)malloc(globaldatasize); + // MACPORT FIX: swap + in->Read(globaldata, globaldatasize); + } + else + globaldata = nullptr; + + if (codesize > 0) { + code = (int32_t *)malloc(codesize * sizeof(int32_t)); + // MACPORT FIX: swap + + // 64 bit: Read code into 8 byte array, necessary for being able to perform + // relocations on the references. + in->ReadArrayOfInt32(code, codesize); + } + else + code = nullptr; + + if (stringssize > 0) { + strings = (char *)malloc(stringssize); + // MACPORT FIX: swap + in->Read(strings, stringssize); + } + else + strings = nullptr; + + numfixups = in->ReadInt32(); + if (numfixups > 0) { + fixuptypes = (char *)malloc(numfixups); + fixups = (int32_t *)malloc(numfixups * sizeof(int32_t)); + // MACPORT FIX: swap 'size' and 'nmemb' + in->Read(fixuptypes, numfixups); + in->ReadArrayOfInt32(fixups, numfixups); + } + else { + fixups = nullptr; + fixuptypes = nullptr; + } + + numimports = in->ReadInt32(); + + imports = (char**)malloc(sizeof(char*) * numimports); + for (n = 0; n < numimports; n++) + freadstring(&imports[n], in); + + numexports = in->ReadInt32(); + exports = (char**)malloc(sizeof(char*) * numexports); + export_addr = (int32_t*)malloc(sizeof(int32_t) * numexports); + for (n = 0; n < numexports; n++) { + freadstring(&exports[n], in); + export_addr[n] = in->ReadInt32(); + } + + if (fileVer >= 83) { + // read in the Sections + numSections = in->ReadInt32(); + sectionNames = (char**)malloc(numSections * sizeof(char*)); + sectionOffsets = (int32_t*)malloc(numSections * sizeof(int32_t)); + for (n = 0; n < numSections; n++) { + freadstring(§ionNames[n], in); + sectionOffsets[n] = in->ReadInt32(); + } + } + else + { + numSections = 0; + sectionNames = nullptr; + sectionOffsets = nullptr; + } + + if (in->ReadInt32() != ENDFILESIG) { + cc_error("internal error rebuilding script"); + return false; + } + return true; +} + +void ccScript::Free() +{ + if (globaldata != nullptr) + free(globaldata); + + if (code != nullptr) + free(code); + + if (strings != nullptr) + free(strings); + + if (fixups != nullptr && numfixups > 0) + free(fixups); + + if (fixuptypes != nullptr && numfixups > 0) + free(fixuptypes); + + globaldata = nullptr; + code = nullptr; + strings = nullptr; + fixups = nullptr; + fixuptypes = nullptr; + + int aa; + for (aa = 0; aa < numimports; aa++) { + if (imports[aa] != nullptr) + free(imports[aa]); + } + + for (aa = 0; aa < numexports; aa++) + free(exports[aa]); + + for (aa = 0; aa < numSections; aa++) + free(sectionNames[aa]); + + if (sectionNames != nullptr) + { + free(sectionNames); + free(sectionOffsets); + sectionNames = nullptr; + sectionOffsets = nullptr; + } + + if (imports != nullptr) + { + free(imports); + free(exports); + free(export_addr); + imports = nullptr; + exports = nullptr; + export_addr = nullptr; + } + numimports = 0; + numexports = 0; + numSections = 0; +} + +const char* ccScript::GetSectionName(int32_t offs) { + + int i; + for (i = 0; i < numSections; i++) { + if (sectionOffsets[i] < offs) + continue; + break; + } + + // if no sections in script, return unknown + if (i == 0) + return "(unknown section)"; + + return sectionNames[i - 1]; +} diff --git a/engines/ags/shared/script/cc_script.h b/engines/ags/shared/script/cc_script.h new file mode 100644 index 00000000000..e422893af79 --- /dev/null +++ b/engines/ags/shared/script/cc_script.h @@ -0,0 +1,75 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// 'C'-style script compiler +// +//============================================================================= + +#ifndef __CC_SCRIPT_H +#define __CC_SCRIPT_H + +#include +#include "core/types.h" + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +struct ccScript +{ +public: + char *globaldata; + int32_t globaldatasize; + int32_t *code; // executable byte-code, 32-bit per op or arg + int32_t codesize; // TODO: find out if we can make it size_t + char *strings; + int32_t stringssize; + char *fixuptypes; // global data/string area/ etc + int32_t *fixups; // code array index to fixup (in ints) + int numfixups; + int importsCapacity; + char **imports; + int numimports; + int exportsCapacity; + char **exports; // names of exports + int32_t *export_addr; // high byte is type; low 24-bits are offset + int numexports; + int instances; + // 'sections' allow the interpreter to find out which bit + // of the code came from header files, and which from the main file + char **sectionNames; + int32_t *sectionOffsets; + int numSections; + int capacitySections; + + static ccScript *CreateFromStream(Common::Stream *in); + + ccScript(); + ccScript(const ccScript &src); + virtual ~ccScript(); // there are few derived classes, so dtor should be virtual + + // write the script to disk (after compiling) + void Write(Common::Stream *out); + // read back a script written with Write + bool Read(Common::Stream *in); + const char* GetSectionName(int32_t offset); + +protected: + // free the memory occupied by the script - do NOT attempt to run the + // script after calling this function + void Free(); +}; + +typedef std::shared_ptr PScript; + +#endif // __CC_SCRIPT_H diff --git a/engines/ags/shared/script/script_common.h b/engines/ags/shared/script/script_common.h new file mode 100644 index 00000000000..5dcf0b5d7d2 --- /dev/null +++ b/engines/ags/shared/script/script_common.h @@ -0,0 +1,131 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// 'C'-style script compiler +// +//============================================================================= + +#ifndef __CS_COMMON_H +#define __CS_COMMON_H + +#define SCOM_VERSION 90 +#define SCOM_VERSIONSTR "0.90" + +// virtual CPU registers +#define SREG_SP 1 // stack pointer +#define SREG_MAR 2 // memory address register +#define SREG_AX 3 // general purpose +#define SREG_BX 4 +#define SREG_CX 5 +#define SREG_OP 6 // object pointer for member func calls +#define SREG_DX 7 +#define CC_NUM_REGISTERS 8 + +// virtual CPU commands +#define SCMD_ADD 1 // reg1 += arg2 +#define SCMD_SUB 2 // reg1 -= arg2 +#define SCMD_REGTOREG 3 // reg2 = reg1 +#define SCMD_WRITELIT 4 // m[MAR] = arg2 (copy arg1 bytes) +#define SCMD_RET 5 // return from subroutine +#define SCMD_LITTOREG 6 // set reg1 to literal value arg2 +#define SCMD_MEMREAD 7 // reg1 = m[MAR] +#define SCMD_MEMWRITE 8 // m[MAR] = reg1 +#define SCMD_MULREG 9 // reg1 *= reg2 +#define SCMD_DIVREG 10 // reg1 /= reg2 +#define SCMD_ADDREG 11 // reg1 += reg2 +#define SCMD_SUBREG 12 // reg1 -= reg2 +#define SCMD_BITAND 13 // bitwise reg1 & reg2 +#define SCMD_BITOR 14 // bitwise reg1 | reg2 +#define SCMD_ISEQUAL 15 // reg1 == reg2 reg1=1 if true, =0 if not +#define SCMD_NOTEQUAL 16 // reg1 != reg2 +#define SCMD_GREATER 17 // reg1 > reg2 +#define SCMD_LESSTHAN 18 // reg1 < reg2 +#define SCMD_GTE 19 // reg1 >= reg2 +#define SCMD_LTE 20 // reg1 <= reg2 +#define SCMD_AND 21 // (reg1!=0) && (reg2!=0) -> reg1 +#define SCMD_OR 22 // (reg1!=0) || (reg2!=0) -> reg1 +#define SCMD_CALL 23 // jump to subroutine at reg1 +#define SCMD_MEMREADB 24 // reg1 = m[MAR] (1 byte) +#define SCMD_MEMREADW 25 // reg1 = m[MAR] (2 bytes) +#define SCMD_MEMWRITEB 26 // m[MAR] = reg1 (1 byte) +#define SCMD_MEMWRITEW 27 // m[MAR] = reg1 (2 bytes) +#define SCMD_JZ 28 // jump if ax==0 to arg1 +#define SCMD_PUSHREG 29 // m[sp]=reg1; sp++ +#define SCMD_POPREG 30 // sp--; reg1=m[sp] +#define SCMD_JMP 31 // jump to arg1 +#define SCMD_MUL 32 // reg1 *= arg2 +#define SCMD_CALLEXT 33 // call external (imported) function reg1 +#define SCMD_PUSHREAL 34 // push reg1 onto real stack +#define SCMD_SUBREALSTACK 35 // decrement stack ptr by literal +#define SCMD_LINENUM 36 // debug info - source code line number +#define SCMD_CALLAS 37 // call external script function +#define SCMD_THISBASE 38 // current relative address +#define SCMD_NUMFUNCARGS 39 // number of arguments for ext func call +#define SCMD_MODREG 40 // reg1 %= reg2 +#define SCMD_XORREG 41 // reg1 ^= reg2 +#define SCMD_NOTREG 42 // reg1 = !reg1 +#define SCMD_SHIFTLEFT 43 // reg1 = reg1 << reg2 +#define SCMD_SHIFTRIGHT 44 // reg1 = reg1 >> reg2 +#define SCMD_CALLOBJ 45 // next call is member function of reg1 +#define SCMD_CHECKBOUNDS 46 // check reg1 is between 0 and arg2 +#define SCMD_MEMWRITEPTR 47 // m[MAR] = reg1 (adjust ptr addr) +#define SCMD_MEMREADPTR 48 // reg1 = m[MAR] (adjust ptr addr) +#define SCMD_MEMZEROPTR 49 // m[MAR] = 0 (blank ptr) +#define SCMD_MEMINITPTR 50 // m[MAR] = reg1 (but don't free old one) +#define SCMD_LOADSPOFFS 51 // MAR = SP - arg1 (optimization for local var access) +#define SCMD_CHECKNULL 52 // error if MAR==0 +#define SCMD_FADD 53 // reg1 += arg2 (float,int) +#define SCMD_FSUB 54 // reg1 -= arg2 (float,int) +#define SCMD_FMULREG 55 // reg1 *= reg2 (float) +#define SCMD_FDIVREG 56 // reg1 /= reg2 (float) +#define SCMD_FADDREG 57 // reg1 += reg2 (float) +#define SCMD_FSUBREG 58 // reg1 -= reg2 (float) +#define SCMD_FGREATER 59 // reg1 > reg2 (float) +#define SCMD_FLESSTHAN 60 // reg1 < reg2 (float) +#define SCMD_FGTE 61 // reg1 >= reg2 (float) +#define SCMD_FLTE 62 // reg1 <= reg2 (float) +#define SCMD_ZEROMEMORY 63 // m[MAR]..m[MAR+(arg1-1)] = 0 +#define SCMD_CREATESTRING 64 // reg1 = new String(reg1) +#define SCMD_STRINGSEQUAL 65 // (char*)reg1 == (char*)reg2 reg1=1 if true, =0 if not +#define SCMD_STRINGSNOTEQ 66 // (char*)reg1 != (char*)reg2 +#define SCMD_CHECKNULLREG 67 // error if reg1 == NULL +#define SCMD_LOOPCHECKOFF 68 // no loop checking for this function +#define SCMD_MEMZEROPTRND 69 // m[MAR] = 0 (blank ptr, no dispose if = ax) +#define SCMD_JNZ 70 // jump to arg1 if ax!=0 +#define SCMD_DYNAMICBOUNDS 71 // check reg1 is between 0 and m[MAR-4] +#define SCMD_NEWARRAY 72 // reg1 = new array of reg1 elements, each of size arg2 (arg3=managed type?) +#define SCMD_NEWUSEROBJECT 73 // reg1 = new user object of arg1 size + +#define CC_NUM_SCCMDS 74 +#define MAX_SCMD_ARGS 3 // maximal possible number of arguments + +#define EXPORT_FUNCTION 1 +#define EXPORT_DATA 2 + +#define FIXUP_GLOBALDATA 1 // code[fixup] += &globaldata[0] +#define FIXUP_FUNCTION 2 // code[fixup] += &code[0] +#define FIXUP_STRING 3 // code[fixup] += &strings[0] +#define FIXUP_IMPORT 4 // code[fixup] = &imported_thing[code[fixup]] +#define FIXUP_DATADATA 5 // globaldata[fixup] += &globaldata[0] +#define FIXUP_STACK 6 // code[fixup] += &stack[0] + + + + +extern int currentline; +// Script file signature +extern const char scfilesig[5]; +#define ENDFILESIG 0xbeefcafe + +#endif // __CS_COMMON_H diff --git a/engines/ags/shared/util/alignedstream.cpp b/engines/ags/shared/util/alignedstream.cpp new file mode 100644 index 00000000000..b60a00e0baa --- /dev/null +++ b/engines/ags/shared/util/alignedstream.cpp @@ -0,0 +1,389 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "debug/assert.h" +#include "util/alignedstream.h" +#include "util/math.h" + +namespace AGS +{ +namespace Common +{ + +AlignedStream::AlignedStream(Stream *stream, AlignedStreamMode mode, ObjectOwnershipPolicy stream_ownership_policy, + size_t base_alignment) + : ProxyStream(stream, stream_ownership_policy) + , _mode(mode) + , _baseAlignment(base_alignment) + , _maxAlignment(0) + , _block(0) +{ +} + +AlignedStream::~AlignedStream() +{ + AlignedStream::Close(); +} + +void AlignedStream::Reset() +{ + if (!_stream) + { + return; + } + + FinalizeBlock(); +} + +void AlignedStream::Close() +{ + if (!_stream) + { + return; + } + + FinalizeBlock(); + ProxyStream::Close(); +} + +bool AlignedStream::CanRead() const +{ + return _stream ? (_mode == kAligned_Read && _stream->CanRead()) : false; +} + +bool AlignedStream::CanWrite() const +{ + return _stream ? (_mode == kAligned_Write && _stream->CanWrite()) : false; +} + +bool AlignedStream::CanSeek() const +{ + // Aligned stream does not support seeking, hence that will break padding count + return false; +} + +size_t AlignedStream::Read(void *buffer, size_t size) +{ + if (_stream) + { + ReadPadding(sizeof(int8_t)); + size = _stream->Read(buffer, size); + _block += size; + return size; + } + return 0; +} + +int32_t AlignedStream::ReadByte() +{ + uint8_t b = 0; + if (_stream) + { + ReadPadding(sizeof(uint8_t)); + b = _stream->ReadByte(); + _block += sizeof(uint8_t); + } + return b; +} + +int16_t AlignedStream::ReadInt16() +{ + int16_t val = 0; + if (_stream) + { + ReadPadding(sizeof(int16_t)); + val = _stream->ReadInt16(); + _block += sizeof(int16_t); + } + return val; +} + +int32_t AlignedStream::ReadInt32() +{ + int32_t val = 0; + if (_stream) + { + ReadPadding(sizeof(int32_t)); + val = _stream->ReadInt32(); + _block += sizeof(int32_t); + } + return val; +} + +int64_t AlignedStream::ReadInt64() +{ + int64_t val = 0; + if (_stream) + { + ReadPadding(sizeof(int64_t)); + val = _stream->ReadInt64(); + _block += sizeof(int64_t); + } + return val; +} + +size_t AlignedStream::ReadArray(void *buffer, size_t elem_size, size_t count) +{ + if (_stream) + { + ReadPadding(elem_size); + count = _stream->ReadArray(buffer, elem_size, count); + _block += count * elem_size; + return count; + } + return 0; +} + +size_t AlignedStream::ReadArrayOfInt16(int16_t *buffer, size_t count) +{ + if (_stream) + { + ReadPadding(sizeof(int16_t)); + count = _stream->ReadArrayOfInt16(buffer, count); + _block += count * sizeof(int16_t); + return count; + } + return 0; +} + +size_t AlignedStream::ReadArrayOfInt32(int32_t *buffer, size_t count) +{ + if (_stream) + { + ReadPadding(sizeof(int32_t)); + count = _stream->ReadArrayOfInt32(buffer, count); + _block += count * sizeof(int32_t); + return count; + } + return 0; +} + +size_t AlignedStream::ReadArrayOfInt64(int64_t *buffer, size_t count) +{ + if (_stream) + { + ReadPadding(sizeof(int64_t)); + count = _stream->ReadArrayOfInt64(buffer, count); + _block += count * sizeof(int64_t); + return count; + } + return 0; +} + +size_t AlignedStream::Write(const void *buffer, size_t size) +{ + if (_stream) + { + WritePadding(sizeof(int8_t)); + size = _stream->Write(buffer, size); + _block += size; + return size; + } + return 0; +} + +int32_t AlignedStream::WriteByte(uint8_t b) +{ + if (_stream) + { + WritePadding(sizeof(uint8_t)); + b = _stream->WriteByte(b); + _block += sizeof(uint8_t); + return b; + } + return 0; +} + +size_t AlignedStream::WriteInt16(int16_t val) +{ + if (_stream) + { + WritePadding(sizeof(int16_t)); + size_t size = _stream->WriteInt16(val); + _block += sizeof(int16_t); + return size; + } + return 0; +} + +size_t AlignedStream::WriteInt32(int32_t val) +{ + if (_stream) + { + WritePadding(sizeof(int32_t)); + size_t size = _stream->WriteInt32(val); + _block += sizeof(int32_t); + return size; + } + return 0; +} + +size_t AlignedStream::WriteInt64(int64_t val) +{ + if (_stream) + { + WritePadding(sizeof(int64_t)); + size_t size = _stream->WriteInt64(val); + _block += sizeof(int64_t); + return size; + } + return 0; +} + +size_t AlignedStream::WriteArray(const void *buffer, size_t elem_size, size_t count) +{ + if (_stream) + { + WritePadding(elem_size); + count = _stream->WriteArray(buffer, elem_size, count); + _block += count * elem_size; + return count; + } + return 0; +} + +size_t AlignedStream::WriteArrayOfInt16(const int16_t *buffer, size_t count) +{ + if (_stream) + { + WritePadding(sizeof(int16_t)); + count = _stream->WriteArrayOfInt16(buffer, count); + _block += count * sizeof(int16_t); + return count; + } + return 0; +} + +size_t AlignedStream::WriteArrayOfInt32(const int32_t *buffer, size_t count) +{ + if (_stream) + { + WritePadding(sizeof(int32_t)); + count = _stream->WriteArrayOfInt32(buffer, count); + _block += count * sizeof(int32_t); + return count; + } + return 0; +} + +size_t AlignedStream::WriteArrayOfInt64(const int64_t *buffer, size_t count) +{ + if (_stream) + { + WritePadding(sizeof(int64_t)); + count = _stream->WriteArrayOfInt64(buffer, count); + _block += count * sizeof(int64_t); + return count; + } + return 0; +} + +bool AlignedStream::Seek(soff_t offset, StreamSeek origin) +{ + // TODO: split out Seekable Stream interface + return false; +} + +void AlignedStream::ReadPadding(size_t next_type) +{ + if (!IsValid()) + { + return; + } + + if (next_type == 0) + { + return; + } + + // The next is going to be evenly aligned data type, + // therefore a padding check must be made + if (next_type % _baseAlignment == 0) + { + int pad = _block % next_type; + // Read padding only if have to + if (pad) + { + // We do not know and should not care if the underlying stream + // supports seek, so use read to skip the padding instead. + for (size_t i = next_type - pad; i > 0; --i) + _stream->ReadByte(); + _block += next_type - pad; + } + + _maxAlignment = Math::Max(_maxAlignment, next_type); + // Data is evenly aligned now + if (_block % LargestPossibleType == 0) + { + _block = 0; + } + } +} + +void AlignedStream::WritePadding(size_t next_type) +{ + if (!IsValid()) + { + return; + } + + if (next_type == 0) + { + return; + } + + // The next is going to be evenly aligned data type, + // therefore a padding check must be made + if (next_type % _baseAlignment == 0) + { + int pad = _block % next_type; + // Write padding only if have to + if (pad) + { + _stream->WriteByteCount(0, next_type - pad); + _block += next_type - pad; + } + + _maxAlignment = Math::Max(_maxAlignment, next_type); + // Data is evenly aligned now + if (_block % LargestPossibleType == 0) + { + _block = 0; + } + } +} + +void AlignedStream::FinalizeBlock() +{ + if (!IsValid()) + { + return; + } + + // Force the stream to read or write remaining padding to match the alignment + if (_mode == kAligned_Read) + { + ReadPadding(_maxAlignment); + } + else if (_mode == kAligned_Write) + { + WritePadding(_maxAlignment); + } + + _maxAlignment = 0; + _block = 0; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/alignedstream.h b/engines/ags/shared/util/alignedstream.h new file mode 100644 index 00000000000..c0aed86d7cf --- /dev/null +++ b/engines/ags/shared/util/alignedstream.h @@ -0,0 +1,106 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Class AlignedStream +// A simple wrapper around stream that controls data padding. +// +// Originally, a number of objects in AGS were read and written directly +// as a data struct in a whole. In order to support backwards compatibility +// with games made by older versions of AGS, some of the game objects must +// be read having automatic data alignment in mind. +//----------------------------------------------------------------------------- +// +// AlignedStream uses the underlying stream, it overrides the reading and +// writing, and inserts extra data padding when needed. +// +// Aligned stream works either in read or write mode, it cannot be opened in +// combined mode. +// +// AlignedStream does not support seek, hence moving stream pointer to random +// position will break padding count logic. +// +//============================================================================= +#ifndef __AGS_CN_UTIL__ALIGNEDSTREAM_H +#define __AGS_CN_UTIL__ALIGNEDSTREAM_H + +#include "util/proxystream.h" + +namespace AGS +{ +namespace Common +{ + +enum AlignedStreamMode +{ + kAligned_Read, + kAligned_Write +}; + +class AlignedStream : public ProxyStream +{ +public: + AlignedStream(Stream *stream, AlignedStreamMode mode, + ObjectOwnershipPolicy stream_ownership_policy = kReleaseAfterUse, + size_t base_alignment = sizeof(int16_t)); + ~AlignedStream() override; + + // Read/Write cumulated padding and reset block counter + void Reset(); + + void Close() override; + + bool CanRead() const override; + bool CanWrite() const override; + bool CanSeek() const override; + + size_t Read(void *buffer, size_t size) override; + int32_t ReadByte() override; + int16_t ReadInt16() override; + int32_t ReadInt32() override; + int64_t ReadInt64() override; + size_t ReadArray(void *buffer, size_t elem_size, size_t count) override; + size_t ReadArrayOfInt16(int16_t *buffer, size_t count) override; + size_t ReadArrayOfInt32(int32_t *buffer, size_t count) override; + size_t ReadArrayOfInt64(int64_t *buffer, size_t count) override; + + size_t Write(const void *buffer, size_t size) override; + int32_t WriteByte(uint8_t b) override; + size_t WriteInt16(int16_t val) override; + size_t WriteInt32(int32_t val) override; + size_t WriteInt64(int64_t val) override; + size_t WriteArray(const void *buffer, size_t elem_size, size_t count) override; + size_t WriteArrayOfInt16(const int16_t *buffer, size_t count) override; + size_t WriteArrayOfInt32(const int32_t *buffer, size_t count) override; + size_t WriteArrayOfInt64(const int64_t *buffer, size_t count) override; + + bool Seek(soff_t offset, StreamSeek origin) override; + +protected: + void ReadPadding(size_t next_type); + void WritePadding(size_t next_type); + void FinalizeBlock(); + +private: + static const size_t LargestPossibleType = sizeof(int64_t); + + AlignedStreamMode _mode; + size_t _baseAlignment; + size_t _maxAlignment; + int64_t _block; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__ALIGNEDSTREAM_H diff --git a/engines/ags/shared/util/bbop.h b/engines/ags/shared/util/bbop.h new file mode 100644 index 00000000000..398ca7dad45 --- /dev/null +++ b/engines/ags/shared/util/bbop.h @@ -0,0 +1,158 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Various utility bit and byte operations +// +//============================================================================= +#ifndef __AGS_CN_UTIL__BBOP_H +#define __AGS_CN_UTIL__BBOP_H + +#include "core/platform.h" +#include "core/types.h" + +#if AGS_PLATFORM_ENDIAN_BIG || defined (TEST_BIGENDIAN) +#define BITBYTE_BIG_ENDIAN +#endif + +namespace AGS +{ +namespace Common +{ + +enum DataEndianess +{ + kBigEndian, + kLittleEndian, +#if defined (BITBYTE_BIG_ENDIAN) + kDefaultSystemEndianess = kBigEndian +#else + kDefaultSystemEndianess = kLittleEndian +#endif +}; + +namespace BitByteOperations +{ + inline int16_t SwapBytesInt16(const int16_t val) + { + return ((val >> 8) & 0xFF) | ((val << 8) & 0xFF00); + } + + inline int32_t SwapBytesInt32(const int32_t val) + { + return ((val >> 24) & 0xFF) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | ((val << 24) & 0xFF000000); + } + + inline int64_t SwapBytesInt64(const int64_t val) + { + return ((val >> 56) & 0xFF) | ((val >> 40) & 0xFF00) | ((val >> 24) & 0xFF0000) | + ((val >> 8) & 0xFF000000) | ((val << 8) & 0xFF00000000LL) | + ((val << 24) & 0xFF0000000000LL) | ((val << 40) & 0xFF000000000000LL) | ((val << 56) & 0xFF00000000000000LL); + } + + inline float SwapBytesFloat(const float val) + { + // (c) SDL2 + union + { + float f; + uint32_t ui32; + } swapper; + swapper.f = val; + swapper.ui32 = SwapBytesInt32(swapper.ui32); + return swapper.f; + } + + inline int16_t Int16FromLE(const int16_t val) + { +#if defined (BITBYTE_BIG_ENDIAN) + return SwapBytesInt16(val); +#else + return val; +#endif + } + + inline int32_t Int32FromLE(const int32_t val) + { +#if defined (BITBYTE_BIG_ENDIAN) + return SwapBytesInt32(val); +#else + return val; +#endif + } + + inline int64_t Int64FromLE(const int64_t val) + { +#if defined (BITBYTE_BIG_ENDIAN) + return SwapBytesInt64(val); +#else + return val; +#endif + } + + inline float FloatFromLE(const float val) + { +#if defined (BITBYTE_BIG_ENDIAN) + return SwapBytesFloat(val); +#else + return val; +#endif + } + + inline int16_t Int16FromBE(const int16_t val) + { +#if defined (BITBYTE_BIG_ENDIAN) + return val; +#else + return SwapBytesInt16(val); +#endif + } + + inline int32_t Int32FromBE(const int32_t val) + { +#if defined (BITBYTE_BIG_ENDIAN) + return val; +#else + return SwapBytesInt32(val); +#endif + } + + inline int64_t Int64FromBE(const int64_t val) + { +#if defined (BITBYTE_BIG_ENDIAN) + return val; +#else + return SwapBytesInt64(val); +#endif + } + + inline float FloatFromBE(const float val) + { +#if defined (BITBYTE_BIG_ENDIAN) + return val; +#else + return SwapBytesFloat(val); +#endif + } + +} // namespace BitByteOperations + + +// Aliases for easier calling +namespace BBOp = BitByteOperations; + + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__BBOP_H diff --git a/engines/ags/shared/util/bufferedstream.cpp b/engines/ags/shared/util/bufferedstream.cpp new file mode 100644 index 00000000000..a67d95415f1 --- /dev/null +++ b/engines/ags/shared/util/bufferedstream.cpp @@ -0,0 +1,134 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include +#include +#include +#include "util/bufferedstream.h" +#include "util/stdio_compat.h" +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +BufferedStream::BufferedStream(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode, DataEndianess stream_endianess) + : FileStream(file_name, open_mode, work_mode, stream_endianess), _buffer(BufferStreamSize), _bufferPosition(0), _position(0) +{ + if (FileStream::Seek(0, kSeekEnd) == false) + throw std::runtime_error("Error determining stream end."); + + _end = FileStream::GetPosition(); + if (_end == -1) + throw std::runtime_error("Error determining stream end."); + + if (FileStream::Seek(0, kSeekBegin) == false) + throw std::runtime_error("Error determining stream end."); + + _buffer.resize(0); + +} + +void BufferedStream::FillBufferFromPosition(soff_t position) +{ + FileStream::Seek(position, kSeekBegin); + + _buffer.resize(BufferStreamSize); + auto sz = FileStream::Read(_buffer.data(), BufferStreamSize); + _buffer.resize(sz); + + _bufferPosition = position; +} + +bool BufferedStream::EOS() const +{ + return _position == _end; +} + +soff_t BufferedStream::GetPosition() const +{ + return _position; +} + +size_t BufferedStream::Read(void *toBuffer, size_t toSize) +{ + auto to = static_cast(toBuffer); + + while(toSize > 0) + { + if (_position < _bufferPosition || _position >= _bufferPosition + _buffer.size()) + { + FillBufferFromPosition(_position); + } + if (_buffer.size() <= 0) { break; } // reached EOS + assert(_position >= _bufferPosition && _position < _bufferPosition + _buffer.size()); // sanity check only, should be checked by above. + + soff_t bufferOffset = _position - _bufferPosition; + assert(bufferOffset >= 0); + size_t bytesLeft = _buffer.size() - (size_t)bufferOffset; + size_t chunkSize = std::min(bytesLeft, toSize); + + std::memcpy(to, _buffer.data()+bufferOffset, chunkSize); + + to += chunkSize; + _position += chunkSize; + toSize -= chunkSize; + } + + return to - (char*)toBuffer; +} + +int32_t BufferedStream::ReadByte() +{ + uint8_t ch; + auto bytesRead = Read(&ch, 1); + if (bytesRead != 1) { return EOF; } + return ch; +} + +size_t BufferedStream::Write(const void *buffer, size_t size) +{ + FileStream::Seek(_position, kSeekBegin); + auto sz = FileStream::Write(buffer, size); + if (_position == _end) + _end += sz; + _position += sz; + return sz; +} + +int32_t BufferedStream::WriteByte(uint8_t val) +{ + auto sz = Write(&val, 1); + if (sz != 1) { return -1; } + return sz; +} + +bool BufferedStream::Seek(soff_t offset, StreamSeek origin) +{ + soff_t want_pos = -1; + switch(origin) + { + case StreamSeek::kSeekCurrent: want_pos = _position + offset; break; + case StreamSeek::kSeekBegin: want_pos = 0 + offset; break; + case StreamSeek::kSeekEnd: want_pos = _end + offset; break; + break; + } + + // clamp + _position = std::min(std::max(want_pos, (soff_t)0), _end); + return _position == want_pos; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/bufferedstream.h b/engines/ags/shared/util/bufferedstream.h new file mode 100644 index 00000000000..10e36dc9a79 --- /dev/null +++ b/engines/ags/shared/util/bufferedstream.h @@ -0,0 +1,69 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_UTIL__BUFFEREDSTREAM_H +#define __AGS_CN_UTIL__BUFFEREDSTREAM_H + +#include +#include "util/filestream.h" +#include "util/file.h" // TODO: extract filestream mode constants + +namespace AGS +{ +namespace Common +{ + +// Needs tuning depending on the platform. +const auto BufferStreamSize = 8*1024; + +class BufferedStream : public FileStream +{ +public: + // Represents an open _buffered_ file object + // The constructor may raise std::runtime_error if + // - there is an issue opening the file (does not exist, locked, permissions, etc) + // - the open mode could not be determined + // - could not determine the length of the stream + // It is recommended to use File::OpenFile to safely construct this object. + BufferedStream(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode, DataEndianess stream_endianess = kLittleEndian); + + bool EOS() const override; ///< Is end of stream + soff_t GetPosition() const override; ///< Current position (if known) + + size_t Read(void *buffer, size_t size) override; + int32_t ReadByte() override; + size_t Write(const void *buffer, size_t size) override; + int32_t WriteByte(uint8_t b) override; + + bool Seek(soff_t offset, StreamSeek origin) override; + + +private: + + soff_t _bufferPosition; + std::vector _buffer; + + soff_t _position; + soff_t _end; + + void FillBufferFromPosition(soff_t position); +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__BUFFEREDSTREAM_H diff --git a/engines/ags/shared/util/compress.cpp b/engines/ags/shared/util/compress.cpp new file mode 100644 index 00000000000..8561fb68759 --- /dev/null +++ b/engines/ags/shared/util/compress.cpp @@ -0,0 +1,434 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifdef _MANAGED +// ensure this doesn't get compiled to .NET IL +#pragma unmanaged +#endif + +#include +#include +#include "ac/common.h" // quit, update_polled_stuff +#include "gfx/bitmap.h" +#include "util/compress.h" +#include "util/lzw.h" +#include "util/misc.h" +#include "util/stream.h" +#if AGS_PLATFORM_ENDIAN_BIG +#include "util/bbop.h" +#endif + +using namespace AGS::Common; + +void cpackbitl(const uint8_t *line, int size, Stream *out) +{ + int cnt = 0; // bytes encoded + + while (cnt < size) { + int i = cnt; + int j = i + 1; + int jmax = i + 126; + if (jmax >= size) + jmax = size - 1; + + if (i == size - 1) { //................last byte alone + out->WriteInt8(0); + out->WriteInt8(line[i]); + cnt++; + + } else if (line[i] == line[j]) { //....run + while ((j < jmax) && (line[j] == line[j + 1])) + j++; + + out->WriteInt8(i - j); + out->WriteInt8(line[i]); + cnt += j - i + 1; + + } else { //.............................sequence + while ((j < jmax) && (line[j] != line[j + 1])) + j++; + + out->WriteInt8(j - i); + out->WriteArray(line + i, j - i + 1, 1); + cnt += j - i + 1; + + } + } // end while +} + +void cpackbitl16(const uint16_t *line, int size, Stream *out) +{ + int cnt = 0; // bytes encoded + + while (cnt < size) { + int i = cnt; + int j = i + 1; + int jmax = i + 126; + if (jmax >= size) + jmax = size - 1; + + if (i == size - 1) { //................last byte alone + out->WriteInt8(0); + out->WriteInt16(line[i]); + cnt++; + + } else if (line[i] == line[j]) { //....run + while ((j < jmax) && (line[j] == line[j + 1])) + j++; + + out->WriteInt8(i - j); + out->WriteInt16(line[i]); + cnt += j - i + 1; + + } else { //.............................sequence + while ((j < jmax) && (line[j] != line[j + 1])) + j++; + + out->WriteInt8(j - i); + out->WriteArray(line + i, j - i + 1, 2); + cnt += j - i + 1; + + } + } // end while +} + +void cpackbitl32(const uint32_t *line, int size, Stream *out) +{ + int cnt = 0; // bytes encoded + + while (cnt < size) { + int i = cnt; + int j = i + 1; + int jmax = i + 126; + if (jmax >= size) + jmax = size - 1; + + if (i == size - 1) { //................last byte alone + out->WriteInt8(0); + out->WriteInt32(line[i]); + cnt++; + + } else if (line[i] == line[j]) { //....run + while ((j < jmax) && (line[j] == line[j + 1])) + j++; + + out->WriteInt8(i - j); + out->WriteInt32(line[i]); + cnt += j - i + 1; + + } else { //.............................sequence + while ((j < jmax) && (line[j] != line[j + 1])) + j++; + + out->WriteInt8(j - i); + out->WriteArray(line + i, j - i + 1, 4); + cnt += j - i + 1; + + } + } // end while +} + + +void csavecompressed(Stream *out, const unsigned char * tobesaved, const color pala[256]) +{ + int widt, hit; + widt = *tobesaved++; + widt += (*tobesaved++) * 256; + hit = *tobesaved++; + hit += (*tobesaved++) * 256; + // Those were originally written as shorts, although they are ints + out->WriteInt16(widt); + out->WriteInt16(hit); + + unsigned char *ress = (unsigned char *)malloc(widt + 1); + int ww; + + for (ww = 0; ww < hit; ww++) { + for (int ss = 0; ss < widt; ss++) + (*ress++) = (*tobesaved++); + + ress -= widt; + cpackbitl(ress, widt, out); + } + + for (ww = 0; ww < 256; ww++) { + out->WriteInt8(pala[ww].r); + out->WriteInt8(pala[ww].g); + out->WriteInt8(pala[ww].b); + } + free(ress); +} + +int cunpackbitl(uint8_t *line, int size, Stream *in) +{ + int n = 0; // number of bytes decoded + + while (n < size) { + int ix = in->ReadByte(); // get index byte + if (in->HasErrors()) + break; + + char cx = ix; + if (cx == -128) + cx = 0; + + if (cx < 0) { //.............run + int i = 1 - cx; + char ch = in->ReadInt8(); + while (i--) { + // test for buffer overflow + if (n >= size) + return -1; + + line[n++] = ch; + } + } else { //.....................seq + int i = cx + 1; + while (i--) { + // test for buffer overflow + if (n >= size) + return -1; + + line[n++] = in->ReadByte(); + } + } + } + + return in->HasErrors() ? -1 : 0; +} + +int cunpackbitl16(uint16_t *line, int size, Stream *in) +{ + int n = 0; // number of bytes decoded + + while (n < size) { + int ix = in->ReadByte(); // get index byte + if (in->HasErrors()) + break; + + char cx = ix; + if (cx == -128) + cx = 0; + + if (cx < 0) { //.............run + int i = 1 - cx; + unsigned short ch = in->ReadInt16(); + while (i--) { + // test for buffer overflow + if (n >= size) + return -1; + + line[n++] = ch; + } + } else { //.....................seq + int i = cx + 1; + while (i--) { + // test for buffer overflow + if (n >= size) + return -1; + + line[n++] = in->ReadInt16(); + } + } + } + + return in->HasErrors() ? -1 : 0; +} + +int cunpackbitl32(uint32_t *line, int size, Stream *in) +{ + int n = 0; // number of bytes decoded + + while (n < size) { + int ix = in->ReadByte(); // get index byte + if (in->HasErrors()) + break; + + char cx = ix; + if (cx == -128) + cx = 0; + + if (cx < 0) { //.............run + int i = 1 - cx; + unsigned int ch = in->ReadInt32(); + while (i--) { + // test for buffer overflow + if (n >= size) + return -1; + + line[n++] = ch; + } + } else { //.....................seq + int i = cx + 1; + while (i--) { + // test for buffer overflow + if (n >= size) + return -1; + + line[n++] = (unsigned int)in->ReadInt32(); + } + } + } + + return in->HasErrors() ? -1 : 0; +} + +//============================================================================= + +const char *lztempfnm = "~aclzw.tmp"; + +void save_lzw(Stream *out, const Bitmap *bmpp, const color *pall) +{ + // First write original bitmap into temporary file + Stream *lz_temp_s = ci_fopen(lztempfnm, kFile_CreateAlways, kFile_Write); + lz_temp_s->WriteInt32(bmpp->GetWidth() * bmpp->GetBPP()); + lz_temp_s->WriteInt32(bmpp->GetHeight()); + lz_temp_s->WriteArray(bmpp->GetData(), bmpp->GetLineLength(), bmpp->GetHeight()); + delete lz_temp_s; + + // Now open same file for reading, and begin writing compressed data into required output stream + lz_temp_s = ci_fopen(lztempfnm); + soff_t temp_sz = lz_temp_s->GetLength(); + out->WriteArray(&pall[0], sizeof(color), 256); + out->WriteInt32(temp_sz); + soff_t gobacto = out->GetPosition(); + + // reserve space for compressed size + out->WriteInt32(temp_sz); + lzwcompress(lz_temp_s, out); + soff_t toret = out->GetPosition(); + out->Seek(gobacto, kSeekBegin); + soff_t compressed_sz = (toret - gobacto) - 4; + out->WriteInt32(compressed_sz); // write compressed size + + // Delete temp file + delete lz_temp_s; + ::remove(lztempfnm); + + // Seek back to the end of the output stream + out->Seek(toret, kSeekBegin); +} + +void load_lzw(Stream *in, Bitmap **dst_bmp, int dst_bpp, color *pall) { + soff_t uncompsiz; + int *loptr; + unsigned char *membuffer; + int arin; + + in->Read(&pall[0], sizeof(color)*256); + maxsize = in->ReadInt32(); + uncompsiz = in->ReadInt32(); + + uncompsiz += in->GetPosition(); + outbytes = 0; putbytes = 0; + + update_polled_stuff_if_runtime(); + membuffer = lzwexpand_to_mem(in); + update_polled_stuff_if_runtime(); + + loptr = (int *)&membuffer[0]; + membuffer += 8; +#if AGS_PLATFORM_ENDIAN_BIG + loptr[0] = BBOp::SwapBytesInt32(loptr[0]); + loptr[1] = BBOp::SwapBytesInt32(loptr[1]); + int bitmapNumPixels = loptr[0]*loptr[1]/ dst_bpp; + switch (dst_bpp) // bytes per pixel! + { + case 1: + { + // all done + break; + } + case 2: + { + short *sp = (short *)membuffer; + for (int i = 0; i < bitmapNumPixels; ++i) + { + sp[i] = BBOp::SwapBytesInt16(sp[i]); + } + // all done + break; + } + case 4: + { + int *ip = (int *)membuffer; + for (int i = 0; i < bitmapNumPixels; ++i) + { + ip[i] = BBOp::SwapBytesInt32(ip[i]); + } + // all done + break; + } + } +#endif // AGS_PLATFORM_ENDIAN_BIG + + update_polled_stuff_if_runtime(); + + Bitmap *bmm = BitmapHelper::CreateBitmap((loptr[0] / dst_bpp), loptr[1], dst_bpp * 8); + if (bmm == nullptr) + quit("!load_room: not enough memory to load room background"); + + update_polled_stuff_if_runtime(); + + bmm->Acquire (); + + for (arin = 0; arin < loptr[1]; arin++) + memcpy(&bmm->GetScanLineForWriting(arin)[0], &membuffer[arin * loptr[0]], loptr[0]); + + bmm->Release (); + + update_polled_stuff_if_runtime(); + + free(membuffer-8); + + if (in->GetPosition() != uncompsiz) + in->Seek(uncompsiz, kSeekBegin); + + update_polled_stuff_if_runtime(); + + *dst_bmp = bmm; +} + +void savecompressed_allegro(Stream *out, const Bitmap *bmpp, const color *pall) { + unsigned char *wgtbl = (unsigned char *)malloc(bmpp->GetWidth() * bmpp->GetHeight() + 4); + short *sss = (short *)wgtbl; + + sss[0] = bmpp->GetWidth(); + sss[1] = bmpp->GetHeight(); + + memcpy(&wgtbl[4], bmpp->GetData(), bmpp->GetWidth() * bmpp->GetHeight()); + + csavecompressed(out, wgtbl, pall); + free(wgtbl); +} + +void loadcompressed_allegro(Stream *in, Bitmap **bimpp, color *pall) { + short widd,hitt; + int ii; + + widd = in->ReadInt16(); + hitt = in->ReadInt16(); + Bitmap *bim = BitmapHelper::CreateBitmap(widd, hitt, 8); + if (bim == nullptr) + quit("!load_room: not enough memory to decompress masks"); + + for (ii = 0; ii < hitt; ii++) { + cunpackbitl(&bim->GetScanLineForWriting(ii)[0], widd, in); + if (ii % 20 == 0) + update_polled_stuff_if_runtime(); + } + + in->Seek(768); // skip palette + *bimpp = bim; +} diff --git a/engines/ags/shared/util/compress.h b/engines/ags/shared/util/compress.h new file mode 100644 index 00000000000..356f8782e00 --- /dev/null +++ b/engines/ags/shared/util/compress.h @@ -0,0 +1,40 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AC_COMPRESS_H +#define __AC_COMPRESS_H + +#include "util/wgt2allg.h" // color (allegro RGB) + +namespace AGS { namespace Common { class Stream; class Bitmap; } } +using namespace AGS; // FIXME later + +void csavecompressed(Common::Stream *out, const unsigned char * tobesaved, const color pala[256]); +// RLE compression +void cpackbitl(const uint8_t *line, int size, Common::Stream *out); +void cpackbitl16(const uint16_t *line, int size, Common::Stream *out); +void cpackbitl32(const uint32_t *line, int size, Common::Stream *out); +// RLE decompression +int cunpackbitl(uint8_t *line, int size, Common::Stream *in); +int cunpackbitl16(uint16_t *line, int size, Common::Stream *in); +int cunpackbitl32(uint32_t *line, int size, Common::Stream *in); + +//============================================================================= + +void save_lzw(Common::Stream *out, const Common::Bitmap *bmpp, const color *pall); +void load_lzw(Common::Stream *in, Common::Bitmap **bmm, int dst_bpp, color *pall); +void savecompressed_allegro(Common::Stream *out, const Common::Bitmap *bmpp, const color *pall); +void loadcompressed_allegro(Common::Stream *in, Common::Bitmap **bimpp, color *pall); + +#endif // __AC_COMPRESS_H diff --git a/engines/ags/shared/util/datastream.cpp b/engines/ags/shared/util/datastream.cpp new file mode 100644 index 00000000000..4348da9e837 --- /dev/null +++ b/engines/ags/shared/util/datastream.cpp @@ -0,0 +1,177 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/datastream.h" + +namespace AGS +{ +namespace Common +{ + +DataStream::DataStream(DataEndianess stream_endianess) + : _streamEndianess(stream_endianess) +{ +} + +DataStream::~DataStream() = default; + +int16_t DataStream::ReadInt16() +{ + int16_t val = 0; + Read(&val, sizeof(int16_t)); + ConvertInt16(val); + return val; +} + +int32_t DataStream::ReadInt32() +{ + int32_t val = 0; + Read(&val, sizeof(int32_t)); + ConvertInt32(val); + return val; +} + +int64_t DataStream::ReadInt64() +{ + int64_t val = 0; + Read(&val, sizeof(int64_t)); + ConvertInt64(val); + return val; +} + +size_t DataStream::WriteInt16(int16_t val) +{ + ConvertInt16(val); + return Write(&val, sizeof(int16_t)); +} + +size_t DataStream::WriteInt32(int32_t val) +{ + ConvertInt32(val); + return Write(&val, sizeof(int32_t)); +} + +size_t DataStream::WriteInt64(int64_t val) +{ + ConvertInt64(val); + return Write(&val, sizeof(int64_t)); +} + +size_t DataStream::ReadAndConvertArrayOfInt16(int16_t *buffer, size_t count) +{ + if (!CanRead() || !buffer) + { + return 0; + } + + count = ReadArray(buffer, sizeof(int16_t), count); + for (size_t i = 0; i < count; ++i, ++buffer) + { + *buffer = BBOp::SwapBytesInt16(*buffer); + } + return count; +} + +size_t DataStream::ReadAndConvertArrayOfInt32(int32_t *buffer, size_t count) +{ + if (!CanRead() || !buffer) + { + return 0; + } + + count = ReadArray(buffer, sizeof(int32_t), count); + for (size_t i = 0; i < count; ++i, ++buffer) + { + *buffer = BBOp::SwapBytesInt32(*buffer); + } + return count; +} + +size_t DataStream::ReadAndConvertArrayOfInt64(int64_t *buffer, size_t count) +{ + if (!CanRead() || !buffer) + { + return 0; + } + + count = ReadArray(buffer, sizeof(int64_t), count); + for (size_t i = 0; i < count; ++i, ++buffer) + { + *buffer = BBOp::SwapBytesInt64(*buffer); + } + return count; +} + +size_t DataStream::WriteAndConvertArrayOfInt16(const int16_t *buffer, size_t count) +{ + if (!CanWrite() || !buffer) + { + return 0; + } + + size_t elem; + for (elem = 0; elem < count && !EOS(); ++elem, ++buffer) + { + int16_t val = *buffer; + ConvertInt16(val); + if (Write(&val, sizeof(int16_t)) < sizeof(int16_t)) + { + break; + } + } + return elem; +} + +size_t DataStream::WriteAndConvertArrayOfInt32(const int32_t *buffer, size_t count) +{ + if (!CanWrite() || !buffer) + { + return 0; + } + + size_t elem; + for (elem = 0; elem < count && !EOS(); ++elem, ++buffer) + { + int32_t val = *buffer; + ConvertInt32(val); + if (Write(&val, sizeof(int32_t)) < sizeof(int32_t)) + { + break; + } + } + return elem; +} + +size_t DataStream::WriteAndConvertArrayOfInt64(const int64_t *buffer, size_t count) +{ + if (!CanWrite() || !buffer) + { + return 0; + } + + size_t elem; + for (elem = 0; elem < count && !EOS(); ++elem, ++buffer) + { + int64_t val = *buffer; + ConvertInt64(val); + if (Write(&val, sizeof(int64_t)) < sizeof(int64_t)) + { + break; + } + } + return elem; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/datastream.h b/engines/ags/shared/util/datastream.h new file mode 100644 index 00000000000..2d1f489c852 --- /dev/null +++ b/engines/ags/shared/util/datastream.h @@ -0,0 +1,132 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Standard AGS stream implementation for reading raw data with support for +// converting to opposite endianess. Most I/O devices should inherit this +// class and provide implementation for basic reading and writing only. +// +//============================================================================= +#ifndef __AGS_CN_UTIL__DATASTREAM_H +#define __AGS_CN_UTIL__DATASTREAM_H + +#include "util/bbop.h" +#include "util/stream.h" + +namespace AGS +{ +namespace Common +{ + +class DataStream : public Stream +{ +public: + DataStream(DataEndianess stream_endianess = kLittleEndian); + ~DataStream() override; + + int16_t ReadInt16() override; + int32_t ReadInt32() override; + int64_t ReadInt64() override; + + // + // Read- and WriteArray methods return number of full elements (NOT bytes) + // read or written, or -1 if end of stream is reached + // + // Note that ReadArray and WriteArray do NOT convert byte order even when + // work with data of different endianess; they are meant for optimal + // reading and writing blocks of raw bytes + inline size_t ReadArray(void *buffer, size_t elem_size, size_t count) override + { + return Read(buffer, elem_size * count) / elem_size; + } + + inline size_t ReadArrayOfInt16(int16_t *buffer, size_t count) override + { + return MustSwapBytes() ? + ReadAndConvertArrayOfInt16(buffer, count) : ReadArray(buffer, sizeof(int16_t), count); + } + + inline size_t ReadArrayOfInt32(int32_t *buffer, size_t count) override + { + return MustSwapBytes() ? + ReadAndConvertArrayOfInt32(buffer, count) : ReadArray(buffer, sizeof(int32_t), count); + } + + inline size_t ReadArrayOfInt64(int64_t *buffer, size_t count) override + { + return MustSwapBytes() ? + ReadAndConvertArrayOfInt64(buffer, count) : ReadArray(buffer, sizeof(int64_t), count); + } + + size_t WriteInt16(int16_t val) override; + size_t WriteInt32(int32_t val) override; + size_t WriteInt64(int64_t val) override; + + inline size_t WriteArray(const void *buffer, size_t elem_size, size_t count) override + { + return Write(buffer, elem_size * count) / elem_size; + } + + inline size_t WriteArrayOfInt16(const int16_t *buffer, size_t count) override + { + return MustSwapBytes() ? + WriteAndConvertArrayOfInt16(buffer, count) : WriteArray(buffer, sizeof(int16_t), count); + } + + inline size_t WriteArrayOfInt32(const int32_t *buffer, size_t count) override + { + return MustSwapBytes() ? + WriteAndConvertArrayOfInt32(buffer, count) : WriteArray(buffer, sizeof(int32_t), count); + } + + inline size_t WriteArrayOfInt64(const int64_t *buffer, size_t count) override + { + return MustSwapBytes() ? + WriteAndConvertArrayOfInt64(buffer, count) : WriteArray(buffer, sizeof(int64_t), count); + } + +protected: + DataEndianess _streamEndianess; + + // Helper methods for reading/writing arrays of basic types and + // converting their elements to opposite endianess (swapping bytes). + size_t ReadAndConvertArrayOfInt16(int16_t *buffer, size_t count); + size_t ReadAndConvertArrayOfInt32(int32_t *buffer, size_t count); + size_t ReadAndConvertArrayOfInt64(int64_t *buffer, size_t count); + size_t WriteAndConvertArrayOfInt16(const int16_t *buffer, size_t count); + size_t WriteAndConvertArrayOfInt32(const int32_t *buffer, size_t count); + size_t WriteAndConvertArrayOfInt64(const int64_t *buffer, size_t count); + + inline bool MustSwapBytes() + { + return kDefaultSystemEndianess != _streamEndianess; + } + + inline void ConvertInt16(int16_t &val) + { + if (MustSwapBytes()) val = BBOp::SwapBytesInt16(val); + } + inline void ConvertInt32(int32_t &val) + { + if (MustSwapBytes()) val = BBOp::SwapBytesInt32(val); + } + inline void ConvertInt64(int64_t &val) + { + if (MustSwapBytes()) val = BBOp::SwapBytesInt64(val); + } +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__DATASTREAM_H diff --git a/engines/ags/shared/util/directory.cpp b/engines/ags/shared/util/directory.cpp new file mode 100644 index 00000000000..acea430a9e3 --- /dev/null +++ b/engines/ags/shared/util/directory.cpp @@ -0,0 +1,72 @@ + +#include "core/platform.h" +#include +#if AGS_PLATFORM_OS_WINDOWS +#include +#else +#include +#include +#endif +#include "util/directory.h" +#include "util/path.h" +#include "stdio_compat.h" + +namespace AGS +{ +namespace Common +{ + +namespace Directory +{ + +bool CreateDirectory(const String &path) +{ + return mkdir(path +#if ! AGS_PLATFORM_OS_WINDOWS + , 0755 +#endif + ) == 0 || errno == EEXIST; +} + +bool CreateAllDirectories(const String &parent, const String &path) +{ + if (!ags_directory_exists(parent.GetCStr())) + return false; + if (path.IsEmpty()) + return true; + if (!Path::IsSameOrSubDir(parent, path)) + return false; + + String sub_path = Path::MakeRelativePath(parent, path); + String make_path = parent; + std::vector dirs = sub_path.Split('/'); + for (const String &dir : dirs) + { + if (dir.IsEmpty() || dir.Compare(".") == 0) continue; + make_path.AppendChar('/'); + make_path.Append(dir); + if (!CreateDirectory(make_path)) + return false; + } + return true; +} + +String SetCurrentDirectory(const String &path) +{ + chdir(path); + return GetCurrentDirectory(); +} + +String GetCurrentDirectory() +{ + char buf[512]; + getcwd(buf, 512); + String str(buf); + Path::FixupPath(str); + return str; +} + +} // namespace Directory + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/directory.h b/engines/ags/shared/util/directory.h new file mode 100644 index 00000000000..b5a63497566 --- /dev/null +++ b/engines/ags/shared/util/directory.h @@ -0,0 +1,45 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Platform-independent Directory functions +// +//============================================================================= +#ifndef __AGS_CN_UTIL__DIRECTORY_H +#define __AGS_CN_UTIL__DIRECTORY_H + +#include "core/platform.h" +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +namespace Directory +{ + // Creates new directory (if it does not exist) + bool CreateDirectory(const String &path); + // Makes sure all directories in the path are created. Parent path is + // not touched, and function must fail if parent path is not accessible. + bool CreateAllDirectories(const String &parent, const String &path); + // Sets current working directory, returns the resulting path + String SetCurrentDirectory(const String &path); + // Gets current working directory + String GetCurrentDirectory(); +} // namespace Directory + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__DIRECTORY_H diff --git a/engines/ags/shared/util/error.h b/engines/ags/shared/util/error.h new file mode 100644 index 00000000000..1ada93918ce --- /dev/null +++ b/engines/ags/shared/util/error.h @@ -0,0 +1,134 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Universal error class, that may be used both as a return value or +// thrown as an exception. +// +//============================================================================= +#ifndef __AGS_CN_UTIL__ERROR_H +#define __AGS_CN_UTIL__ERROR_H + +#include +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +class Error; +typedef std::shared_ptr PError; + +// +// A simple struct, that provides several fields to describe an error in the program. +// If wanted, may be reworked into subclass of std::exception. +// +class Error +{ +public: + Error(int code, String general, PError inner_error = PError()) : _code(code), _general(general), _innerError(inner_error) {} + Error(int code, String general, String comment, PError inner_error = PError()) : _code(code), _general(general), _comment(comment), _innerError(inner_error) {} + Error(String general, PError inner_error = PError()) : _code(0), _general(general), _innerError(inner_error) {} + Error(String general, String comment, PError inner_error = PError()) : _code(0), _general(general), _comment(comment), _innerError(inner_error) {} + + + // Error code is a number, defining error subtype. It is not much use to the end-user, + // but may be checked in the program to know more precise cause of the error. + int Code() const { return _code; } + // General description of this error type and subtype. + String General() const { return _general; } + // Any complementary information. + String Comment() const { return _comment; } + PError InnerError() const { return _innerError; } + // Full error message combines general description and comment. + // NOTE: if made a child of std::exception, FullMessage may be substituted + // or complemented with virtual const char* what(). + String FullMessage() const + { + String msg; + const Error *err = this; + do + { + msg.Append(err->General()); + if (!err->Comment().IsEmpty()) + { + msg.AppendChar('\n'); + msg.Append(err->Comment()); + } + err = err->InnerError().get(); + if (err) + msg.AppendChar('\n'); + } while (err); + return msg; + } + +private: + int _code; // numeric code, for specific uses + String _general; // general description of this error class + String _comment; // additional information about particular case + PError _innerError; // previous error that caused this one +}; + + +// ErrorHandle is a helper class that lets you have an Error object +// wrapped in a smart pointer. ErrorHandle's only data member is a +// shared_ptr, which means that it does not cause too much data copying +// when used as a function's return value. +// Note, that the reason to have distinct class instead of a shared_ptr's +// typedef is an inverted boolean comparison: +// shared_ptr converts to 'true' when it contains an object, but ErrorHandle +// returns 'true' when it *does NOT* contain an object, meaning there +// is no error. +template class ErrorHandle +{ +public: + static ErrorHandle None() { return ErrorHandle(); } + + ErrorHandle() = default; + ErrorHandle(T *err) : _error(err) {} + ErrorHandle(std::shared_ptr err) : _error(err) {} + + bool HasError() const { return _error.get() != NULL; } + explicit operator bool() const { return _error.get() == nullptr; } + operator PError() const { return _error; } + T *operator ->() const { return _error.operator->(); } + T &operator *() const { return _error.operator*(); } + +private: + std::shared_ptr _error; +}; + + +// Basic error handle, containing Error object +typedef ErrorHandle HError; + + +// TypedCodeError is the Error's subclass, which only purpose is to override +// error code type in constructor and Code() getter, that may be useful if +// you'd like to restrict code values to particular enumerator. +template +class TypedCodeError : public Error +{ +public: + TypedCodeError(CodeType code, PError inner_error = PError()) : Error(code, GetErrorText(code), inner_error) {} + TypedCodeError(CodeType code, String comment, PError inner_error = PError()) : + Error(code, GetErrorText(code), comment, inner_error) {} + + CodeType Code() const { return (CodeType)Error::Code(); } +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__ERROR_H diff --git a/engines/ags/shared/util/file.cpp b/engines/ags/shared/util/file.cpp new file mode 100644 index 00000000000..8f1fb3f6feb --- /dev/null +++ b/engines/ags/shared/util/file.cpp @@ -0,0 +1,177 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/file.h" + +#include +#include "core/platform.h" +#include "util/stdio_compat.h" +#include +#include "util/filestream.h" +#include "util/bufferedstream.h" + +namespace AGS +{ +namespace Common +{ + +soff_t File::GetFileSize(const String &filename) +{ + return ags_file_size(filename.GetCStr()); +} + +bool File::TestReadFile(const String &filename) +{ + FILE *test_file = fopen(filename, "rb"); + if (test_file) + { + fclose(test_file); + return true; + } + return false; +} + +bool File::TestWriteFile(const String &filename) +{ + FILE *test_file = fopen(filename, "r+"); + if (test_file) + { + fclose(test_file); + return true; + } + return TestCreateFile(filename); +} + +bool File::TestCreateFile(const String &filename) +{ + FILE *test_file = fopen(filename, "wb"); + if (test_file) + { + fclose(test_file); + ::remove(filename); + return true; + } + return false; +} + +bool File::DeleteFile(const String &filename) +{ + if (::remove(filename) != 0) + { + int err; +#if AGS_PLATFORM_OS_WINDOWS + _get_errno(&err); +#else + err = errno; +#endif + if (err == EACCES) + { + return false; + } + } + return true; +} + +bool File::GetFileModesFromCMode(const String &cmode, FileOpenMode &open_mode, FileWorkMode &work_mode) +{ + // We do not test for 'b' and 't' here, because text mode reading/writing should be done with + // the use of ITextReader and ITextWriter implementations. + // The number of supported variants here is quite limited due the restrictions AGS makes on them. + bool read_base_mode = false; + // Default mode is open/read for safety reasons + open_mode = kFile_Open; + work_mode = kFile_Read; + for (size_t c = 0; c < cmode.GetLength(); ++c) + { + if (read_base_mode) + { + if (cmode[c] == '+') + { + work_mode = kFile_ReadWrite; + } + break; + } + else + { + if (cmode[c] == 'r') + { + open_mode = kFile_Open; + work_mode = kFile_Read; + read_base_mode = true; + } + else if (cmode[c] == 'a') + { + open_mode = kFile_Create; + work_mode = kFile_Write; + read_base_mode = true; + } + else if (cmode[c] == 'w') + { + open_mode = kFile_CreateAlways; + work_mode = kFile_Write; + read_base_mode = true; + } + } + } + return read_base_mode; +} + +String File::GetCMode(FileOpenMode open_mode, FileWorkMode work_mode) +{ + String mode; + if (open_mode == kFile_Open) + { + if (work_mode == kFile_Read) + mode.AppendChar('r'); + else if (work_mode == kFile_Write || work_mode == kFile_ReadWrite) + mode.Append("r+"); + } + else if (open_mode == kFile_Create) + { + if (work_mode == kFile_Write) + mode.AppendChar('a'); + else if (work_mode == kFile_Read || work_mode == kFile_ReadWrite) + mode.Append("a+"); + } + else if (open_mode == kFile_CreateAlways) + { + if (work_mode == kFile_Write) + mode.AppendChar('w'); + else if (work_mode == kFile_Read || work_mode == kFile_ReadWrite) + mode.Append("w+"); + } + mode.AppendChar('b'); + return mode; +} + +Stream *File::OpenFile(const String &filename, FileOpenMode open_mode, FileWorkMode work_mode) +{ + FileStream *fs = nullptr; + try { + if (work_mode == kFile_Read) // NOTE: BufferedStream does not work correctly in the write mode + fs = new BufferedStream(filename, open_mode, work_mode); + else + fs = new FileStream(filename, open_mode, work_mode); + if (fs != nullptr && !fs->IsValid()) { + delete fs; + fs = nullptr; + } + } catch(std::runtime_error) { + fs = nullptr; + } + return fs; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/file.h b/engines/ags/shared/util/file.h new file mode 100644 index 00000000000..c81e4140aa9 --- /dev/null +++ b/engines/ags/shared/util/file.h @@ -0,0 +1,86 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Platform-independent File functions +// +//============================================================================= +#ifndef __AGS_CN_UTIL__FILE_H +#define __AGS_CN_UTIL__FILE_H + +#include "core/platform.h" +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +// Forward declarations +class Stream; + +enum FileOpenMode +{ + kFile_Open, // Open existing file + kFile_Create, // Create new file, or open existing one + kFile_CreateAlways // Always create a new file, replacing any existing one +}; + +enum FileWorkMode +{ + kFile_Read, + kFile_Write, + kFile_ReadWrite +}; + +namespace File +{ + // Returns size of a file, or -1 if no such file found + soff_t GetFileSize(const String &filename); + // Tests if file could be opened for reading + bool TestReadFile(const String &filename); + // Opens a file for writing or creates new one if it does not exist; deletes file if it was created during test + bool TestWriteFile(const String &filename); + // Create new empty file and deletes it; returns TRUE if was able to create file + bool TestCreateFile(const String &filename); + // Deletes existing file; returns TRUE if was able to delete one + bool DeleteFile(const String &filename); + + // Sets FileOpenMode and FileWorkMode values corresponding to C-style file open mode string + bool GetFileModesFromCMode(const String &cmode, FileOpenMode &open_mode, FileWorkMode &work_mode); + // Gets C-style file mode from FileOpenMode and FileWorkMode + String GetCMode(FileOpenMode open_mode, FileWorkMode work_mode); + + Stream *OpenFile(const String &filename, FileOpenMode open_mode, FileWorkMode work_mode); + // Convenience helpers + // Create a totally new file, overwrite existing one + inline Stream *CreateFile(const String &filename) + { + return OpenFile(filename, kFile_CreateAlways, kFile_Write); + } + // Open existing file for reading + inline Stream *OpenFileRead(const String &filename) + { + return OpenFile(filename, kFile_Open, kFile_Read); + } + // Open existing file for writing (append) or create if it does not exist + inline Stream *OpenFileWrite(const String &filename) + { + return OpenFile(filename, kFile_Create, kFile_Write); + } +} // namespace File + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__FILE_H diff --git a/engines/ags/shared/util/filestream.cpp b/engines/ags/shared/util/filestream.cpp new file mode 100644 index 00000000000..6c681285b94 --- /dev/null +++ b/engines/ags/shared/util/filestream.cpp @@ -0,0 +1,180 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/filestream.h" + +#include +#include "util/stdio_compat.h" +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +FileStream::FileStream(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode, + DataEndianess stream_endianess) + : DataStream(stream_endianess) + , _file(nullptr) + , _openMode(open_mode) + , _workMode(work_mode) +{ + Open(file_name, open_mode, work_mode); +} + +FileStream::~FileStream() +{ + FileStream::Close(); +} + +bool FileStream::HasErrors() const +{ + return IsValid() && ferror(_file) != 0; +} + +void FileStream::Close() +{ + if (_file) + { + fclose(_file); + } + _file = nullptr; +} + +bool FileStream::Flush() +{ + if (_file) + { + return fflush(_file) == 0; + } + return false; +} + +bool FileStream::IsValid() const +{ + return _file != nullptr; +} + +bool FileStream::EOS() const +{ + return !IsValid() || feof(_file) != 0; +} + +soff_t FileStream::GetLength() const +{ + if (IsValid()) + { + soff_t pos = (soff_t)ags_ftell(_file); + ags_fseek(_file, 0, SEEK_END); + soff_t end = (soff_t)ags_ftell(_file); + ags_fseek(_file, pos, SEEK_SET); + return end; + } + + return 0; +} + +soff_t FileStream::GetPosition() const +{ + if (IsValid()) + { + return (soff_t) ags_ftell(_file); + } + return -1; +} + +bool FileStream::CanRead() const +{ + return IsValid() && _workMode != kFile_Write; +} + +bool FileStream::CanWrite() const +{ + return IsValid() && _workMode != kFile_Read; +} + +bool FileStream::CanSeek() const +{ + return IsValid(); +} + +size_t FileStream::Read(void *buffer, size_t size) +{ + if (_file && buffer) + { + return fread(buffer, sizeof(uint8_t), size, _file); + } + return 0; +} + +int32_t FileStream::ReadByte() +{ + if (_file) + { + return fgetc(_file); + } + return -1; +} + +size_t FileStream::Write(const void *buffer, size_t size) +{ + if (_file && buffer) + { + return fwrite(buffer, sizeof(uint8_t), size, _file); + } + return 0; +} + +int32_t FileStream::WriteByte(uint8_t val) +{ + if (_file) + { + return fputc(val, _file); + } + return -1; +} + +bool FileStream::Seek(soff_t offset, StreamSeek origin) +{ + if (!_file) + { + return false; + } + + int stdclib_origin; + switch (origin) + { + case kSeekBegin: stdclib_origin = SEEK_SET; break; + case kSeekCurrent: stdclib_origin = SEEK_CUR; break; + case kSeekEnd: stdclib_origin = SEEK_END; break; + default: + // TODO: warning to the log + return false; + } + + return ags_fseek(_file, (file_off_t)offset, stdclib_origin) == 0; +} + +void FileStream::Open(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode) +{ + String mode = File::GetCMode(open_mode, work_mode); + if (mode.IsEmpty()) + throw std::runtime_error("Error determining open mode"); + _file = fopen(file_name, mode); + if (_file == nullptr) + throw std::runtime_error("Error opening file."); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/filestream.h b/engines/ags/shared/util/filestream.h new file mode 100644 index 00000000000..d083da355f2 --- /dev/null +++ b/engines/ags/shared/util/filestream.h @@ -0,0 +1,77 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_UTIL__FILESTREAM_H +#define __AGS_CN_UTIL__FILESTREAM_H + +#include + +#include "util/datastream.h" +#include "util/file.h" // TODO: extract filestream mode constants + +namespace AGS +{ +namespace Common +{ + +class FileStream : public DataStream +{ +public: + + // Represents an open file object + // The constructor may raise std::runtime_error if + // - there is an issue opening the file (does not exist, locked, permissions, etc) + // - the open mode could not be determined + FileStream(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode, + DataEndianess stream_endianess = kLittleEndian); + ~FileStream() override; + + bool HasErrors() const override; + void Close() override; + bool Flush() override; + + // Is stream valid (underlying data initialized properly) + bool IsValid() const override; + // Is end of stream + bool EOS() const override; + // Total length of stream (if known) + soff_t GetLength() const override; + // Current position (if known) + soff_t GetPosition() const override; + bool CanRead() const override; + bool CanWrite() const override; + bool CanSeek() const override; + + size_t Read(void *buffer, size_t size) override; + int32_t ReadByte() override; + size_t Write(const void *buffer, size_t size) override; + int32_t WriteByte(uint8_t b) override; + + bool Seek(soff_t offset, StreamSeek origin) override; + +private: + void Open(const String &file_name, FileOpenMode open_mode, FileWorkMode work_mode); + + FILE *_file; + const FileOpenMode _openMode; + const FileWorkMode _workMode; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__FILESTREAM_H diff --git a/engines/ags/shared/util/geometry.cpp b/engines/ags/shared/util/geometry.cpp new file mode 100644 index 00000000000..06f9358df22 --- /dev/null +++ b/engines/ags/shared/util/geometry.cpp @@ -0,0 +1,133 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include "util/geometry.h" +#include +#include + +//namespace AGS +//{ +//namespace Common +//{ + +bool AreRectsIntersecting(const Rect &r1, const Rect &r2) +{ // NOTE: remember that in AGS Y axis is pointed downwards + return r1.Left <= r2.Right && r1.Right >= r2.Left && + r1.Top <= r2.Bottom && r1.Bottom >= r2.Top; +} + +bool IsRectInsideRect(const Rect &place, const Rect &item) +{ + return item.Left >= place.Left && item.Right <= place.Right && + item.Top >= place.Top && item.Bottom <= place.Bottom; +} + +float DistanceBetween(const Rect &r1, const Rect &r2) +{ + // https://gamedev.stackexchange.com/a/154040 + Rect rect_outer( + std::min(r1.Left, r2.Left), + std::min(r1.Top, r2.Top), + std::max(r1.Right, r2.Right), + std::max(r1.Bottom, r2.Bottom) + ); + int inner_width = std::max(0, rect_outer.GetWidth() - r1.GetWidth() - r2.GetWidth()); + int inner_height = std::max(0, rect_outer.GetHeight() - r1.GetHeight() - r2.GetHeight()); + return std::sqrt(inner_width ^ 2 + inner_height ^ 2); +} + +Size ProportionalStretch(int dest_w, int dest_h, int item_w, int item_h) +{ + int width = item_w ? dest_w : 0; + int height = item_w ? (dest_w * item_h / item_w) : 0; + if (height > dest_h) + { + width = item_h ? (dest_h * item_w / item_h) : 0; + height = dest_h; + } + return Size(width, height); +} + +Size ProportionalStretch(const Size &dest, const Size &item) +{ + return ProportionalStretch(dest.Width, dest.Height, item.Width, item.Height); +} + +int AlignInHRange(int x1, int x2, int off_x, int width, FrameAlignment align) +{ + if (align & kMAlignRight) + return off_x + x2 - width; + else if (align & kMAlignHCenter) + return off_x + x1 + ((x2 - x1 + 1) >> 1) - (width >> 1); + return off_x + x1; // kAlignLeft is default +} + +int AlignInVRange(int y1, int y2, int off_y, int height, FrameAlignment align) +{ + if (align & kMAlignBottom) + return off_y + y2 - height; + else if (align & kMAlignVCenter) + return off_y + y1 + ((y2 - y1 + 1) >> 1) - (height >> 1); + return off_y + y1; // kAlignTop is default +} + +Rect AlignInRect(const Rect &frame, const Rect &item, FrameAlignment align) +{ + int x = AlignInHRange(frame.Left, frame.Right, item.Left, item.GetWidth(), align); + int y = AlignInVRange(frame.Top, frame.Bottom, item.Top, item.GetHeight(), align); + + Rect dst_item = item; + dst_item.MoveTo(Point(x, y)); + return dst_item; +} + +Rect OffsetRect(const Rect &r, const Point off) +{ + return Rect(r.Left + off.X, r.Top + off.Y, r.Right + off.X, r.Bottom + off.Y); +} + +Rect CenterInRect(const Rect &place, const Rect &item) +{ + return RectWH((place.GetWidth() >> 1) - (item.GetWidth() >> 1), + (place.GetHeight() >> 1) - (item.GetHeight() >> 1), + item.GetWidth(), item.GetHeight()); +} + +Rect ClampToRect(const Rect &place, const Rect &item) +{ + return Rect( + AGSMath::Clamp(item.Left, place.Left, place.Right), + AGSMath::Clamp(item.Top, place.Top, place.Bottom), + AGSMath::Clamp(item.Right, place.Left, place.Right), + AGSMath::Clamp(item.Bottom, place.Top, place.Bottom) + ); +} + +Rect PlaceInRect(const Rect &place, const Rect &item, const RectPlacement &placement) +{ + switch (placement) + { + case kPlaceCenter: + return CenterInRect(place, item); + case kPlaceStretch: + return place; + case kPlaceStretchProportional: + return CenterInRect(place, + RectWH(ProportionalStretch(place.GetWidth(), place.GetHeight(), item.GetWidth(), item.GetHeight()))); + default: + return RectWH(place.Left + item.Left, place.Top + item.Top, item.GetWidth(), item.GetHeight()); + } +} + +//} // namespace Common +//} // namespace AGS diff --git a/engines/ags/shared/util/geometry.h b/engines/ags/shared/util/geometry.h new file mode 100644 index 00000000000..a3e3906736e --- /dev/null +++ b/engines/ags/shared/util/geometry.h @@ -0,0 +1,405 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Geometry data structures and helper functions +// +//============================================================================= +#ifndef __AGS_CN_UTIL__GEOMETRY_H +#define __AGS_CN_UTIL__GEOMETRY_H + +#include "util/math.h" + +namespace AGSMath = AGS::Common::Math; +//namespace AGS +//{ +//namespace Common +//{ + +// Type of alignment of a geometric item of rectangular boundaries. +enum FrameAlignment +{ + kAlignNone = 0, + + // Alignment options are representing 8 sides of a frame (rectangle); + // they are implemented as flags that may be combined together if it + // is wanted to define alignment to multiple sides at once. + kAlignTopLeft = 0x0001, + kAlignTopCenter = 0x0002, + kAlignTopRight = 0x0004, + kAlignMiddleLeft = 0x0008, + kAlignMiddleCenter = 0x0010, + kAlignMiddleRight = 0x0020, + kAlignBottomLeft = 0x0040, + kAlignBottomCenter = 0x0080, + kAlignBottomRight = 0x0100, + + // Masks are helping to determine whether alignment parameter contains + // particular horizontal or vertical component (for example: left side + // or bottom side) + kMAlignLeft = kAlignTopLeft | kAlignMiddleLeft | kAlignBottomLeft, + kMAlignRight = kAlignTopRight | kAlignMiddleRight | kAlignBottomRight, + kMAlignTop = kAlignTopLeft | kAlignTopCenter | kAlignTopRight, + kMAlignBottom = kAlignBottomLeft | kAlignBottomCenter | kAlignBottomRight, + kMAlignHCenter = kAlignTopCenter | kAlignMiddleCenter | kAlignBottomCenter, + kMAlignVCenter = kAlignMiddleLeft | kAlignMiddleCenter | kAlignMiddleRight +}; + +// Horizontal alignment; based on FrameAlignment, used to restrict alignment +// setting to left/right/center option, while keeping compatibility with any +// alignment in case it will be supported in the future. +enum HorAlignment +{ + kHAlignNone = kAlignNone, + kHAlignLeft = kAlignTopLeft, + kHAlignRight = kAlignTopRight, + kHAlignCenter = kAlignTopCenter +}; + +enum RectPlacement +{ + kPlaceOffset, + kPlaceCenter, + kPlaceStretch, + kPlaceStretchProportional, + kNumRectPlacement +}; + +struct Point +{ + int X; + int Y; + + Point() + { + X = 0; + Y = 0; + } + + Point(int x, int y) + { + X = x; + Y = y; + } + + inline bool operator ==(const Point &p) const + { + return X == p.X && Y == p.Y; + } + + inline bool operator !=(const Point &p) const + { + return X != p.X || Y != p.Y; + } + + inline Point operator +(const Point &p) const + { + return Point(X + p.X, Y + p.Y); + } +}; + +struct Line +{ + int X1; + int Y1; + int X2; + int Y2; + + Line() + { + X1 = 0; + Y1 = 0; + X2 = 0; + Y2 = 0; + } + + Line(int x1, int y1, int x2, int y2) + { + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + } +}; + +// Helper factory functions +inline Line HLine(int x1, int x2, int y) +{ + return Line(x1, y, x2, y); +} + +inline Line VLine(int x, int y1, int y2) +{ + return Line(x, y1, x, y2); +} + +struct Size +{ + int Width; + int Height; + + Size() + { + Width = 0; + Height = 0; + } + + Size(int width, int height) + { + Width = width; + Height = height; + } + + inline bool IsNull() const + { + return Width <= 0 || Height <= 0; + } + + inline static Size Clamp(const Size &sz, const Size &floor, const Size &ceil) + { + return Size(AGSMath::Clamp(sz.Width, floor.Width, ceil.Width), + AGSMath::Clamp(sz.Height, floor.Height, ceil.Height)); + } + + // Indicates if current size exceeds other size by any metric + inline bool ExceedsByAny(const Size size) const + { + return Width > size.Width || Height > size.Height; + } + + inline bool operator==(const Size size) const + { + return Width == size.Width && Height == size.Height; + } + + inline bool operator!=(const Size size) const + { + return Width != size.Width || Height != size.Height; + } + + inline bool operator<(const Size &other) const + { // TODO: this implementation is silly and not universally useful; make a realistic one and replace with another function where necessary + return Width < other.Width || (Width == other.Width && Height < other.Height); + } + + inline Size operator *(int x) const + { + return Size(Width * x, Height * x); + } + + inline Size operator /(int x) const + { + return Size(Width / x, Height / x); + } + + inline Size &operator *=(int x) + { + Width *= x; + Height *= x; + return *this; + } + + inline Size &operator /=(int x) + { + Width /= x; + Height /= x; + return *this; + } +}; + +// TODO: consider making Rect have right-bottom coordinate with +1 offset +// to comply with many other libraries (i.e. Right - Left == Width) +struct Rect +{ + int Left; + int Top; + int Right; + int Bottom; + + Rect() + { + Left = 0; + Top = 0; + Right = -1; + Bottom = -1; + } + + Rect(int l, int t, int r, int b) + { + Left = l; + Top = t; + Right = r; + Bottom = b; + } + + inline Point GetLT() const + { + return Point(Left, Top); + } + + inline Point GetCenter() const + { + return Point(Left + GetWidth() / 2, Top + GetHeight() / 2); + } + + inline int GetWidth() const + { + return Right - Left + 1; + } + + inline int GetHeight() const + { + return Bottom - Top + 1; + } + + inline Size GetSize() const + { + return Size(GetWidth(), GetHeight()); + } + + inline bool IsEmpty() const + { + return Right < Left || Bottom < Top; + } + + inline bool IsInside(int x, int y) const + { + return x >= Left && y >= Top && (x <= Right) && (y <= Bottom); + } + + inline bool IsInside(const Point &pt) const + { + return IsInside(pt.X, pt.Y); + } + + inline void MoveToX(int x) + { + Right += x - Left; + Left = x; + } + + inline void MoveToY(int y) + { + Bottom += y - Top; + Top = y; + } + + inline void MoveTo(const Point &pt) + { + MoveToX(pt.X); + MoveToY(pt.Y); + } + + inline void SetWidth(int width) + { + Right = Left + width - 1; + } + + inline void SetHeight(int height) + { + Bottom = Top + height - 1; + } + + inline static Rect MoveBy(const Rect &r, int x, int y) + { + return Rect(r.Left + x, r.Top + y, r.Right + x, r.Bottom + y); + } +}; + +// Helper factory function +inline Rect RectWH(int x, int y, int width, int height) +{ + return Rect(x, y, x + width - 1, y + height - 1); +} + +inline Rect RectWH(const Size &sz) +{ + return Rect(0, 0, sz.Width - 1, sz.Height - 1); +} + + +struct Triangle +{ + int X1; + int Y1; + int X2; + int Y2; + int X3; + int Y3; + + Triangle() + { + X1 = 0; + Y1 = 0; + X2 = 0; + Y2 = 0; + X3 = 0; + Y3 = 0; + } + + Triangle(int x1, int y1, int x2, int y2, int x3, int y3) + { + X1 = x1; + Y1 = y1; + X2 = x2; + Y2 = y2; + X3 = x3; + Y3 = y3; + } +}; + +struct Circle +{ + int X; + int Y; + int Radius; + + Circle() + { + X = 0; + Y = 0; + Radius = 0; + } + + Circle(int x, int y, int radius) + { + X = x; + Y = y; + Radius = radius; + } + +}; + + +// Tells if two rectangles intersect (overlap) at least partially +bool AreRectsIntersecting(const Rect &r1, const Rect &r2); +// Tells if the item is completely inside place +bool IsRectInsideRect(const Rect &place, const Rect &item); +// Calculates a distance between two axis-aligned rectangles +float DistanceBetween(const Rect &r1, const Rect &r2); + +int AlignInHRange(int x1, int x2, int off_x, int width, FrameAlignment align); +int AlignInVRange(int y1, int y2, int off_y, int height, FrameAlignment align); +Rect AlignInRect(const Rect &frame, const Rect &item, FrameAlignment align); + +Size ProportionalStretch(int dest_w, int dest_h, int item_w, int item_h); +Size ProportionalStretch(const Size &dest, const Size &item); + +Rect OffsetRect(const Rect &r, const Point off); +Rect CenterInRect(const Rect &place, const Rect &item); +Rect ClampToRect(const Rect &place, const Rect &item); +Rect PlaceInRect(const Rect &place, const Rect &item, const RectPlacement &placement); +//} // namespace Common +//} // namespace AGS + +#endif // __AGS_CN_UTIL__GEOMETRY_H diff --git a/engines/ags/shared/util/ini_util.cpp b/engines/ags/shared/util/ini_util.cpp new file mode 100644 index 00000000000..2ee0943d9e4 --- /dev/null +++ b/engines/ags/shared/util/ini_util.cpp @@ -0,0 +1,194 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "util/file.h" +#include "util/ini_util.h" +#include "util/inifile.h" +#include "util/stream.h" +#include "util/textstreamwriter.h" + +namespace AGS +{ +namespace Common +{ + +typedef std::unique_ptr UStream; +typedef IniFile::SectionIterator SectionIterator; +typedef IniFile::ConstSectionIterator CSectionIterator; +typedef IniFile::ItemIterator ItemIterator; +typedef IniFile::ConstItemIterator CItemIterator; + +static bool ReadIni(const String &file, IniFile &ini) +{ + UStream fs(File::OpenFileRead(file)); + if (fs.get()) + { + ini.Read(fs.get()); + return true; + } + return false; +} + +bool IniUtil::Read(const String &file, ConfigTree &tree) +{ + // Read ini content + IniFile ini; + if (!ReadIni(file, ini)) + return false; + + // Copy items into key-value tree + for (CSectionIterator sec = ini.CBegin(); sec != ini.CEnd(); ++sec) + { + if (!sec->GetItemCount()) + continue; // skip empty sections + StringOrderMap &subtree = tree[sec->GetName()]; + for (CItemIterator item = sec->CBegin(); item != sec->CEnd(); ++item) + { + if (!item->IsKeyValue()) + continue; // skip non key-value items + subtree[item->GetKey()] = item->GetValue(); + } + } + return true; +} + +void IniUtil::Write(const String &file, const ConfigTree &tree) +{ + UStream fs(File::CreateFile(file)); + TextStreamWriter writer(fs.get()); + + for (ConfigNode it_sec = tree.begin(); it_sec != tree.end(); ++it_sec) + { + const String &sec_key = it_sec->first; + const StringOrderMap &sec_tree = it_sec->second; + + if (!sec_tree.size()) + continue; // skip empty sections + // write section name + if (!sec_key.IsEmpty()) + { + writer.WriteFormat("[%s]", sec_key.GetCStr()); + writer.WriteLineBreak(); + } + // write all items + for (StrStrOIter keyval = sec_tree.begin(); keyval != sec_tree.end(); ++keyval) + { + const String &item_key = keyval->first; + const String &item_value = keyval->second; + + writer.WriteFormat("%s = %s", item_key.GetCStr(), item_value.GetCStr()); + writer.WriteLineBreak(); + } + } + + writer.ReleaseStream(); +} + +void IniUtil::WriteToString(String &s, const ConfigTree &tree) +{ + for (ConfigNode it_sec = tree.begin(); it_sec != tree.end(); ++it_sec) + { + const String &sec_key = it_sec->first; + const StringOrderMap &sec_tree = it_sec->second; + if (!sec_tree.size()) + continue; // skip empty sections + // write section name + if (!sec_key.IsEmpty()) + s.Append(String::FromFormat("[%s]\n", sec_key.GetCStr())); + // write all items + for (StrStrOIter keyval = sec_tree.begin(); keyval != sec_tree.end(); ++keyval) + s.Append(String::FromFormat("%s = %s\n", keyval->first.GetCStr(), keyval->second.GetCStr())); + } +} + +bool IniUtil::Merge(const String &file, const ConfigTree &tree) +{ + // Read ini content + IniFile ini; + ReadIni(file, ini); // NOTE: missing file is a valid case + + // Remember the sections we find in file, if some sections are not found, + // they will be appended to the end of file. + std::map sections_found; + for (ConfigNode it = tree.begin(); it != tree.end(); ++it) + sections_found[it->first] = false; + + // Merge existing sections + for (SectionIterator sec = ini.Begin(); sec != ini.End(); ++sec) + { + if (!sec->GetItemCount()) + continue; // skip empty sections + String secname = sec->GetName(); + ConfigNode tree_node = tree.find(secname); + if (tree_node == tree.end()) + continue; // this section is not interesting for us + + // Remember the items we find in this section, if some items are not found, + // they will be appended to the end of section. + const StringOrderMap &subtree = tree_node->second; + std::map items_found; + for (StrStrOIter keyval = subtree.begin(); keyval != subtree.end(); ++keyval) + items_found[keyval->first] = false; + + // Replace matching items + for (ItemIterator item = sec->Begin(); item != sec->End(); ++item) + { + String key = item->GetKey(); + StrStrOIter keyval = subtree.find(key); + if (keyval == subtree.end()) + continue; // this item is not interesting for us + + String old_value = item->GetValue(); + String new_value = keyval->second; + if (old_value != new_value) + item->SetValue(new_value); + items_found[key] = true; + } + + // Append new items + if (!sections_found[secname]) + { + for (std::map::const_iterator item_f = items_found.begin(); item_f != items_found.end(); ++item_f) + { + if (item_f->second) + continue; // item was already found + StrStrOIter keyval = subtree.find(item_f->first); + ini.InsertItem(sec, sec->End(), keyval->first, keyval->second); + } + sections_found[secname] = true; // mark section as known + } + } + + // Add new sections + for (std::map::const_iterator sec_f = sections_found.begin(); sec_f != sections_found.end(); ++sec_f) + { + if (sec_f->second) + continue; + SectionIterator sec = ini.InsertSection(ini.End(), sec_f->first); + const StringOrderMap &subtree = tree.find(sec_f->first)->second; + for (StrStrOIter keyval = subtree.begin(); keyval != subtree.end(); ++keyval) + ini.InsertItem(sec, sec->End(), keyval->first, keyval->second); + } + + // Write the resulting set of lines + UStream fs(File::CreateFile(file)); + if (!fs.get()) + return false; + ini.Write(fs.get()); + return true; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/ini_util.h b/engines/ags/shared/util/ini_util.h new file mode 100644 index 00000000000..50eca7f2a58 --- /dev/null +++ b/engines/ags/shared/util/ini_util.h @@ -0,0 +1,66 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Functions for exchanging configuration data between key-value tree and +// INI file. +// +//============================================================================= +#ifndef __AGS_CN_UTIL__INIUTIL_H +#define __AGS_CN_UTIL__INIUTIL_H + +#include +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +typedef std::map StringOrderMap; +typedef StringOrderMap::const_iterator StrStrOIter; + +typedef std::map ConfigTree; +typedef ConfigTree::const_iterator ConfigNode; + +namespace IniUtil +{ + // Parse the contents of given file as INI format and insert values + // into the tree. The pre-existing tree items, if any, are NOT erased. + // Returns FALSE if the file could not be opened. + bool Read(const String &file, ConfigTree &tree); + // Serialize given tree to the stream in INI text format. + // The INI format suggests only one nested level (group - items). + // The first level values are treated as a global section items. + // The sub-nodes beyond 2nd level are ignored completely. + void Write(const String &file, const ConfigTree &tree); + // Serialize given tree to the string in INI text format. + // TODO: implement proper memory/string stream compatible with base Stream + // class and merge this with Write function. + void WriteToString(String &s, const ConfigTree &tree); + // Parse the contents of given source stream as INI format and merge + // with values of the given tree while doing only minimal replaces; + // write the result into destination stream. + // If item already exists, only value is overwrited, if section exists, + // new items are appended to the end of it; completely new sections are + // appended to the end of text. + // Source and destination streams may refer either to different objects, + // or same stream opened for both reading and writing. + // Returns FALSE if the file could not be opened for writing. + bool Merge(const String &file, const ConfigTree &tree); +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__INIUTIL_H diff --git a/engines/ags/shared/util/inifile.cpp b/engines/ags/shared/util/inifile.cpp new file mode 100644 index 00000000000..10665dd2dc7 --- /dev/null +++ b/engines/ags/shared/util/inifile.cpp @@ -0,0 +1,301 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include +#include "util/inifile.h" +#include "util/textstreamreader.h" +#include "util/textstreamwriter.h" + +// TODO: replace with C++11 std::isblank library function +namespace agsstd +{ +inline int isblank(int ch) +{ + return ch == ' ' || ch == '\t'; +} +} // std + +namespace AGS +{ +namespace Common +{ + +inline static void ReplaceSubString(String &line, IniFile::StrPos &sub_pos, const String &new_sub) +{ + line.ReplaceMid(sub_pos.first, sub_pos.second - sub_pos.first, new_sub); +} + +IniFile::ItemDef::ItemDef(const String &key, const String &value) +{ + Line = String::FromFormat("%s=%s", key.GetCStr(), value.GetCStr()); + Key.first = 0; + Key.second = Key.first + key.GetLength(); + SepAt = Key.second; + Value.first = Key.second + 1; + Value.second = Value.first + value.GetLength(); +} + +IniFile::ItemDef::ItemDef(const String &line, const StrPos &key, const StrPos &value, int sep_at) +{ + Line = line; + Key = key; + Value = value; + SepAt = sep_at; +} + +void IniFile::ItemDef::SetKey(const String &key) +{ + if (key.IsEmpty()) + return; + + if (IsKeyValue()) + { + int diff = key.GetLength() - (Key.second - Key.first); + ReplaceSubString(Line, Key, key); + Key.second += diff; + Value.first += diff; + Value.second += diff; + } + else + { + *this = ItemDef(key, ""); + } +} + +void IniFile::ItemDef::SetValue(const String &value) +{ + if (!IsKeyValue()) + return; // no key + + if (SepAt > 0) + { // replacing existing value + int diff = static_cast(value.GetLength()) - (Value.second - Value.first); + ReplaceSubString(Line, Value, value); + Value.second += diff; + } + else + { // inserting value behind the key + StrPos valpos(Key.second, Key.second); + ReplaceSubString(Line, valpos, String::FromFormat("=%s", value.GetCStr())); + } +} + +IniFile::SectionDef::SectionDef(const String &name) +{ + if (name.IsEmpty()) + { + // global section + Name = StrPos(0,0); + } + else + { + // named section + Header = String::FromFormat("[%s]", name.GetCStr()); + Name.first = 1; + Name.second = 1 + Header.GetLength(); + } +} + +IniFile::SectionDef::SectionDef(const String &line, const StrPos &name) +{ + Header = line; + Name = name; +} + +void IniFile::SectionDef::SetName(const String &sec_name) +{ + if (sec_name.IsEmpty()) + return; + + int diff = sec_name.GetLength() - (Name.second - Name.first); + ReplaceSubString(Header, Name, sec_name); + Name.second += diff; +} + +void IniFile::SectionDef::Clear() +{ + Items.clear(); +} + +IniFile::ItemIterator IniFile::SectionDef::InsertItem(ItemIterator item, const ItemDef &itemdef) +{ + return Items.insert(item, itemdef); +} + +void IniFile::SectionDef::EraseItem(ItemIterator item) +{ + Items.erase(item); +} + +IniFile::ItemIterator IniFile::InsertItem(SectionIterator sec, ItemIterator item, const String &key, const String &value) +{ + ItemDef itemdef(key, value); + return sec->InsertItem(item, itemdef); +} + +IniFile::SectionIterator IniFile::InsertSection(SectionIterator sec, const String &name) +{ + if (name.IsEmpty()) + return _sections.end(); // do not allow adding random global sections + + SectionDef secdef(name); + return _sections.insert(sec, secdef); +} + +void IniFile::RemoveItem(SectionIterator sec, ItemIterator item) +{ + sec->EraseItem(item); +} + +void IniFile::RemoveSection(SectionIterator sec) +{ + if (sec == _sections.begin()) + // do not remove global section, clear items instead + sec->Clear(); + else + _sections.erase(sec); +} + + +// Moves string pointer forward to the first non-space character +const char *SkipSpace(const char *line, const char *endl) +{ + for (; line != endl && isspace(*line); ++line); + return line; +} + +// Parse given line and extract a meaningful string; +// Parses line from 'line' to 'endl', skips padding (spaces) +// at the beginning and the end. Assignes the starting and ending +// pointers of the string. Returns pointer to where parsing stopped. +// The 'endl' must point beyond the last character of the string +// (e.g. terminator). +const char *ParsePaddedString(const char *line, const char *endl, + const char *&str_at, const char *&str_end) +{ + // skip left padding + for (; line != endl && agsstd::isblank(*line); ++line); + str_at = line; + // skip right padding + const char *p_value = line; + for (line = endl; line != p_value && agsstd::isblank(*(line - 1)); --line); + str_end = line; + return line; +} + +IniFile::IniFile() +{ + // precreate global section + _sections.push_back(SectionDef("")); +} + +void IniFile::Read(Stream *in) +{ + TextStreamReader reader(in); + + _sections.clear(); + // Create a global section; + // all the items we meet before explicit section declaration + // will be treated as "global" items. + _sections.push_back(SectionDef("")); + SectionDef *cur_section = &_sections.back(); + + do + { + String line = reader.ReadLine(); + if (line.IsEmpty() && reader.EOS()) + break; + + const char *cstr = line.GetCStr(); + const char *pstr = cstr; + const char *endl = cstr + line.GetLength(); + + // Find first non-space character + pstr = SkipSpace(pstr, endl); + if (pstr == endl) + continue; // empty line + + // Detect the kind of string we found + if ((endl - pstr >= 2 && *pstr == '/' && *(pstr + 1) == '/') || + (endl - pstr >= 1 && (*pstr == '#' || *pstr == ';'))) + { + StrPos nullpos(0,0); + cur_section->InsertItem(cur_section->End(), ItemDef(line, nullpos, nullpos, -1)); + continue; + } + + if (*pstr == '[') + { + // Parse this as section + const char *pstr_end = strrchr(pstr, ']'); + if (pstr_end < pstr) + continue; // no closing bracket + // Parse the section name + const char *str_at, *str_end; + ParsePaddedString(++pstr, pstr_end, str_at, str_end); + if (str_end == str_at) + continue; // inappropriate data or empty string + StrPos namepos(str_at - cstr, str_end - cstr); + _sections.push_back(SectionDef(line, namepos)); + cur_section = &_sections.back(); + } + else + { + // Parse this as a key-value pair + const char *pstr_end = strchr(pstr, '='); + if (pstr_end == pstr) + continue; // no key part, skip the line + if (!pstr_end) + pstr_end = endl; // no value part + // Parse key + const char *str_at, *str_end; + ParsePaddedString(pstr, pstr_end, str_at, str_end); + pstr = pstr_end; + if (str_end == str_at) + continue; // inappropriate data or empty string + // Create an item and parse value, if any + StrPos keypos(str_at - cstr, str_end - cstr); + StrPos valpos(0, 0); + int sep_at = -1; + if (pstr != endl) + { + sep_at = pstr - cstr; + ParsePaddedString(++pstr, endl, str_at, str_end); + valpos.first = str_at - cstr; + valpos.second = str_end - cstr; + } + cur_section->InsertItem(cur_section->End(), ItemDef(line, keypos, valpos, sep_at)); + } + } + while (!reader.EOS()); + + reader.ReleaseStream(); +} + +void IniFile::Write(Stream *out) const +{ + TextStreamWriter writer(out); + for (ConstSectionIterator sec = _sections.begin(); sec != _sections.end(); ++sec) + { + if (sec != _sections.begin()) // do not write global section's name + writer.WriteLine(sec->GetLine()); + for (ConstItemIterator item = sec->CBegin(); item != sec->CEnd(); ++item) + writer.WriteLine(item->GetLine()); + } + writer.ReleaseStream(); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/inifile.h b/engines/ags/shared/util/inifile.h new file mode 100644 index 00000000000..0f26ce5cfb8 --- /dev/null +++ b/engines/ags/shared/util/inifile.h @@ -0,0 +1,134 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// IniFile class defines contents of the configuration file. +// It serves as a INI parser and plain enumerator of all the sections and items +// found in file, or, oppositely, as INI file constructor. +// But is not much suitable for regular key/value lookup. It is suggested to +// create a proper map to store items, from IniFile contents. +// +//============================================================================= +#ifndef __AGS_CN_UTIL__INIFILE_H +#define __AGS_CN_UTIL__INIFILE_H + +#include +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +class IniFile +{ +public: + // Position of a string in the line of text: + // is defined by a pair of first and next-after-last character indices + typedef std::pair StrPos; + // Location of section in the array of text lines: + // is defined by a pair of first and next-after-last line indices + typedef std::pair SectionPos; + + // Item definition + // Valid key indicates a key-value line; no key means unparsed + // line of text, e.g. comment or incorrectly formatted item. + class ItemDef + { + public: + ItemDef(const String &key, const String &value); + ItemDef(const String &line, const StrPos &key, const StrPos &value, int sep_at); + String GetLine() const { return Line; } + String GetKey() const { return SubString(Line, Key); } + String GetValue() const { return SubString(Line, Value); } + bool IsKeyValue() const { return Key.second - Key.first > 0; } + void SetKey(const String &key); + void SetValue(const String &value); + + private: + String Line; // actual text + StrPos Key; // position of item key + int SepAt; // position of the separator (assignment) symbol + StrPos Value; // position of item value + }; + // Linked list of items + typedef std::list LItems; + typedef LItems::iterator ItemIterator; + typedef LItems::const_iterator ConstItemIterator; + + // Section definition + class SectionDef + { + public: + SectionDef(const String &name); + SectionDef(const String &line, const StrPos &name); + String GetLine() const { return Header; } + String GetName() const { return SubString(Header, Name); } + size_t GetItemCount() const { return Items.size(); } + bool IsGlobal() const { return Name.second - Name.first <= 0; } + ItemIterator Begin() { return Items.begin(); } + ItemIterator End() { return Items.end(); } + ConstItemIterator CBegin() const { return Items.begin(); } + ConstItemIterator CEnd() const { return Items.end(); } + void SetName(const String &sec_name); + void Clear(); + ItemIterator InsertItem(ItemIterator item, const ItemDef &itemdef); + void EraseItem(ItemIterator item); + + private: + String Header;// section's heading line + StrPos Name; // location of section name in the header line + LItems Items; // linked list of items belonging to the section + }; + // Linked list of sections + typedef std::list LSections; + typedef LSections::iterator SectionIterator; + typedef LSections::const_iterator ConstSectionIterator; + +private: + inline static String SubString(const String &line, const StrPos &pos) + { + return line.Mid(pos.first, pos.second - pos.first); + } + +public: + IniFile(); + + SectionIterator Begin() { return _sections.begin(); } + SectionIterator End() { return _sections.end(); } + ConstSectionIterator CBegin() const { return _sections.begin(); } + ConstSectionIterator CEnd() const { return _sections.end(); } + + void Read(Stream *in); + void Write(Stream *out) const; + + // Return number of sections + size_t GetSectionCount() const { return _sections.size(); } + // Insert new item *before* existing item + ItemIterator InsertItem(SectionIterator sec, ItemIterator item, const String &key, const String &value); + // Insert new section *before* existing section + SectionIterator InsertSection(SectionIterator sec, const String &name); + // Remove a single item + void RemoveItem(SectionIterator sec, ItemIterator item); + // Completely remove whole section; this removes all lines between section + // header and the last item found in that section (inclusive). + void RemoveSection(SectionIterator sec); + +private: + LSections _sections; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__INIFILE_H diff --git a/engines/ags/shared/util/lzw.cpp b/engines/ags/shared/util/lzw.cpp new file mode 100644 index 00000000000..7eadcd8e281 --- /dev/null +++ b/engines/ags/shared/util/lzw.cpp @@ -0,0 +1,271 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// LZW compression -- the LZW/GIF patent has expired, so we can use it now!!! +// +//============================================================================= + +#include +#include "ac/common.h" // quit +#include "util/stream.h" + +using namespace AGS::Common; + +#ifdef _MANAGED +// ensure this doesn't get compiled to .NET IL +#pragma unmanaged +#endif + +int insert(int, int); +void _delete(int); + +#define N 4096 +#define F 16 +#define THRESHOLD 3 +#define min(xx,yy) ((yy match) { + match = n; + pos = j; + } + + if (c < 0) { + p = &lson[j]; + k = n; + } else if (c > 0) { + p = &rson[j]; + l = n; + } else { + dad[j] = NIL; + dad[lson[j]] = lson + i - node; + dad[rson[j]] = rson + i - node; + lson[i] = lson[j]; + rson[i] = rson[j]; + break; + } + } + + dad[i] = p - node; + *p = i; + return match; +} + +void _delete(int z) +{ + int j; + + if (dad[z] != NIL) { + if (rson[z] == NIL) + j = lson[z]; + else if (lson[z] == NIL) + j = rson[z]; + else { + j = lson[z]; + if (rson[j] != NIL) { + do { + j = rson[j]; + } while (rson[j] != NIL); + + node[dad[j]] = lson[j]; + dad[lson[j]] = dad[j]; + lson[j] = lson[z]; + dad[lson[z]] = lson + j - node; + } + + rson[j] = rson[z]; + dad[rson[z]] = rson + j - node; + } + + dad[j] = dad[z]; + node[dad[z]] = j; + dad[z] = NIL; + } +} + +void lzwcompress(Stream *lzw_in, Stream *out) +{ + int ch, i, run, len, match, size, mask; + char buf[17]; + + lzbuffer = (char *)malloc(N + F + (N + 1 + N + N + 256) * sizeof(int)); // 28.5 k ! + if (lzbuffer == nullptr) { + quit("unable to compress: out of memory"); + } + + node = (int *)(lzbuffer + N + F); + for (i = 0; i < 256; i++) + root[i] = NIL; + + for (i = NIL; i < N; i++) + dad[i] = NIL; + + size = mask = 1; + buf[0] = 0; + i = N - F - F; + + for (len = 0; len < F && (ch = lzw_in->ReadByte()) != -1; len++) { + lzbuffer[i + F] = ch; + i = (i + 1) & (N - 1); + } + + run = len; + + do { + ch = lzw_in->ReadByte(); + if (i >= N - F) { + _delete(i + F - N); + lzbuffer[i + F] = lzbuffer[i + F - N] = ch; + } else { + _delete(i + F); + lzbuffer[i + F] = ch; + } + + match = insert(i, run); + if (ch == -1) { + run--; + len--; + } + + if (len++ >= run) { + if (match >= THRESHOLD) { + buf[0] |= mask; + // possible fix: change int* to short* ?? + *(short *)(buf + size) = ((match - 3) << 12) | ((i - pos - 1) & (N - 1)); + size += 2; + len -= match; + } else { + buf[size++] = lzbuffer[i]; + len--; + } + + if (!((mask += mask) & 0xFF)) { + out->WriteArray(buf, size, 1); + outbytes += size; + size = mask = 1; + buf[0] = 0; + } + } + i = (i + 1) & (N - 1); + } while (len > 0); + + if (size > 1) { + out->WriteArray(buf, size, 1); + outbytes += size; + } + + free(lzbuffer); +} + +int expand_to_mem = 0; +unsigned char *membfptr = nullptr; +void myputc(int ccc, Stream *out) +{ + if (maxsize > 0) { + putbytes++; + if (putbytes > maxsize) + return; + } + + outbytes++; + if (expand_to_mem) { + membfptr[0] = ccc; + membfptr++; + } else + out->WriteInt8(ccc); +} + +void lzwexpand(Stream *lzw_in, Stream *out) +{ + int bits, ch, i, j, len, mask; + char *lzbuffer; +// printf(" UnShrinking: %s ",filena); + putbytes = 0; + + lzbuffer = (char *)malloc(N); + if (lzbuffer == nullptr) { + quit("compress.cpp: unable to decompress: insufficient memory"); + } + i = N - F; + + // this end condition just checks for EOF, which is no good to us + while ((bits = lzw_in->ReadByte()) != -1) { + for (mask = 0x01; mask & 0xFF; mask <<= 1) { + if (bits & mask) { + // MACPORT FIX: read to short and expand + short jshort = 0; + jshort = lzw_in->ReadInt16(); + j = jshort; + + len = ((j >> 12) & 15) + 3; + j = (i - j - 1) & (N - 1); + + while (len--) { + myputc(lzbuffer[i] = lzbuffer[j], out); + j = (j + 1) & (N - 1); + i = (i + 1) & (N - 1); + } + } else { + ch = lzw_in->ReadByte(); + myputc(lzbuffer[i] = ch, out); + i = (i + 1) & (N - 1); + } + + if ((putbytes >= maxsize) && (maxsize > 0)) + break; + + if ((lzw_in->EOS()) && (maxsize > 0)) + quit("Read error decompressing image - file is corrupt"); + } // end for mask + + if ((putbytes >= maxsize) && (maxsize > 0)) + break; + } + + free(lzbuffer); + expand_to_mem = 0; +} + +unsigned char *lzwexpand_to_mem(Stream *in) +{ + unsigned char *membuff = (unsigned char *)malloc(maxsize + 10); + expand_to_mem = 1; + membfptr = membuff; + lzwexpand(in, nullptr); + return membuff; +} diff --git a/engines/ags/shared/util/lzw.h b/engines/ags/shared/util/lzw.h new file mode 100644 index 00000000000..b3c1af849b2 --- /dev/null +++ b/engines/ags/shared/util/lzw.h @@ -0,0 +1,29 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_UTIL__LZW_H +#define __AGS_CN_UTIL__LZW_H + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +void lzwcompress(Common::Stream *lzw_in, Common::Stream *out); +unsigned char *lzwexpand_to_mem(Common::Stream *in); + +extern long outbytes, maxsize, putbytes; + +#endif // __AGS_CN_UTIL__LZW_H diff --git a/engines/ags/shared/util/math.h b/engines/ags/shared/util/math.h new file mode 100644 index 00000000000..07367591984 --- /dev/null +++ b/engines/ags/shared/util/math.h @@ -0,0 +1,90 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Helper math functions +// +//============================================================================= +#ifndef __AGS_CN_UTIL__MATH_H +#define __AGS_CN_UTIL__MATH_H + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +namespace AGS +{ +namespace Common +{ + +namespace Math +{ + template + inline const T &Max(const T &a, const T &b) + { + return a < b ? b : a; + } + + template + inline const T &Min(const T &a, const T &b) + { + return a < b ? a : b; + } + + template + inline const T &Clamp(const T &val, const T &floor, const T &ceil) + { + return Max(floor, Min(val, ceil)); + } + + template + inline void ClampLength(T &from, T &length, const T &floor, const T &height) + { + if (from < floor) + { + length -= floor - from; + from = floor; + } + else if (from >= floor + height) + { + from = floor + height; + length = 0; + } + + length = Max(length, 0); + length = Min(length, height - from); + } + + // Get a measure of how value A is greater than value B; + // if A is smaller than or equal to B, returns 0. + template + inline T Surplus(const T &larger, const T &smaller) + { + return larger > smaller ? larger - smaller : 0; + } + + inline float RadiansToDegrees(float rads) + { + return rads * (float)(180.0 / M_PI); + } + + inline float DegreesToRadians(float deg) + { + return deg * (float)(M_PI / 180.0); + } +} // namespace Math + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__MATH_H diff --git a/engines/ags/shared/util/memory.h b/engines/ags/shared/util/memory.h new file mode 100644 index 00000000000..5e049d74e59 --- /dev/null +++ b/engines/ags/shared/util/memory.h @@ -0,0 +1,245 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Memory utils and algorithms +// +//============================================================================= +#ifndef __AGS_CN_UTIL__MEMORY_H +#define __AGS_CN_UTIL__MEMORY_H + +#include +#include "util/bbop.h" +#include "util/math.h" + +#if defined (AGS_STRICT_ALIGNMENT) || defined (TEST_STRICT_ALIGNMENT) +#define MEMORY_STRICT_ALIGNMENT +#endif + +namespace AGS +{ +namespace Common +{ + +namespace Memory +{ + //------------------------------------------------------------------------- + // Converts pointer to 32-bit integer value and vice-versa. + // Only for use in special cases for compatibility with 32-bit plugin API. + // Dangerous on 64-bit systems. + //------------------------------------------------------------------------- + template + inline int32_t PtrToInt32(T *ptr) + { + return static_cast(reinterpret_cast(ptr)); + } + template + inline T *Int32ToPtr(int32_t value) + { + return reinterpret_cast(static_cast(value)); + } + + //------------------------------------------------------------------------- + // Functions for reading and writing basic types from/to the memory. + // Implement safety workaround for CPUs with alignment restrictions + // (e.g. MIPS). + //------------------------------------------------------------------------- + inline int16_t ReadInt16(const void *ptr) + { + #if defined (MEMORY_STRICT_ALIGNMENT) + const uint8_t *b = (const uint8_t *)ptr; + #if defined (BITBYTE_BIG_ENDIAN) + return (b[0] << 8) | b[1]; + #else + return (b[1] << 8) | b[0]; + #endif + #else + return *(int16_t*)ptr; + #endif + } + + inline int32_t ReadInt32(const void *ptr) + { + #if defined (MEMORY_STRICT_ALIGNMENT) + const uint8_t *b = (const uint8_t *)ptr; + #if defined (BITBYTE_BIG_ENDIAN) + return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; + #else + return (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; + #endif + #else + return *(int32_t*)ptr; + #endif + } + + inline int64_t ReadInt64(const void *ptr) + { + #if defined (MEMORY_STRICT_ALIGNMENT) + const uint8_t *b = (const uint8_t *)ptr; + #if defined (BITBYTE_BIG_ENDIAN) + return ((uint64_t)b[0] << 56) | ((uint64_t)b[1] << 48) | ((uint64_t)b[2] << 40) | ((uint64_t)b[3] << 32) | + (b[4] << 24) | (b[5] << 16) | (b[6] << 8) | b[7]; + #else + return ((uint64_t)b[7] << 56) | ((uint64_t)b[6] << 48) | ((uint64_t)b[5] << 40) | ((uint64_t)b[4] << 32) | + (b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]; + #endif + #else + return *(int64_t*)ptr; + #endif + } + + inline void WriteInt16(void *ptr, int16_t value) + { + #if defined (MEMORY_STRICT_ALIGNMENT) + uint8_t *b = (uint8_t *)ptr; + #if defined (BITBYTE_BIG_ENDIAN) + b[0] = (uint8_t)(value >> 8); + b[1] = (uint8_t)(value); + #else + b[1] = (uint8_t)(value >> 8); + b[0] = (uint8_t)(value); + #endif + #else + *(int16_t*)ptr = value; + #endif + } + + inline void WriteInt32(void *ptr, int32_t value) + { + #if defined (MEMORY_STRICT_ALIGNMENT) + uint8_t *b = (uint8_t *)ptr; + #if defined (BITBYTE_BIG_ENDIAN) + b[0] = (uint8_t)(value >> 24); + b[1] = (uint8_t)(value >> 16); + b[2] = (uint8_t)(value >> 8); + b[3] = (uint8_t)(value); + #else + b[3] = (uint8_t)(value >> 24); + b[2] = (uint8_t)(value >> 16); + b[1] = (uint8_t)(value >> 8); + b[0] = (uint8_t)(value); + #endif + #else + *(int32_t*)ptr = value; + #endif + } + + inline void WriteInt64(void *ptr, int64_t value) + { + #if defined (MEMORY_STRICT_ALIGNMENT) + uint8_t *b = (uint8_t *)ptr; + #if defined (BITBYTE_BIG_ENDIAN) + b[0] = (uint8_t)(value >> 56); + b[1] = (uint8_t)(value >> 48); + b[2] = (uint8_t)(value >> 40); + b[3] = (uint8_t)(value >> 32); + b[4] = (uint8_t)(value >> 24); + b[5] = (uint8_t)(value >> 16); + b[6] = (uint8_t)(value >> 8); + b[7] = (uint8_t)(value); + #else + b[7] = (uint8_t)(value >> 56); + b[6] = (uint8_t)(value >> 48); + b[5] = (uint8_t)(value >> 40); + b[4] = (uint8_t)(value >> 32); + b[3] = (uint8_t)(value >> 24); + b[2] = (uint8_t)(value >> 16); + b[1] = (uint8_t)(value >> 8); + b[0] = (uint8_t)(value); + #endif + #else + *(int64_t*)ptr = value; + #endif + } + + //------------------------------------------------------------------------- + // Helpers for reading and writing from memory with respect for endianess. + //------------------------------------------------------------------------- + inline int16_t ReadInt16LE(const void *ptr) + { + return BBOp::Int16FromLE(ReadInt16(ptr)); + } + + inline int32_t ReadInt32LE(const void *ptr) + { + return BBOp::Int32FromLE(ReadInt32(ptr)); + } + + inline int64_t ReadInt64LE(const void *ptr) + { + return BBOp::Int64FromLE(ReadInt64(ptr)); + } + + inline void WriteInt16LE(void *ptr, int16_t value) + { + WriteInt16(ptr, BBOp::Int16FromLE(value)); + } + + inline void WriteInt32LE(void *ptr, int32_t value) + { + WriteInt32(ptr, BBOp::Int32FromLE(value)); + } + + inline void WriteInt64LE(void *ptr, int64_t value) + { + WriteInt64(ptr, BBOp::Int64FromLE(value)); + } + + inline int16_t ReadInt16BE(const void *ptr) + { + return BBOp::Int16FromBE(ReadInt16(ptr)); + } + + inline int32_t ReadInt32BE(const void *ptr) + { + return BBOp::Int32FromBE(ReadInt32(ptr)); + } + + inline int64_t ReadInt64BE(const void *ptr) + { + return BBOp::Int64FromBE(ReadInt64(ptr)); + } + + inline void WriteInt16BE(void *ptr, int16_t value) + { + WriteInt16(ptr, BBOp::Int16FromBE(value)); + } + + inline void WriteInt32BE(void *ptr, int32_t value) + { + WriteInt32(ptr, BBOp::Int32FromBE(value)); + } + + inline void WriteInt64BE(void *ptr, int64_t value) + { + WriteInt64(ptr, BBOp::Int64FromBE(value)); + } + + //------------------------------------------------------------------------- + // Memory data manipulation + //------------------------------------------------------------------------- + // Copies block of 2d data from source into destination. + inline void BlockCopy(uint8_t *dst, const size_t dst_pitch, const size_t dst_offset, + const uint8_t *src, const size_t src_pitch, const size_t src_offset, + const size_t height) + { + for (size_t y = 0; y < height; ++y, src += src_pitch, dst += dst_pitch) + memcpy(dst + dst_offset, src + src_offset, Math::Min(dst_pitch - dst_offset, src_pitch - src_offset)); + } + +} // namespace Memory + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__MEMORY_H diff --git a/engines/ags/shared/util/misc.cpp b/engines/ags/shared/util/misc.cpp new file mode 100644 index 00000000000..8848b7e0255 --- /dev/null +++ b/engines/ags/shared/util/misc.cpp @@ -0,0 +1,200 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +/* + Copyright (c) 2003, Shawn R. Walker + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Shawn R. Walker nor names of contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "core/platform.h" + +#include +#include +#include +#if !AGS_PLATFORM_OS_WINDOWS +#include +#endif + +#include "allegro.h" +#include "util/file.h" +#include "util/stream.h" + + +using namespace AGS::Common; + +#if !defined (AGS_CASE_SENSITIVE_FILESYSTEM) +#include +/* File Name Concatenator basically on Windows / DOS */ +char *ci_find_file(const char *dir_name, const char *file_name) +{ + char *diamond = NULL; + + if (dir_name == NULL && file_name == NULL) + return NULL; + + if (dir_name == NULL) { + diamond = (char *)malloc(strlen(file_name) + 3); + strcpy(diamond, file_name); + } else { + diamond = (char *)malloc(strlen(dir_name) + strlen(file_name) + 2); + append_filename(diamond, dir_name, file_name, strlen(dir_name) + strlen(file_name) + 2); + } + fix_filename_case(diamond); + fix_filename_slashes(diamond); + return diamond; +} + +#else +/* Case Insensitive File Find */ +char *ci_find_file(const char *dir_name, const char *file_name) +{ + struct stat statbuf; + struct dirent *entry = nullptr; + DIR *rough = nullptr; + DIR *prevdir = nullptr; + char *diamond = nullptr; + char *directory = nullptr; + char *filename = nullptr; + + if (dir_name == nullptr && file_name == nullptr) + return nullptr; + + if (dir_name != nullptr) { + directory = (char *)malloc(strlen(dir_name) + 1); + strcpy(directory, dir_name); + + fix_filename_case(directory); + fix_filename_slashes(directory); + } + + if (file_name != nullptr) { + filename = (char *)malloc(strlen(file_name) + 1); + strcpy(filename, file_name); + + fix_filename_case(filename); + fix_filename_slashes(filename); + } + + if (directory == nullptr) { + char *match = nullptr; + int match_len = 0; + int dir_len = 0; + + match = get_filename(filename); + if (match == nullptr) + return nullptr; + + match_len = strlen(match); + dir_len = (match - filename); + + if (dir_len == 0) { + directory = (char *)malloc(2); + strcpy(directory,"."); + } else { + directory = (char *)malloc(dir_len + 1); + strncpy(directory, file_name, dir_len); + directory[dir_len] = '\0'; + } + + filename = (char *)malloc(match_len + 1); + strncpy(filename, match, match_len); + filename[match_len] = '\0'; + } + + if ((prevdir = opendir(".")) == nullptr) { + fprintf(stderr, "ci_find_file: cannot open current working directory\n"); + return nullptr; + } + + if (chdir(directory) == -1) { + fprintf(stderr, "ci_find_file: cannot change to directory: %s\n", directory); + return nullptr; + } + + if ((rough = opendir(directory)) == nullptr) { + fprintf(stderr, "ci_find_file: cannot open directory: %s\n", directory); + return nullptr; + } + + while ((entry = readdir(rough)) != nullptr) { + lstat(entry->d_name, &statbuf); + if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) { + if (strcasecmp(filename, entry->d_name) == 0) { +#if AGS_PLATFORM_DEBUG + fprintf(stderr, "ci_find_file: Looked for %s in rough %s, found diamond %s.\n", filename, directory, entry->d_name); +#endif // AGS_PLATFORM_DEBUG + diamond = (char *)malloc(strlen(directory) + strlen(entry->d_name) + 2); + append_filename(diamond, directory, entry->d_name, strlen(directory) + strlen(entry->d_name) + 2); + break; + } + } + } + closedir(rough); + + fchdir(dirfd(prevdir)); + closedir(prevdir); + + free(directory); + free(filename); + + return diamond; +} +#endif + + +/* Case Insensitive fopen */ +Stream *ci_fopen(const char *file_name, FileOpenMode open_mode, FileWorkMode work_mode) +{ +#if !defined (AGS_CASE_SENSITIVE_FILESYSTEM) + return File::OpenFile(file_name, open_mode, work_mode); +#else + Stream *fs = nullptr; + char *fullpath = ci_find_file(nullptr, (char*)file_name); + + /* If I didn't find a file, this could be writing a new file, + so use whatever file_name they passed */ + if (fullpath == nullptr) { + fs = File::OpenFile(file_name, open_mode, work_mode); + } else { + fs = File::OpenFile(fullpath, open_mode, work_mode); + free(fullpath); + } + + return fs; +#endif +} diff --git a/engines/ags/shared/util/misc.h b/engines/ags/shared/util/misc.h new file mode 100644 index 00000000000..8e5e5916c10 --- /dev/null +++ b/engines/ags/shared/util/misc.h @@ -0,0 +1,65 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +/* + Copyright (c) 2003, Shawn R. Walker + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Shawn R. Walker nor names of contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef __MISC_H +#define __MISC_H + +#include "util/file.h" // TODO: extract filestream mode constants + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +// Case-insensitive file lookup functions. On case-insensitive systems +// (like MS Windows) they simply return given file, but on case-sensitive +// systems (like Linux) they search the directory for files with matching +// names in different character case. +// They are used as a system-independent way to open a file when its name +// case can be ignored. +Common::Stream *ci_fopen(const char *file_name, + Common::FileOpenMode open_mode = Common::kFile_Open, + Common::FileWorkMode work_mode = Common::kFile_Read); +// TODO: return String object +char *ci_find_file(const char *dir_name, const char *file_name); + + +#endif // __MISC_H diff --git a/engines/ags/shared/util/multifilelib.h b/engines/ags/shared/util/multifilelib.h new file mode 100644 index 00000000000..a0e1192c04f --- /dev/null +++ b/engines/ags/shared/util/multifilelib.h @@ -0,0 +1,73 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// A packed data file header and functions for reading it from the stream. +// +//----------------------------------------------------------------------------- +// +// The code is based on CLIB32, by Chris Jones (1998-99), DJGPP implementation +// of the CLIB reader. +// +//============================================================================= + +#ifndef __AGS_CN_UTIL__MULTIFILELIB_H +#define __AGS_CN_UTIL__MULTIFILELIB_H + +#include "core/asset.h" +#include "util/stream.h" + +namespace AGS +{ +namespace Common +{ + +// +// MultiFileLib utilities: (de)serialization of asset library in MFL format +// +namespace MFLUtil +{ + enum MFLError + { + kMFLNoError = 0, + kMFLErrNoLibSig = -1, // library signature does not match + kMFLErrLibVersion = -2, // library version unsupported + kMFLErrNoLibBase = -3, // file is not library base (head) + kMFLErrLibAssetCount = -4, // too many assets in library + }; + + enum MFLVersion + { + kMFLVersion_SingleLib = 6, + kMFLVersion_MultiV10 = 10, + kMFLVersion_MultiV11 = 11, + kMFLVersion_MultiV15 = 15, // unknown differences + kMFLVersion_MultiV20 = 20, + kMFLVersion_MultiV21 = 21, + kMFLVersion_MultiV30 = 30 // 64-bit file support, loose limits + }; + + // Maximal number of the data files in one library chain (1-byte index) + const size_t MaxMultiLibFiles = 256; + + MFLError TestIsMFL(Stream *in, bool test_is_main = false); + MFLError ReadHeader(AssetLibInfo &lib, Stream *in); + + void WriteHeader(const AssetLibInfo &lib, MFLVersion lib_version, int lib_index, Stream *out); + void WriteEnder(soff_t lib_offset, MFLVersion lib_index, Stream *out); +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__MULTIFILELIB_H diff --git a/engines/ags/shared/util/mutifilelib.cpp b/engines/ags/shared/util/mutifilelib.cpp new file mode 100644 index 00000000000..d0154662dde --- /dev/null +++ b/engines/ags/shared/util/mutifilelib.cpp @@ -0,0 +1,466 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/bbop.h" +#include "util/multifilelib.h" +#include "util/stream.h" +#include "util/string_utils.h" + +namespace AGS +{ +namespace Common +{ + +namespace MFLUtil +{ + const String HeadSig = "CLIB\x1a"; + const String TailSig = "CLIB\x1\x2\x3\x4SIGE"; + + static const size_t SingleFilePswLen = 13; + + static const size_t MaxAssetFileLen = 100; + static const size_t MaxDataFileLen = 50; + static const size_t V10LibFileLen = 20; + static const size_t V10AssetFileLen = 25; + + static const int EncryptionRandSeed = 9338638; + static const String EncryptionString = "My\x1\xde\x4Jibzle"; + + MFLError ReadSigsAndVersion(Stream *in, MFLVersion *p_lib_version, soff_t *p_abs_offset); + MFLError ReadSingleFileLib(AssetLibInfo &lib, Stream *in, MFLVersion lib_version); + MFLError ReadMultiFileLib(AssetLibInfo &lib, Stream *in, MFLVersion lib_version); + MFLError ReadV10(AssetLibInfo &lib, Stream *in, MFLVersion lib_version); + MFLError ReadV20(AssetLibInfo &lib, Stream *in); + MFLError ReadV21(AssetLibInfo &lib, Stream *in); + MFLError ReadV30(AssetLibInfo &lib, Stream *in, MFLVersion lib_version); + + void WriteV30(const AssetLibInfo &lib, MFLVersion lib_version, Stream *out); + + // Encryption / decryption + int GetNextPseudoRand(int &rand_val); + void DecryptText(char *text); + void ReadEncArray(void *data, size_t size, size_t count, Stream *in, int &rand_val); + int8_t ReadEncInt8(Stream *in, int &rand_val); + int32_t ReadEncInt32(Stream *in, int &rand_val); + void ReadEncString(char *buffer, size_t max_len, Stream *in, int &rand_val); +}; + + +MFLUtil::MFLError MFLUtil::TestIsMFL(Stream *in, bool test_is_main) +{ + MFLVersion lib_version; + MFLError err = ReadSigsAndVersion(in, &lib_version, nullptr); + if (err == kMFLNoError) + { + if (lib_version >= kMFLVersion_MultiV10 && test_is_main) + { + // this version supports multiple data files, check if it is the first one + if (in->ReadByte() != 0) + return kMFLErrNoLibBase; // not first datafile in chain + } + } + return err; +} + +MFLUtil::MFLError MFLUtil::ReadHeader(AssetLibInfo &lib, Stream *in) +{ + MFLVersion lib_version; + soff_t abs_offset; + MFLError err = ReadSigsAndVersion(in, &lib_version, &abs_offset); + if (err != kMFLNoError) + return err; + + if (lib_version >= kMFLVersion_MultiV10) + { + // read newer clib versions (versions 10+) + err = ReadMultiFileLib(lib, in, lib_version); + } + else + { + // read older clib versions (versions 1 to 9) + err = ReadSingleFileLib(lib, in, lib_version); + } + + // apply absolute offset for the assets contained in base data file + // (since only base data file may be EXE file, other clib parts are always on their own) + if (abs_offset > 0) + { + for (AssetVec::iterator it = lib.AssetInfos.begin(); + it != lib.AssetInfos.end(); ++it) + { + if (it->LibUid == 0) + it->Offset += abs_offset; + } + } + return err; +} + +MFLUtil::MFLError MFLUtil::ReadSigsAndVersion(Stream *in, MFLVersion *p_lib_version, soff_t *p_abs_offset) +{ + soff_t abs_offset = 0; // library offset in this file + String sig; + // check multifile lib signature at the beginning of file + sig.ReadCount(in, HeadSig.GetLength()); + if (HeadSig.Compare(sig) != 0) + { + // signature not found, check signature at the end of file + in->Seek(-(soff_t)TailSig.GetLength(), kSeekEnd); + // by definition, tail marks the max absolute offset value + auto tail_abs_offset = in->GetPosition(); + sig.ReadCount(in, TailSig.GetLength()); + // signature not found, return error code + if (TailSig.Compare(sig) != 0) + return kMFLErrNoLibSig; + + // it's an appended-to-end-of-exe thing; + // now we need to read multifile lib offset value, but we do not know + // if its 32-bit or 64-bit yet, so we'll have to test both + in->Seek(-(soff_t)TailSig.GetLength() - sizeof(int64_t), kSeekEnd); + abs_offset = in->ReadInt64(); + in->Seek(-(soff_t)sizeof(int32_t), kSeekCurrent); + soff_t abs_offset_32 = in->ReadInt32(); + + // test for header signature again, with 64-bit and 32-bit offsets if necessary + if (abs_offset > 0 && abs_offset < (tail_abs_offset - HeadSig.GetLength())) + { + in->Seek(abs_offset, kSeekBegin); + sig.ReadCount(in, HeadSig.GetLength()); + } + + // try again with 32-bit offset + if (HeadSig.Compare(sig) != 0) + { + abs_offset = abs_offset_32; + if (abs_offset > 0 && abs_offset < (tail_abs_offset - HeadSig.GetLength())) + { + in->Seek(abs_offset, kSeekBegin); + sig.ReadCount(in, HeadSig.GetLength()); + } + if (HeadSig.Compare(sig) != 0) + { + // nope, no luck, bad / unknown format + return kMFLErrNoLibSig; + } + } + } + // if we've reached this point we must be right behind the header signature + + // read library header + MFLVersion lib_version = (MFLVersion)in->ReadByte(); + if ((lib_version != kMFLVersion_SingleLib) && (lib_version != kMFLVersion_MultiV10) && + (lib_version != kMFLVersion_MultiV11) && (lib_version != kMFLVersion_MultiV15) && + (lib_version != kMFLVersion_MultiV20) && (lib_version != kMFLVersion_MultiV21) && + lib_version != kMFLVersion_MultiV30) + return kMFLErrLibVersion; // unsupported version + + if (p_lib_version) + *p_lib_version = lib_version; + if (p_abs_offset) + *p_abs_offset = abs_offset; + return kMFLNoError; +} + +MFLUtil::MFLError MFLUtil::ReadSingleFileLib(AssetLibInfo &lib, Stream *in, MFLVersion lib_version) +{ + int passwmodifier = in->ReadByte(); + in->ReadInt8(); // unused byte + lib.LibFileNames.resize(1); // only one library part + size_t asset_count = in->ReadInt16(); + lib.AssetInfos.resize(asset_count); + + in->Seek(SingleFilePswLen, kSeekCurrent); // skip password dooberry + char fn_buf[SingleFilePswLen + 1]; + // read information on contents + for (size_t i = 0; i < asset_count; ++i) + { + in->Read(fn_buf, SingleFilePswLen); + fn_buf[SingleFilePswLen] = 0; + for (char *c = fn_buf; *c; ++c) + *c -= passwmodifier; + lib.AssetInfos[i].FileName = fn_buf; + lib.AssetInfos[i].LibUid = 0; + } + for (size_t i = 0; i < asset_count; ++i) + { + lib.AssetInfos[i].Size = in->ReadInt32(); + } + in->Seek(2 * asset_count, kSeekCurrent); // skip flags & ratio + lib.AssetInfos[0].Offset = in->GetPosition(); + // set offsets (assets are positioned in sequence) + for (size_t i = 1; i < asset_count; ++i) + { + lib.AssetInfos[i].Offset = lib.AssetInfos[i - 1].Offset + lib.AssetInfos[i - 1].Size; + } + // return success + return kMFLNoError; +} + +MFLUtil::MFLError MFLUtil::ReadMultiFileLib(AssetLibInfo &lib, Stream *in, MFLVersion lib_version) +{ + if (in->ReadByte() != 0) + return kMFLErrNoLibBase; // not first datafile in chain + + if (lib_version >= kMFLVersion_MultiV30) + { + // read new clib format with 64-bit files support + return ReadV30(lib, in, lib_version); + } + if (lib_version >= kMFLVersion_MultiV21) + { + // read new clib format with encoding support (versions 21+) + return ReadV21(lib, in); + } + else if (lib_version == kMFLVersion_MultiV20) + { + // read new clib format without encoding support (version 20) + return ReadV20(lib, in); + } + // read older clib format (versions 10 to 19), the ones with shorter filenames + return ReadV10(lib, in, lib_version); +} + +MFLUtil::MFLError MFLUtil::ReadV10(AssetLibInfo &lib, Stream *in, MFLVersion lib_version) +{ + // number of clib parts + size_t mf_count = in->ReadInt32(); + lib.LibFileNames.resize(mf_count); + // filenames for all clib parts; filenames are only 20 chars long in this format version + for (size_t i = 0; i < mf_count; ++i) + { + lib.LibFileNames[i].ReadCount(in, V10LibFileLen); + } + + // number of files in clib + size_t asset_count = in->ReadInt32(); + // read information on clib contents + lib.AssetInfos.resize(asset_count); + // filename array is only 25 chars long in this format version + char fn_buf[V10AssetFileLen]; + for (size_t i = 0; i < asset_count; ++i) + { + in->Read(fn_buf, V10AssetFileLen); + if (lib_version >= kMFLVersion_MultiV11) + DecryptText(fn_buf); + lib.AssetInfos[i].FileName = fn_buf; + } + for (size_t i = 0; i < asset_count; ++i) + lib.AssetInfos[i].Offset = in->ReadInt32(); + for (size_t i = 0; i < asset_count; ++i) + lib.AssetInfos[i].Size = in->ReadInt32(); + for (size_t i = 0; i < asset_count; ++i) + lib.AssetInfos[i].LibUid = in->ReadInt8(); + return kMFLNoError; +} + +MFLUtil::MFLError MFLUtil::ReadV20(AssetLibInfo &lib, Stream *in) +{ + // number of clib parts + size_t mf_count = in->ReadInt32(); + lib.LibFileNames.resize(mf_count); + // filenames for all clib parts + for (size_t i = 0; i < mf_count; ++i) + { + lib.LibFileNames[i].Read(in, MaxDataFileLen); + } + + // number of files in clib + size_t asset_count = in->ReadInt32(); + // read information on clib contents + lib.AssetInfos.resize(asset_count); + char fn_buf[MaxAssetFileLen]; + for (size_t i = 0; i < asset_count; ++i) + { + short len = in->ReadInt16(); + len /= 5; // CHECKME: why 5? + in->Read(fn_buf, len); + // decrypt filenames + DecryptText(fn_buf); + lib.AssetInfos[i].FileName = fn_buf; + } + for (size_t i = 0; i < asset_count; ++i) + lib.AssetInfos[i].Offset = in->ReadInt32(); + for (size_t i = 0; i < asset_count; ++i) + lib.AssetInfos[i].Size = in->ReadInt32(); + for (size_t i = 0; i < asset_count; ++i) + lib.AssetInfos[i].LibUid = in->ReadInt8(); + return kMFLNoError; +} + +MFLUtil::MFLError MFLUtil::ReadV21(AssetLibInfo &lib, Stream *in) +{ + // init randomizer + int rand_val = in->ReadInt32() + EncryptionRandSeed; + // number of clib parts + size_t mf_count = ReadEncInt32(in, rand_val); + lib.LibFileNames.resize(mf_count); + // filenames for all clib parts + char fn_buf[MaxDataFileLen > MaxAssetFileLen ? MaxDataFileLen : MaxAssetFileLen]; + for (size_t i = 0; i < mf_count; ++i) + { + ReadEncString(fn_buf, MaxDataFileLen, in, rand_val); + lib.LibFileNames[i] = fn_buf; + } + + // number of files in clib + size_t asset_count = ReadEncInt32(in, rand_val); + // read information on clib contents + lib.AssetInfos.resize(asset_count); + for (size_t i = 0; i < asset_count; ++i) + { + ReadEncString(fn_buf, MaxAssetFileLen, in, rand_val); + lib.AssetInfos[i].FileName = fn_buf; + } + for (size_t i = 0; i < asset_count; ++i) + lib.AssetInfos[i].Offset = ReadEncInt32(in, rand_val); + for (size_t i = 0; i < asset_count; ++i) + lib.AssetInfos[i].Size = ReadEncInt32(in, rand_val); + for (size_t i = 0; i < asset_count; ++i) + lib.AssetInfos[i].LibUid = ReadEncInt8(in, rand_val); + return kMFLNoError; +} + +MFLUtil::MFLError MFLUtil::ReadV30(AssetLibInfo &lib, Stream *in, MFLVersion /* lib_version */) +{ + // NOTE: removed encryption like in v21, because it makes little sense + // with open-source program. But if really wanted it may be restored + // as one of the options here. + /* int flags = */ in->ReadInt32(); // reserved options + // number of clib parts + size_t mf_count = in->ReadInt32(); + lib.LibFileNames.resize(mf_count); + // filenames for all clib parts + for (size_t i = 0; i < mf_count; ++i) + lib.LibFileNames[i] = String::FromStream(in); + + // number of files in clib + size_t asset_count = in->ReadInt32(); + // read information on clib contents + lib.AssetInfos.resize(asset_count); + for (AssetVec::iterator it = lib.AssetInfos.begin(); it != lib.AssetInfos.end(); ++it) + { + it->FileName = String::FromStream(in); + it->LibUid = in->ReadInt8(); + it->Offset = in->ReadInt64(); + it->Size = in->ReadInt64(); + } + return kMFLNoError; +} + +void MFLUtil::WriteHeader(const AssetLibInfo &lib, MFLVersion lib_version, int lib_index, Stream *out) +{ + out->Write(MFLUtil::HeadSig, MFLUtil::HeadSig.GetLength()); + out->WriteByte(lib_version); + out->WriteByte(lib_index); // file number + + // First datafile in chain: write the table of contents + if (lib_index == 0) + { + WriteV30(lib, lib_version, out); + } +} + +void MFLUtil::WriteV30(const AssetLibInfo &lib, MFLVersion lib_version, Stream *out) +{ + out->WriteInt32(0); // reserved options + // filenames for all library parts + out->WriteInt32(lib.LibFileNames.size()); + for (size_t i = 0; i < lib.LibFileNames.size(); ++i) + { + StrUtil::WriteCStr(lib.LibFileNames[i], out); + } + + // table of contents for all assets in library + out->WriteInt32(lib.AssetInfos.size()); + for (AssetVec::const_iterator it = lib.AssetInfos.begin(); it != lib.AssetInfos.end(); ++it) + { + StrUtil::WriteCStr(it->FileName, out); + out->WriteInt8(it->LibUid); + out->WriteInt64(it->Offset); + out->WriteInt64(it->Size); + } +} + +void MFLUtil::WriteEnder(soff_t lib_offset, MFLVersion lib_index, Stream *out) +{ + if (lib_index < kMFLVersion_MultiV30) + out->WriteInt32((int32_t)lib_offset); + else + out->WriteInt64(lib_offset); + out->Write(TailSig, TailSig.GetLength()); +} + +void MFLUtil::DecryptText(char *text) +{ + size_t adx = 0; + while (true) + { + text[0] -= EncryptionString[adx]; + if (text[0] == 0) + break; + + adx++; + text++; + + if (adx > 10) // CHECKME: why 10? + adx = 0; + } +} + +int MFLUtil::GetNextPseudoRand(int &rand_val) +{ + return( ((rand_val = rand_val * 214013L + + 2531011L) >> 16) & 0x7fff ); +} + +void MFLUtil::ReadEncArray(void *data, size_t size, size_t count, Stream *in, int &rand_val) +{ + in->ReadArray(data, size, count); + uint8_t *ch = (uint8_t*)data; + const size_t len = size * count; + for (size_t i = 0; i < len; ++i) + { + ch[i] -= GetNextPseudoRand(rand_val); + } +} + +int8_t MFLUtil::ReadEncInt8(Stream *in, int &rand_val) +{ + return in->ReadByte() - GetNextPseudoRand(rand_val); +} + +int32_t MFLUtil::ReadEncInt32(Stream *in, int &rand_val) +{ + int val; + ReadEncArray(&val, sizeof(int32_t), 1, in, rand_val); +#if AGS_PLATFORM_ENDIAN_BIG + AGS::Common::BitByteOperations::SwapBytesInt32(val); +#endif + return val; +} + +void MFLUtil::ReadEncString(char *buffer, size_t max_len, Stream *in, int &rand_val) +{ + size_t i = 0; + while ((i == 0) || (buffer[i - 1] != 0)) + { + buffer[i] = in->ReadByte() - GetNextPseudoRand(rand_val); + if (i < max_len - 1) + i++; + else + break; // avoid an endless loop + } +} + +} // namespace AGS +} // namespace Common diff --git a/engines/ags/shared/util/path.cpp b/engines/ags/shared/util/path.cpp new file mode 100644 index 00000000000..88e305f05f6 --- /dev/null +++ b/engines/ags/shared/util/path.cpp @@ -0,0 +1,250 @@ + +#include "core/platform.h" +#if AGS_PLATFORM_OS_WINDOWS +#include +#endif +#include "allegro/file.h" +#include "util/path.h" +#include "util/stdio_compat.h" + +// TODO: implement proper portable path length +#ifndef MAX_PATH +#define MAX_PATH 512 +#endif + +namespace AGS +{ +namespace Common +{ + +namespace Path +{ + +bool IsDirectory(const String &filename) +{ + // stat() does not like trailing slashes, remove them + String fixed_path = MakePathNoSlash(filename); + return ags_directory_exists(fixed_path.GetCStr()) != 0; +} + +bool IsFile(const String &filename) +{ + return ags_file_exists(filename.GetCStr()) != 0; +} + +bool IsFileOrDir(const String &filename) +{ + // stat() does not like trailing slashes, remove them + String fixed_path = MakePathNoSlash(filename); + return ags_path_exists(fixed_path.GetCStr()) != 0; +} + +int ComparePaths(const String &path1, const String &path2) +{ + // Make minimal absolute paths + String fixed_path1 = MakeAbsolutePath(path1); + String fixed_path2 = MakeAbsolutePath(path2); + + fixed_path1.TrimRight('/'); + fixed_path2.TrimRight('/'); + + int cmp_result = +#if defined AGS_CASE_SENSITIVE_FILESYSTEM + fixed_path1.Compare(fixed_path2); +#else + fixed_path1.CompareNoCase(fixed_path2); +#endif // AGS_CASE_SENSITIVE_FILESYSTEM + return cmp_result; +} + +String GetDirectoryPath(const String &path) +{ + if (IsDirectory(path)) + return path; + + String dir = path; + FixupPath(dir); + size_t slash_at = dir.FindCharReverse('/'); + if (slash_at != -1) + { + dir.ClipMid(slash_at + 1); + return dir; + } + return "./"; +} + +bool IsSameOrSubDir(const String &parent, const String &path) +{ + char can_parent[MAX_PATH]; + char can_path[MAX_PATH]; + char relative[MAX_PATH]; + // canonicalize_filename treats "." as "./." (file in working dir) + const char *use_parent = parent == "." ? "./" : parent; + const char *use_path = path == "." ? "./" : path; + canonicalize_filename(can_parent, use_parent, MAX_PATH); + canonicalize_filename(can_path, use_path, MAX_PATH); + const char *pstr = make_relative_filename(relative, can_parent, can_path, MAX_PATH); + if (!pstr) + return false; + for (pstr = strstr(pstr, ".."); pstr && *pstr; pstr = strstr(pstr, "..")) + { + pstr += 2; + if (*pstr == '/' || *pstr == '\\' || *pstr == 0) + return false; + } + return true; +} + +void FixupPath(String &path) +{ +#if AGS_PLATFORM_OS_WINDOWS + path.Replace('\\', '/'); // bring Windows path separators to uniform style +#endif +} + +String MakePathNoSlash(const String &path) +{ + String dir_path = path; + FixupPath(dir_path); +#if AGS_PLATFORM_OS_WINDOWS + // if the path is 'x:/' don't strip the slash + if (path.GetLength() == 3 && path[1u] == ':') + ; + else +#endif + // if the path is '/' don't strip the slash + if (dir_path.GetLength() > 1) + dir_path.TrimRight('/'); + return dir_path; +} + +String MakeTrailingSlash(const String &path) +{ + String dir_path = path; + FixupPath(dir_path); + if (dir_path.GetLast() != '/') + dir_path.AppendChar('/'); + return dir_path; +} + +String MakeAbsolutePath(const String &path) +{ + if (path.IsEmpty()) + { + return ""; + } + // canonicalize_filename treats "." as "./." (file in working dir) + String abs_path = path == "." ? "./" : path; +#if AGS_PLATFORM_OS_WINDOWS + // NOTE: cannot use long path names in the engine, because it does not have unicode strings support + // + //char long_path_buffer[MAX_PATH]; + //if (GetLongPathNameA(path, long_path_buffer, MAX_PATH) > 0) + //{ + // abs_path = long_path_buffer; + //} +#endif + char buf[MAX_PATH]; + canonicalize_filename(buf, abs_path, MAX_PATH); + abs_path = buf; + FixupPath(abs_path); + return abs_path; +} + +String MakeRelativePath(const String &base, const String &path) +{ + char can_parent[MAX_PATH]; + char can_path[MAX_PATH]; + char relative[MAX_PATH]; + // canonicalize_filename treats "." as "./." (file in working dir) + const char *use_parent = base == "." ? "./" : base; + const char *use_path = path == "." ? "./" : path; + canonicalize_filename(can_parent, use_parent, MAX_PATH); + canonicalize_filename(can_path, use_path, MAX_PATH); + String rel_path = make_relative_filename(relative, can_parent, can_path, MAX_PATH); + FixupPath(rel_path); + return rel_path; +} + +String ConcatPaths(const String &parent, const String &child) +{ + String path = parent; + FixupPath(path); + if (path.GetLast() != '/') + path.AppendChar('/'); + path.Append(child); + return path; +} + +String FixupSharedFilename(const String &filename) +{ + const char *illegal_chars = "\\/:?\"<>|*"; + String fixed_name = filename; + for (size_t i = 0; i < filename.GetLength(); ++i) + { + if (filename[i] < ' ') + { + fixed_name.SetAt(i, '_'); + } + else + { + for (const char *ch_ptr = illegal_chars; *ch_ptr; ++ch_ptr) + if (filename[i] == *ch_ptr) + fixed_name.SetAt(i, '_'); + } + } + return fixed_name; +} + +String GetPathInASCII(const String &path) +{ +#if AGS_PLATFORM_OS_WINDOWS + char ascii_buffer[MAX_PATH]; + if (GetShortPathNameA(path, ascii_buffer, MAX_PATH) == 0) + return ""; + return ascii_buffer; +#else + // TODO: implement conversion for other platforms! + return path; +#endif +} + +#if AGS_PLATFORM_OS_WINDOWS +String WidePathNameToAnsi(LPCWSTR pathw) +{ + WCHAR short_path[MAX_PATH]; + char ascii_buffer[MAX_PATH]; + LPCWSTR arg_path = pathw; + if (GetShortPathNameW(arg_path, short_path, MAX_PATH) == 0) + return ""; + WideCharToMultiByte(CP_ACP, 0, short_path, -1, ascii_buffer, MAX_PATH, NULL, NULL); + return ascii_buffer; +} +#endif + +String GetCmdLinePathInASCII(const char *arg, int arg_index) +{ +#if AGS_PLATFORM_OS_WINDOWS + // Hack for Windows in case there are unicode chars in the path. + // The normal argv[] array has ????? instead of the unicode chars + // and fails, so instead we manually get the short file name, which + // is always using ASCII chars. + int wargc = 0; + LPWSTR *wargv = CommandLineToArgvW(GetCommandLineW(), &wargc); + if (wargv == nullptr) + return ""; + String path; + if (arg_index <= wargc) + path = WidePathNameToAnsi(wargv[arg_index]); + LocalFree(wargv); + return path; +#else + // TODO: implement conversion for other platforms! + return arg; +#endif +} + +} // namespace Path + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/path.h b/engines/ags/shared/util/path.h new file mode 100644 index 00000000000..edcb82d1da9 --- /dev/null +++ b/engines/ags/shared/util/path.h @@ -0,0 +1,82 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Platform-independent Path functions +// +//============================================================================= +#ifndef __AGS_CN_UTIL__PATH_H +#define __AGS_CN_UTIL__PATH_H + +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +namespace Path +{ + // Tells if the given path is a directory + bool IsDirectory(const String &directory); + // Tells if the given path is a file + bool IsFile(const String &filename); + // Tells if the given path is file or directory; + // may be used to check if it's valid to use + bool IsFileOrDir(const String &filename); + + // Makes a platform-dependant path comparison. + // This takes into consideration platform's filename case (in)sensivity and + // DOS-compatible 8.3 filenames; + // The result value corresponds to stdlib strcmp function. + int ComparePaths(const String &path1, const String &path2); + + // Returns path to the actual directory, referenced by given path; + // if path is a directory, returns path unchanged, if path is a file, returns + // parent directory containing that file. + String GetDirectoryPath(const String &path); + // Tells if the path points to the parent path's location or lower directory; + // return FALSE if the path points to outside of the parent location. + bool IsSameOrSubDir(const String &parent, const String &path); + + // Makes a path have only '/' slashes; this is to make it easier to work + // with path, knowing it contains only one type of directory separators + void FixupPath(String &path); + // Fixups path and removes trailing slash + String MakePathNoSlash(const String &path); + // Fixups path and adds trailing slash if it's missing + String MakeTrailingSlash(const String &path); + // Converts any path to an absolute path; relative paths are assumed to + // refer to the current working directory. + String MakeAbsolutePath(const String &path); + // Tries to create a relative path that would point to 'path' location + // if walking out of the 'base'. Returns empty string on failure. + String MakeRelativePath(const String &base, const String &path); + // Concatenates parent and relative paths + String ConcatPaths(const String &parent, const String &child); + + // Subsitutes illegal characters with '_'. This function uses a combined set + // of illegal chars from all the supported platforms to make a name that + // could be copied across systems without problems. + String FixupSharedFilename(const String &filename); + + // Converts filepath into ASCII variant; returns empty string on failure + String GetPathInASCII(const String &path); + // Converts filepath from command line's argument into ASCII variant + String GetCmdLinePathInASCII(const char *arg, int arg_index); +} // namespace Path + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__PATH_H diff --git a/engines/ags/shared/util/proxystream.cpp b/engines/ags/shared/util/proxystream.cpp new file mode 100644 index 00000000000..164ca737ebb --- /dev/null +++ b/engines/ags/shared/util/proxystream.cpp @@ -0,0 +1,182 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/proxystream.h" + +namespace AGS +{ +namespace Common +{ + +ProxyStream::ProxyStream(Stream *stream, ObjectOwnershipPolicy stream_ownership_policy) + : _stream(stream) + , _streamOwnershipPolicy(stream_ownership_policy) +{ +} + +ProxyStream::~ProxyStream() +{ + ProxyStream::Close(); +} + +void ProxyStream::Close() +{ + if (_stream && _streamOwnershipPolicy == kDisposeAfterUse) + { + delete _stream; + } + _stream = nullptr; +} + +bool ProxyStream::Flush() +{ + if (_stream) + { + return _stream->Flush(); + } + return false; +} + +bool ProxyStream::IsValid() const +{ + return _stream && _stream->IsValid(); +} + +bool ProxyStream::EOS() const +{ + return _stream ? _stream->EOS() : true; +} + +soff_t ProxyStream::GetLength() const +{ + return _stream ? _stream->GetLength() : 0; +} + +soff_t ProxyStream::GetPosition() const +{ + return _stream ? _stream->GetPosition() : -1; +} + +bool ProxyStream::CanRead() const +{ + return _stream ? _stream->CanRead() : false; +} + +bool ProxyStream::CanWrite() const +{ + return _stream ? _stream->CanWrite() : false; +} + +bool ProxyStream::CanSeek() const +{ + return _stream ? _stream->CanSeek() : false; +} + +size_t ProxyStream::Read(void *buffer, size_t size) +{ + return _stream ? _stream->Read(buffer, size) : 0; +} + +int32_t ProxyStream::ReadByte() +{ + return _stream ? _stream->ReadByte() : 0; +} + +int16_t ProxyStream::ReadInt16() +{ + return _stream ? _stream->ReadInt16() : 0; +} + +int32_t ProxyStream::ReadInt32() +{ + return _stream ? _stream->ReadInt32() : 0; +} + +int64_t ProxyStream::ReadInt64() +{ + return _stream ? _stream->ReadInt64() : 0; +} + +size_t ProxyStream::ReadArray(void *buffer, size_t elem_size, size_t count) +{ + return _stream ? _stream->ReadArray(buffer, elem_size, count) : 0; +} + +size_t ProxyStream::ReadArrayOfInt16(int16_t *buffer, size_t count) +{ + return _stream ? _stream->ReadArrayOfInt16(buffer, count) : 0; +} + +size_t ProxyStream::ReadArrayOfInt32(int32_t *buffer, size_t count) +{ + return _stream ? _stream->ReadArrayOfInt32(buffer, count) : 0; +} + +size_t ProxyStream::ReadArrayOfInt64(int64_t *buffer, size_t count) +{ + return _stream ? _stream->ReadArrayOfInt64(buffer, count) : 0; +} + +size_t ProxyStream::Write(const void *buffer, size_t size) +{ + return _stream ? _stream->Write(buffer, size) : 0; +} + +int32_t ProxyStream::WriteByte(uint8_t b) +{ + return _stream ? _stream->WriteByte(b) : 0; +} + +size_t ProxyStream::WriteInt16(int16_t val) +{ + return _stream ? _stream->WriteInt16(val) : 0; +} + +size_t ProxyStream::WriteInt32(int32_t val) +{ + return _stream ? _stream->WriteInt32(val) : 0; +} + +size_t ProxyStream::WriteInt64(int64_t val) +{ + return _stream ? _stream->WriteInt64(val) : 0; +} + +size_t ProxyStream::WriteArray(const void *buffer, size_t elem_size, size_t count) +{ + return _stream ? _stream->WriteArray(buffer, elem_size, count) : 0; +} + +size_t ProxyStream::WriteArrayOfInt16(const int16_t *buffer, size_t count) +{ + return _stream ? _stream->WriteArrayOfInt16(buffer, count) : 0; +} + +size_t ProxyStream::WriteArrayOfInt32(const int32_t *buffer, size_t count) +{ + return _stream ? _stream->WriteArrayOfInt32(buffer, count) : 0; +} + +size_t ProxyStream::WriteArrayOfInt64(const int64_t *buffer, size_t count) +{ + return _stream ? _stream->WriteArrayOfInt64(buffer, count) : 0; +} + +bool ProxyStream::Seek(soff_t offset, StreamSeek origin) +{ + return _stream ? _stream->Seek(offset, origin) : false; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/proxystream.h b/engines/ags/shared/util/proxystream.h new file mode 100644 index 00000000000..fa09ed627d5 --- /dev/null +++ b/engines/ags/shared/util/proxystream.h @@ -0,0 +1,87 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_UTIL__PROXYSTREAM_H +#define __AGS_CN_UTIL__PROXYSTREAM_H + +#include "util/stream.h" + +namespace AGS +{ +namespace Common +{ + +// TODO: replace with std::shared_ptr!!! +enum ObjectOwnershipPolicy +{ + kReleaseAfterUse, + kDisposeAfterUse +}; + +class ProxyStream : public Stream +{ +public: + ProxyStream(Stream *stream, ObjectOwnershipPolicy stream_ownership_policy = kReleaseAfterUse); + ~ProxyStream() override; + + void Close() override; + bool Flush() override; + + // Is stream valid (underlying data initialized properly) + bool IsValid() const override; + // Is end of stream + bool EOS() const override; + // Total length of stream (if known) + soff_t GetLength() const override; + // Current position (if known) + soff_t GetPosition() const override; + + bool CanRead() const override; + bool CanWrite() const override; + bool CanSeek() const override; + + size_t Read(void *buffer, size_t size) override; + int32_t ReadByte() override; + int16_t ReadInt16() override; + int32_t ReadInt32() override; + int64_t ReadInt64() override; + size_t ReadArray(void *buffer, size_t elem_size, size_t count) override; + size_t ReadArrayOfInt16(int16_t *buffer, size_t count) override; + size_t ReadArrayOfInt32(int32_t *buffer, size_t count) override; + size_t ReadArrayOfInt64(int64_t *buffer, size_t count) override; + + size_t Write(const void *buffer, size_t size) override; + int32_t WriteByte(uint8_t b) override; + size_t WriteInt16(int16_t val) override; + size_t WriteInt32(int32_t val) override; + size_t WriteInt64(int64_t val) override; + size_t WriteArray(const void *buffer, size_t elem_size, size_t count) override; + size_t WriteArrayOfInt16(const int16_t *buffer, size_t count) override; + size_t WriteArrayOfInt32(const int32_t *buffer, size_t count) override; + size_t WriteArrayOfInt64(const int64_t *buffer, size_t count) override; + + bool Seek(soff_t offset, StreamSeek origin) override; + +protected: + Stream *_stream; + ObjectOwnershipPolicy _streamOwnershipPolicy; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__PROXYSTREAM_H diff --git a/engines/ags/shared/util/stdio_compat.c b/engines/ags/shared/util/stdio_compat.c new file mode 100644 index 00000000000..d3c7e25e3b7 --- /dev/null +++ b/engines/ags/shared/util/stdio_compat.c @@ -0,0 +1,95 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/stdio_compat.h" + +#include "core/platform.h" +#include +#include +#include + +#if AGS_PLATFORM_OS_WINDOWS +#include +#include +#endif + +int ags_fseek(FILE * stream, file_off_t offset, int whence) +{ +#if defined(HAVE_FSEEKO) // Contemporary POSIX libc + return fseeko(stream, offset, whence); +#elif AGS_PLATFORM_OS_WINDOWS // MSVC + return _fseeki64(stream, offset, whence); +#else // No distinct interface with off_t + return fseek(stream, offset, whence); +#endif +} + +file_off_t ags_ftell(FILE * stream) +{ + #if defined(HAVE_FSEEKO) // Contemporary POSIX libc + return ftello(stream); + #elif AGS_PLATFORM_OS_WINDOWS // MSVC + return _ftelli64(stream); + #else // No distinct interface with off_t + return ftell(stream); + #endif +} + +int ags_file_exists(const char *path) +{ +#if AGS_PLATFORM_OS_WINDOWS + return PathFileExistsA(path) && ! PathIsDirectoryA(path); +#else + struct stat path_stat; + if (stat(path, &path_stat) != 0) { + return 0; + } + return S_ISREG(path_stat.st_mode); +#endif +} + +int ags_directory_exists(const char *path) +{ +#if AGS_PLATFORM_OS_WINDOWS + return PathFileExistsA(path) && PathIsDirectoryA(path); +#else + struct stat path_stat; + if (stat(path, &path_stat) != 0) { + return 0; + } + return S_ISDIR(path_stat.st_mode); +#endif +} + +int ags_path_exists(const char *path) +{ + #if AGS_PLATFORM_OS_WINDOWS + return PathFileExistsA(path); + #else + struct stat path_stat; + if (stat(path, &path_stat) != 0) { + return 0; + } + return S_ISREG(path_stat.st_mode) || S_ISDIR(path_stat.st_mode); + #endif +} + +file_off_t ags_file_size(const char *path) +{ + struct stat path_stat; + if (stat(path, &path_stat) != 0) { + return -1; + } + return path_stat.st_size; +} diff --git a/engines/ags/shared/util/stdio_compat.h b/engines/ags/shared/util/stdio_compat.h new file mode 100644 index 00000000000..4ba211088b4 --- /dev/null +++ b/engines/ags/shared/util/stdio_compat.h @@ -0,0 +1,39 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#ifndef __AGS_CN_UTIL__STDIOCOMPAT_H +#define __AGS_CN_UTIL__STDIOCOMPAT_H + +#include +#include + +typedef int64_t file_off_t; + +#ifdef __cplusplus +extern "C" { +#endif + +int ags_fseek(FILE * stream, file_off_t offset, int whence); +file_off_t ags_ftell(FILE * stream); + +int ags_file_exists(const char *path); +int ags_directory_exists(const char *path); +int ags_path_exists(const char *path); +file_off_t ags_file_size(const char *path); + +#ifdef __cplusplus +} +#endif + +#endif // __AGS_CN_UTIL__STDIOCOMPAT_H diff --git a/engines/ags/shared/util/stream.cpp b/engines/ags/shared/util/stream.cpp new file mode 100644 index 00000000000..c991cdfff69 --- /dev/null +++ b/engines/ags/shared/util/stream.cpp @@ -0,0 +1,36 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/stream.h" + +namespace AGS +{ +namespace Common +{ + +size_t Stream::WriteByteCount(uint8_t b, size_t count) +{ + if (!CanWrite()) + return 0; + size_t size = 0; + for (; count > 0; --count, ++size) + { + if (WriteByte(b) < 0) + break; + } + return size; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/stream.h b/engines/ags/shared/util/stream.h new file mode 100644 index 00000000000..26ad9032188 --- /dev/null +++ b/engines/ags/shared/util/stream.h @@ -0,0 +1,84 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Base stream class. +// +// Provides default implementation for a few helper methods. +// +// Only streams with uncommon behavior should be derived directly from Stream. +// Most I/O devices should inherit DataStream instead. +// Streams that wrap other streams should inherit ProxyStream. +// +//============================================================================= +#ifndef __AGS_CN_UTIL__STREAM_H +#define __AGS_CN_UTIL__STREAM_H + +#include "api/stream_api.h" + +namespace AGS +{ +namespace Common +{ + +class Stream : public IAGSStream +{ +public: + // Tells if the stream has errors + virtual bool HasErrors() const { return false; } + // Flush stream buffer to the underlying device + virtual bool Flush() = 0; + + //----------------------------------------------------- + // Helper methods + //----------------------------------------------------- + inline int8_t ReadInt8() override + { + return ReadByte(); + } + + inline size_t WriteInt8(int8_t val) override + { + int32_t ival = WriteByte(val); + return ival >= 0 ? ival : 0; + } + + inline bool ReadBool() override + { + return ReadInt8() != 0; + } + + inline size_t WriteBool(bool val) override + { + return WriteInt8(val ? 1 : 0); + } + + // Practically identical to Read() and Write(), these two helpers' only + // meaning is to underline the purpose of data being (de)serialized + inline size_t ReadArrayOfInt8(int8_t *buffer, size_t count) override + { + return Read(buffer, count); + } + inline size_t WriteArrayOfInt8(const int8_t *buffer, size_t count) override + { + return Write(buffer, count); + } + + // Fill the requested number of bytes with particular value + size_t WriteByteCount(uint8_t b, size_t count); +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__STREAM_H diff --git a/engines/ags/shared/util/string.cpp b/engines/ags/shared/util/string.cpp new file mode 100644 index 00000000000..8f37fb2c5bf --- /dev/null +++ b/engines/ags/shared/util/string.cpp @@ -0,0 +1,1021 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include +#include +#include +#include "util/math.h" +#include "util/stream.h" +#include "util/string.h" +#include "util/string_compat.h" + +namespace AGS +{ +namespace Common +{ + +String::String() + : _cstr(nullptr) + , _len(0) + , _buf(nullptr) +{ +} + +String::String(const String &str) + : _cstr(nullptr) + , _len(0) + , _buf(nullptr) +{ + *this = str; +} + +String::String(const char *cstr) + : _cstr(nullptr) + , _len(0) + , _buf(nullptr) +{ + *this = cstr; +} + +String::String(const char *cstr, size_t length) + : _cstr(nullptr) + , _len(0) + , _buf(nullptr) +{ + SetString(cstr, length); +} + +String::String(char c, size_t count) + : _cstr(nullptr) + , _len(0) + , _buf(nullptr) +{ + FillString(c, count); +} + +String::~String() +{ + Free(); +} + +void String::Read(Stream *in, size_t max_chars, bool stop_at_limit) +{ + Empty(); + if (!in) + { + return; + } + if (max_chars == 0 && stop_at_limit) + { + return; + } + + char buffer[1024]; + char *read_ptr = buffer; + size_t read_size = 0; + int ichar; + do + { + ichar = in->ReadByte(); + read_size++; + if (read_size > max_chars) + { + continue; + } + *read_ptr = (char)(ichar >= 0 ? ichar : 0); + if (!*read_ptr || ((read_ptr - buffer) == (sizeof(buffer) - 1 - 1))) + { + buffer[sizeof(buffer) - 1] = 0; + Append(buffer); + read_ptr = buffer; + } + else + { + read_ptr++; + } + } + while(ichar > 0 && !(stop_at_limit && read_size == max_chars)); +} + +void String::ReadCount(Stream *in, size_t count) +{ + Empty(); + if (in && count > 0) + { + ReserveAndShift(false, count); + count = in->Read(_cstr, count); + _cstr[count] = 0; + _len = strlen(_cstr); + } +} + +void String::Write(Stream *out) const +{ + if (out) + { + out->Write(GetCStr(), GetLength() + 1); + } +} + +void String::WriteCount(Stream *out, size_t count) const +{ + if (out) + { + size_t str_out_len = Math::Min(count - 1, GetLength()); + if (str_out_len > 0) + out->Write(GetCStr(), str_out_len); + size_t null_out_len = count - str_out_len; + if (null_out_len > 0) + out->WriteByteCount(0, null_out_len); + } +} + +/* static */ void String::WriteString(const char *cstr, Stream *out) +{ + if (out) + { + cstr = cstr ? cstr : ""; + out->Write(cstr, strlen(cstr) + 1); + } +} + +int String::Compare(const char *cstr) const +{ + return strcmp(GetCStr(), cstr ? cstr : ""); +} + +int String::CompareNoCase(const char *cstr) const +{ + return ags_stricmp(GetCStr(), cstr ? cstr : ""); +} + +int String::CompareLeft(const char *cstr, size_t count) const +{ + cstr = cstr ? cstr : ""; + return strncmp(GetCStr(), cstr, count != -1 ? count : strlen(cstr)); +} + +int String::CompareLeftNoCase(const char *cstr, size_t count) const +{ + cstr = cstr ? cstr : ""; + return ags_strnicmp(GetCStr(), cstr, count != -1 ? count : strlen(cstr)); +} + +int String::CompareMid(const char *cstr, size_t from, size_t count) const +{ + cstr = cstr ? cstr : ""; + from = Math::Min(from, GetLength()); + return strncmp(GetCStr() + from, cstr, count != -1 ? count : strlen(cstr)); +} + +int String::CompareMidNoCase(const char *cstr, size_t from, size_t count) const +{ + cstr = cstr ? cstr : ""; + from = Math::Min(from, GetLength()); + return ags_strnicmp(GetCStr() + from, cstr, count != -1 ? count : strlen(cstr)); +} + +int String::CompareRight(const char *cstr, size_t count) const +{ + cstr = cstr ? cstr : ""; + count = count != -1 ? count : strlen(cstr); + size_t off = Math::Min(GetLength(), count); + return strncmp(GetCStr() + GetLength() - off, cstr, count); +} + +int String::CompareRightNoCase(const char *cstr, size_t count) const +{ + cstr = cstr ? cstr : ""; + count = count != -1 ? count : strlen(cstr); + size_t off = Math::Min(GetLength(), count); + return ags_strnicmp(GetCStr() + GetLength() - off, cstr, count); +} + +size_t String::FindChar(char c, size_t from) const +{ + if (c && from < _len) + { + const char * found_cstr = strchr(_cstr + from, c); + return found_cstr ? found_cstr - _cstr : -1; + } + return -1; +} + +size_t String::FindCharReverse(char c, size_t from) const +{ + if (!_cstr || !c) + { + return -1; + } + + from = Math::Min(from, _len - 1); + const char *seek_ptr = _cstr + from; + while (seek_ptr >= _cstr) + { + if (*seek_ptr == c) + { + return seek_ptr - _cstr; + } + seek_ptr--; + } + return -1; +} + +size_t String::FindString(const char *cstr, size_t from) const +{ + if (cstr && from < _len) + { + const char * found_cstr = strstr(_cstr + from, cstr); + return found_cstr ? found_cstr - _cstr : -1; + } + return -1; +} + +bool String::FindSection(char separator, size_t first, size_t last, bool exclude_first_sep, bool exclude_last_sep, + size_t &from, size_t &to) const +{ + if (!_cstr || !separator) + { + return false; + } + if (first > last) + { + return false; + } + + size_t this_field = 0; + size_t slice_from = 0; + size_t slice_to = _len; + size_t slice_at = -1; + do + { + slice_at = FindChar(separator, slice_at + 1); + if (slice_at == -1) + slice_at = _len; + // found where previous field ends + if (this_field == last) + { + // if previous field is the last one to be included, + // then set the section tail + slice_to = exclude_last_sep ? slice_at : slice_at + 1; + } + if (slice_at != _len) + { + this_field++; + if (this_field == first) + { + // if the new field is the first one to be included, + // then set the section head + slice_from = exclude_first_sep ? slice_at + 1 : slice_at; + } + } + } + while (slice_at < _len && this_field <= last); + + // the search is a success if at least the first field was found + if (this_field >= first) + { + // correct the indices to stay in the [0; length] range + assert(slice_from <= slice_to); + from = Math::Clamp(slice_from, (size_t)0, _len); + to = Math::Clamp(slice_to, (size_t)0, _len); + return true; + } + return false; +} + +int String::ToInt() const +{ + return atoi(GetCStr()); +} + +String String::Wrapper(const char *cstr) +{ + String str; + str.Wrap(cstr); + return str; +} + +/* static */ String String::FromFormat(const char *fcstr, ...) +{ + fcstr = fcstr ? fcstr : ""; + String str; + va_list argptr; + va_start(argptr, fcstr); + str.FormatV(fcstr, argptr); + va_end(argptr); + return str; +} + +/* static */ String String::FromFormatV(const char *fcstr, va_list argptr) +{ + String str; + str.FormatV(fcstr, argptr); + return str; +} + +/* static */ String String::FromStream(Stream *in, size_t max_chars, bool stop_at_limit) +{ + String str; + str.Read(in, max_chars, stop_at_limit); + return str; +} + +/* static */ String String::FromStreamCount(Stream *in, size_t count) +{ + String str; + str.ReadCount(in, count); + return str; +} + +String String::Lower() const +{ + String str = *this; + str.MakeLower(); + return str; +} + +String String::Upper() const +{ + String str = *this; + str.MakeUpper(); + return str; +} + +String String::Left(size_t count) const +{ + count = Math::Min(count, GetLength()); + return count == GetLength() ? *this : String(GetCStr(), count); +} + +String String::Mid(size_t from, size_t count) const +{ + Math::ClampLength(from, count, (size_t)0, GetLength()); + return count == GetLength() ? *this : String(GetCStr() + from, count); +} + +String String::Right(size_t count) const +{ + count = Math::Min(count, GetLength()); + return count == GetLength() ? *this : String(GetCStr() + GetLength() - count, count); +} + +String String::LeftSection(char separator, bool exclude_separator) const +{ + if (_cstr && separator) + { + size_t slice_at = FindChar(separator); + if (slice_at != -1) + { + slice_at = exclude_separator ? slice_at : slice_at + 1; + return Left(slice_at); + } + } + return *this; +} + +String String::RightSection(char separator, bool exclude_separator) const +{ + if (_cstr && separator) + { + size_t slice_at = FindCharReverse(separator); + if (slice_at != -1) + { + size_t count = exclude_separator ? _len - slice_at - 1 : _len - slice_at; + return Right(count); + } + } + return *this; +} + +String String::Section(char separator, size_t first, size_t last, + bool exclude_first_sep, bool exclude_last_sep) const +{ + if (!_cstr || !separator) + { + return String(); + } + + size_t slice_from; + size_t slice_to; + if (FindSection(separator, first, last, exclude_first_sep, exclude_last_sep, + slice_from, slice_to)) + { + return Mid(slice_from, slice_to - slice_from); + } + return String(); +} + +std::vector String::Split(char separator) const +{ + std::vector result; + if (!_cstr || !separator) + return result; + const char *ptr = _cstr; + while (*ptr) + { + const char *found_cstr = strchr(ptr, separator); + if (!found_cstr) break; + result.push_back(String(ptr, found_cstr - ptr)); + ptr = found_cstr + 1; + } + result.push_back(String(ptr)); + return result; +} + +void String::Reserve(size_t max_length) +{ + if (_bufHead) + { + if (max_length > _bufHead->Capacity) + { + // grow by 50% + size_t grow_length = _bufHead->Capacity + (_bufHead->Capacity / 2); + Copy(Math::Max(max_length, grow_length)); + } + } + else + { + Create(max_length); + } +} + +void String::ReserveMore(size_t more_length) +{ + Reserve(GetLength() + more_length); +} + +void String::Compact() +{ + if (_bufHead && _bufHead->Capacity > _len) + { + Copy(_len); + } +} + +void String::Append(const char *cstr) +{ + if (cstr) + { + size_t length = strlen(cstr); + if (length > 0) + { + ReserveAndShift(false, length); + memcpy(_cstr + _len, cstr, length); + _len += length; + _cstr[_len] = 0; + } + } +} + +void String::AppendChar(char c) +{ + if (c) + { + ReserveAndShift(false, 1); + _cstr[_len++] = c; + _cstr[_len] = 0; + } +} + +void String::ClipLeft(size_t count) +{ + if (_len > 0 && count > 0) + { + count = Math::Min(count, _len); + BecomeUnique(); + _len -= count; + _cstr += count; + } +} + +void String::ClipMid(size_t from, size_t count) +{ + if (from < _len) + { + count = Math::Min(count, _len - from); + if (count > 0) + { + BecomeUnique(); + if (!from) + { + _len -= count; + _cstr += count; + } + else if (from + count == _len) + { + _len -= count; + _cstr[_len] = 0; + } + else + { + char *cstr_mid = _cstr + from; + memmove(cstr_mid, _cstr + from + count, _len - from - count + 1); + _len -= count; + } + } + } +} + +void String::ClipRight(size_t count) +{ + if (count > 0) + { + count = Math::Min(count, GetLength()); + BecomeUnique(); + _len -= count; + _cstr[_len] = 0; + } +} + +void String::ClipLeftSection(char separator, bool include_separator) +{ + if (_cstr && separator) + { + size_t slice_at = FindChar(separator); + if (slice_at != -1) + { + ClipLeft(include_separator ? slice_at + 1 : slice_at); + } + else + Empty(); + } +} + +void String::ClipRightSection(char separator, bool include_separator) +{ + if (_cstr && separator) + { + size_t slice_at = FindCharReverse(separator); + if (slice_at != -1) + { + ClipRight(include_separator ? _len - slice_at : _len - slice_at - 1); + } + else + Empty(); + } +} + +void String::ClipSection(char separator, size_t first, size_t last, + bool include_first_sep, bool include_last_sep) +{ + if (!_cstr || !separator) + { + return; + } + + size_t slice_from; + size_t slice_to; + if (FindSection(separator, first, last, !include_first_sep, !include_last_sep, + slice_from, slice_to)) + { + ClipMid(slice_from, slice_to - slice_from); + } +} + +void String::Empty() +{ + if (_cstr) + { + BecomeUnique(); + _len = 0; + _cstr[0] = 0; + } +} + +void String::FillString(char c, size_t count) +{ + Empty(); + if (count > 0) + { + ReserveAndShift(false, count); + memset(_cstr, c, count); + _len = count; + _cstr[count] = 0; + } +} + +void String::Format(const char *fcstr, ...) +{ + va_list argptr; + va_start(argptr, fcstr); + FormatV(fcstr, argptr); + va_end(argptr); +} + +void String::FormatV(const char *fcstr, va_list argptr) +{ + fcstr = fcstr ? fcstr : ""; + va_list argptr_cpy; + va_copy(argptr_cpy, argptr); + size_t length = vsnprintf(nullptr, 0u, fcstr, argptr); + ReserveAndShift(false, Math::Surplus(length, GetLength())); + vsprintf(_cstr, fcstr, argptr_cpy); + va_end(argptr_cpy); + _len = length; + _cstr[_len] = 0; +} + +void String::Free() +{ + if (_bufHead) + { + assert(_bufHead->RefCount > 0); + _bufHead->RefCount--; + if (!_bufHead->RefCount) + { + delete [] _buf; + } + } + _buf = nullptr; + _cstr = nullptr; + _len = 0; +} + +void String::MakeLower() +{ + if (_cstr) + { + BecomeUnique(); + ags_strlwr(_cstr); + } +} + +void String::MakeUpper() +{ + if (_cstr) + { + BecomeUnique(); + ags_strupr(_cstr); + } +} + +void String::Prepend(const char *cstr) +{ + if (cstr) + { + size_t length = strlen(cstr); + if (length > 0) + { + ReserveAndShift(true, length); + memcpy(_cstr - length, cstr, length); + _len += length; + _cstr -= length; + } + } +} + +void String::PrependChar(char c) +{ + if (c) + { + ReserveAndShift(true, 1); + _len++; + _cstr--; + _cstr[0] = c; + } +} + +void String::Replace(char what, char with) +{ + if (_cstr && what && with && what != with) + { + BecomeUnique(); + char *rep_ptr = _cstr; + while (*rep_ptr) + { + if (*rep_ptr == what) + { + *rep_ptr = with; + } + rep_ptr++; + } + } +} + +void String::ReplaceMid(size_t from, size_t count, const char *cstr) +{ + if (!cstr) + cstr = ""; + size_t length = strlen(cstr); + Math::ClampLength(from, count, (size_t)0, GetLength()); + ReserveAndShift(false, Math::Surplus(length, count)); + memmove(_cstr + from + length, _cstr + from + count, GetLength() - (from + count) + 1); + memcpy(_cstr + from, cstr, length); + _len += length - count; +} + +void String::Reverse() +{ + if (!_cstr || GetLength() <= 1) + return; + for (char *fw = _cstr, *bw = _cstr + _len - 1; + *fw; ++fw, --bw) + { + std::swap(*fw, *bw); + } +} + +void String::SetAt(size_t index, char c) +{ + if (_cstr && index < GetLength() && c) + { + BecomeUnique(); + _cstr[index] = c; + } +} + +void String::SetString(const char *cstr, size_t length) +{ + if (cstr) + { + length = Math::Min(length, strlen(cstr)); + if (length > 0) + { + ReserveAndShift(false, Math::Surplus(length, GetLength())); + memcpy(_cstr, cstr, length); + _len = length; + _cstr[length] = 0; + } + else + { + Empty(); + } + } + else + { + Empty(); + } +} + +void String::Trim(char c) +{ + TrimLeft(c); + TrimRight(c); +} + +void String::TrimLeft(char c) +{ + if (!_cstr || !_len) + { + return; + } + + const char *trim_ptr = _cstr; + for (;;) + { + auto t = *trim_ptr; + if (t == 0) { break; } + if (c && t != c) { break; } + if (!c && !isspace(t)) { break; } + trim_ptr++; + } + size_t trimmed = trim_ptr - _cstr; + if (trimmed > 0) + { + BecomeUnique(); + _len -= trimmed; + _cstr += trimmed; + } +} + +void String::TrimRight(char c) +{ + if (!_cstr || !_len) + { + return; + } + + const char *trim_ptr = _cstr + _len - 1; + for (;;) + { + if (trim_ptr < _cstr) { break; } + auto t = *trim_ptr; + if (c && t != c) { break; } + if (!c && !isspace(t)) { break; } + trim_ptr--; + } + size_t trimmed = (_cstr + _len - 1) - trim_ptr; + if (trimmed > 0) + { + BecomeUnique(); + _len -= trimmed; + _cstr[_len] = 0; + } +} + +void String::TruncateToLeft(size_t count) +{ + if (_cstr) + { + count = Math::Min(count, _len); + if (count < _len) + { + BecomeUnique(); + _len = count; + _cstr[_len] = 0; + } + } +} + +void String::TruncateToMid(size_t from, size_t count) +{ + if (_cstr) + { + Math::ClampLength(from, count, (size_t)0, _len); + if (from > 0 || count < _len) + { + BecomeUnique(); + _len = count; + _cstr += from; + _cstr[_len] = 0; + } + } +} + +void String::TruncateToRight(size_t count) +{ + if (_cstr) + { + count = Math::Min(count, GetLength()); + if (count < _len) + { + BecomeUnique(); + _cstr += _len - count; + _len = count; + } + } +} + +void String::TruncateToLeftSection(char separator, bool exclude_separator) +{ + if (_cstr && separator) + { + size_t slice_at = FindChar(separator); + if (slice_at != -1) + { + TruncateToLeft(exclude_separator ? slice_at : slice_at + 1); + } + } +} + +void String::TruncateToRightSection(char separator, bool exclude_separator) +{ + if (_cstr && separator) + { + size_t slice_at = FindCharReverse(separator); + if (slice_at != -1) + { + TruncateToRight(exclude_separator ? _len - slice_at - 1 : _len - slice_at); + } + } +} + +void String::TruncateToSection(char separator, size_t first, size_t last, + bool exclude_first_sep, bool exclude_last_sep) +{ + if (!_cstr || !separator) + { + return; + } + + size_t slice_from; + size_t slice_to; + if (FindSection(separator, first, last, exclude_first_sep, exclude_last_sep, + slice_from, slice_to)) + { + TruncateToMid(slice_from, slice_to - slice_from); + } + else + { + Empty(); + } +} + +void String::Wrap(const char *cstr) +{ + Free(); + _buf = nullptr; + // Note that String is NOT supposed to *modify* the const buffer. + // Any non-read operation on the buffer is preceded by a call to BecomeUnique, + // which in turn will allocate a reference-counted buffer copy. + _cstr = const_cast(cstr); + _len = strlen(cstr); +} + +String &String::operator=(const String& str) +{ + if (_cstr != str._cstr) + { + Free(); + _buf = str._buf; + _cstr = str._cstr; + _len = str._len; + if (_bufHead) + { + _bufHead->RefCount++; + } + } + return *this; +} + +String &String::operator=(const char *cstr) +{ + SetString(cstr); + return *this; +} + +void String::Create(size_t max_length) +{ + _buf = new char[sizeof(String::BufHeader) + max_length + 1]; + _bufHead->RefCount = 1; + _bufHead->Capacity = max_length; + _len = 0; + _cstr = _buf + sizeof(String::BufHeader); + _cstr[_len] = 0; +} + +void String::Copy(size_t max_length, size_t offset) +{ + if (!_cstr) + { + return; + } + + char *new_data = new char[sizeof(String::BufHeader) + max_length + 1]; + // remember, that _cstr may point to any address in buffer + char *cstr_head = new_data + sizeof(String::BufHeader) + offset; + size_t copy_length = Math::Min(_len, max_length); + memcpy(cstr_head, _cstr, copy_length); + Free(); + _buf = new_data; + _bufHead->RefCount = 1; + _bufHead->Capacity = max_length; + _len = copy_length; + _cstr = cstr_head; + _cstr[_len] = 0; +} + +void String::Align(size_t offset) +{ + char *cstr_head = _buf + sizeof(String::BufHeader) + offset; + memmove(cstr_head, _cstr, _len + 1); + _cstr = cstr_head; +} + +void String::BecomeUnique() +{ + if (_cstr && (_bufHead && _bufHead->RefCount > 1 || !_bufHead)) + { + Copy(_len); + } +} + +void String::ReserveAndShift(bool left, size_t more_length) +{ + if (_bufHead) + { + size_t total_length = _len + more_length; + if (_bufHead->Capacity < total_length) + { + // grow by 50% or at least to total_size + size_t grow_length = _bufHead->Capacity + (_bufHead->Capacity >> 1); + Copy(Math::Max(total_length, grow_length), left ? more_length : 0u); + } + else if (_bufHead->RefCount > 1) + { + Copy(total_length, left ? more_length : 0u); + } + else + { + // make sure we make use of all of our space + const char *cstr_head = _buf + sizeof(String::BufHeader); + size_t free_space = left ? + _cstr - cstr_head : + (cstr_head + _bufHead->Capacity) - (_cstr + _len); + if (free_space < more_length) + { + Align((left ? + _cstr + (more_length - free_space) : + _cstr - (more_length - free_space)) - cstr_head); + } + } + } + else + { + Create(more_length); + } +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/string.h b/engines/ags/shared/util/string.h new file mode 100644 index 00000000000..f96208a1df7 --- /dev/null +++ b/engines/ags/shared/util/string.h @@ -0,0 +1,379 @@ + +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// String class with simple memory management and copy-on-write behavior. +// +// String objects do reference counting and share data buffer on assignment. +// The reallocation and copying is done only when the string is modified. +// +// The copying of memory inside buffer is reduced to minimum. If the string is +// truncated, it is not aligned to buffer head each time, instead the c-str +// pointer is advanced, or null-terminator is put on the new place. Similarly, +// when string is enlarged and new characters are prepended or appended, only +// c-str pointer and null-terminator's position are changed, if there's enough +// space before and after meaningful string data. +// +// The class provides means to reserve large amount of buffer space before +// making modifications, as well as compacting buffer to minimal size. +// +// For all methods that expect C-string as parameter - if the null pointer is +// passed in place of C-string it is treated in all aspects as a valid empty +// string. +// +//============================================================================= +#ifndef __AGS_CN_UTIL__STRING_H +#define __AGS_CN_UTIL__STRING_H + +#include +#include +#include "core/platform.h" +#include "core/types.h" +#include "debug/assert.h" + +namespace AGS +{ +namespace Common +{ + +class Stream; + +class String +{ +public: + // Standard constructor: intialize empty string + String(); + // Copy constructor + String(const String&); + // Initialize with C-string + String(const char *cstr); + // Initialize by copying up to N chars from C-string + String(const char *cstr, size_t length); + // Initialize by filling N chars with certain value + String(char c, size_t count); + ~String(); + + // Get underlying C-string for reading; this method guarantees valid C-string + inline const char *GetCStr() const + { + return _cstr ? _cstr : ""; + } + // Get C-string or nullptr + inline const char *GetNullableCStr() const + { + return _cstr ? _cstr : nullptr; + } + // Get character count + inline size_t GetLength() const + { + return _len; + } + // Know if the string is empty (has no meaningful characters) + inline bool IsEmpty() const + { + return _len == 0; + } + + // Those getters are for tests only, hence if AGS_PLATFORM_DEBUG +#if AGS_PLATFORM_DEBUG + inline const char *GetBuffer() const + { + return _buf; + } + + inline size_t GetCapacity() const + { + return _bufHead ? _bufHead->Capacity : 0; + } + + inline size_t GetRefCount() const + { + return _bufHead ? _bufHead->RefCount : 0; + } +#endif + + // Read() method implies that string length is initially unknown. + // max_chars parameter determine the buffer size limit. + // If stop_at_limit flag is set, it will read only up to the max_chars. + // Otherwise (by default) hitting the limit won't stop stream reading; + // the data will be read until null-terminator or EOS is met, and buffer + // will contain only leftmost part of the longer string that fits in. + // This method is better fit for reading from binary streams. + void Read(Stream *in, size_t max_chars = 5 * 1024 * 1024, bool stop_at_limit = false); + // ReadCount() reads up to N characters from stream, ignoring null- + // terminator. This method is better fit for reading from text + // streams, or when the length of string is known beforehand. + void ReadCount(Stream *in, size_t count); + // Write() puts the null-terminated string into the stream. + void Write(Stream *out) const; + // WriteCount() writes N characters to stream, filling the remaining + // space with null-terminators when needed. + void WriteCount(Stream *out, size_t count) const; + + static void WriteString(const char *cstr, Stream *out); + + //------------------------------------------------------------------------- + // String analysis methods + //------------------------------------------------------------------------- + + // Compares with given C-string + int Compare(const char *cstr) const; + int CompareNoCase(const char *cstr) const; + // Compares the leftmost part of this string with given C-string + int CompareLeft(const char *cstr, size_t count = -1) const; + int CompareLeftNoCase(const char *cstr, size_t count = -1) const; + // Compares any part of this string with given C-string + int CompareMid(const char *cstr, size_t from, size_t count = -1) const; + int CompareMidNoCase(const char *cstr, size_t from, size_t count = -1) const; + // Compares the rightmost part of this string with given C-string + int CompareRight(const char *cstr, size_t count = -1) const; + int CompareRightNoCase(const char *cstr, size_t count = -1) const; + + // These functions search for character or substring inside this string + // and return the index of the (first) character, or -1 if nothing found. + size_t FindChar(char c, size_t from = 0) const; + size_t FindCharReverse(char c, size_t from = -1) const; + size_t FindString(const char *cstr, size_t from = 0) const; + + // Section methods treat string as a sequence of 'fields', separated by + // special character. They search for a substring consisting of all such + // 'fields' from the 'first' to the 'last', inclusive; the bounding + // separators are optionally included too. + // Section indexes are zero-based. The first (0th) section is always + // located before the first separator and the last section is always + // located after the last separator, meaning that if the outermost + // character in string is separator char, there's still an empty trailing + // field beyond that. + // This also means that there's always at least one section in any string, + // even if there are no separating chars. + bool FindSection(char separator, size_t first, size_t last, bool exclude_first_sep, bool exclude_last_sep, + size_t &from, size_t &to) const; + + // Get Nth character with bounds check (as opposed to subscript operator) + inline char GetAt(size_t index) const + { + return (index < _len) ? _cstr[index] : 0; + } + inline char GetLast() const + { + return (_len > 0) ? _cstr[_len - 1] : 0; + } + + //------------------------------------------------------------------------- + // Value cast methods + //------------------------------------------------------------------------- + + int ToInt() const; + + //------------------------------------------------------------------------- + // Factory methods + //------------------------------------------------------------------------- + + // Wraps the given string buffer without owning it, won't count references, + // won't delete it at destruction. Can be used with string literals. + static String Wrapper(const char *cstr); + + static String FromFormat(const char *fcstr, ...); + static String FromFormatV(const char *fcstr, va_list argptr); + // Reads stream until null-terminator or EOS + static String FromStream(Stream *in, size_t max_chars = 5 * 1024 * 1024, bool stop_at_limit = false); + // Reads up to N chars from stream + static String FromStreamCount(Stream *in, size_t count); + + // Creates a lowercased copy of the string + String Lower() const; + // Creates an uppercased copy of the string + String Upper() const; + + // Extract N leftmost characters as a new string + String Left(size_t count) const; + // Extract up to N characters starting from given index + String Mid(size_t from, size_t count = -1) const; + // Extract N rightmost characters + String Right(size_t count) const; + + // Extract leftmost part, separated by the given char; if no separator was + // found returns the whole string + String LeftSection(char separator, bool exclude_separator = true) const; + // Extract rightmost part, separated by the given char; if no separator was + // found returns the whole string + String RightSection(char separator, bool exclude_separator = true) const; + // Extract the range of Xth to Yth fields, separated by the given character + String Section(char separator, size_t first, size_t last, + bool exclude_first_sep = true, bool exclude_last_sep = true) const; + // Splits the string into segments divided by the instances of a given character, + // including empty segments e.g. if separators follow each other; + // returns at least one segment (equal to full string if no separator was found) + std::vector Split(char separator) const; + + //------------------------------------------------------------------------- + // String modification methods + //------------------------------------------------------------------------- + + // Ensure string has at least space to store N chars; + // this does not change string contents, nor length + void Reserve(size_t max_length); + // Ensure string has at least space to store N additional chars + void ReserveMore(size_t more_length); + // Make string's buffer as small as possible to hold current data + void Compact(); + + // Append* methods add content at the string's end, increasing its length + // Add C-string at string's end + void Append(const char *cstr); + // Add single character at string's end + void AppendChar(char c); + // Clip* methods decrease the string, removing defined part + // Cuts off leftmost N characters + void ClipLeft(size_t count); + // Cuts out N characters starting from given index + void ClipMid(size_t from, size_t count = -1); + // Cuts off rightmost N characters + void ClipRight(size_t count); + // Cuts off leftmost part, separated by the given char; if no separator was + // found cuts whole string, leaving empty string + void ClipLeftSection(char separator, bool include_separator = true); + // Cuts off rightmost part, separated by the given char; if no separator + // was found cuts whole string, leaving empty string + void ClipRightSection(char separator, bool include_separator = true); + // Cuts out the range of Xth to Yth fields separated by the given character + void ClipSection(char separator, size_t first, size_t last, + bool include_first_sep = true, bool include_last_sep = true); + // Sets string length to zero + void Empty(); + // Makes a new string by filling N chars with certain value + void FillString(char c, size_t count); + // Makes a new string by putting in parameters according to format string + void Format(const char *fcstr, ...); + void FormatV(const char *fcstr, va_list argptr); + // Decrement ref counter and deallocate data if must. + // Free() should be called only when buffer is not needed anymore; + // if string must be truncated to zero length, but retain the allocated + // memory, call Empty() instead. + void Free(); + // Convert string to lowercase equivalent + void MakeLower(); + // Convert string to uppercase equivalent + void MakeUpper(); + // Prepend* methods add content before the string's head, increasing its length + // Add C-string before string's head + void Prepend(const char *cstr); + // Add single character before string's head + void PrependChar(char c); + // Replaces all occurences of one character with another character + void Replace(char what, char with); + // Replaces particular substring with another substring; new substring + // may have different length + void ReplaceMid(size_t from, size_t count, const char *cstr); + // Reverses the string + void Reverse(); + // Overwrite the Nth character of the string; does not change string's length + void SetAt(size_t index, char c); + // Makes a new string by copying up to N chars from C-string + void SetString(const char *cstr, size_t length = -1); + // For all Trim functions, if given character value is 0, all whitespace + // characters (space, tabs, CRLF) are removed. + // Remove heading and trailing characters from the string + void Trim(char c = 0); + // Remove heading characters from the string; + void TrimLeft(char c = 0); + // Remove trailing characters from the string + void TrimRight(char c = 0); + // Truncate* methods decrease the string to the part of itself + // Truncate the string to the leftmost N characters + void TruncateToLeft(size_t count); + // Truncate the string to the middle N characters + void TruncateToMid(size_t from, size_t count = -1); + // Truncate the string to the rightmost N characters + void TruncateToRight(size_t count); + // Truncate the string to the leftmost part, separated by the given char; + // if no separator was found leaves string unchanged + void TruncateToLeftSection(char separator, bool exclude_separator = true); + // Truncate the string to the rightmost part, separated by the given char; + // if no separator was found leaves string unchanged + void TruncateToRightSection(char separator, bool exclude_separator = true); + // Truncate the string to range of Xth to Yth fields separated by the + // given character + void TruncateToSection(char separator, size_t first, size_t last, + bool exclude_first_sep = true, bool exclude_last_sep = true); + // Wraps the given string buffer without owning it, won't count references, + // won't delete it at destruction. Can be used with string literals. + void Wrap(const char *cstr); + + //------------------------------------------------------------------------- + // Operators + //------------------------------------------------------------------------- + + inline operator const char *() const + { + return GetCStr(); + } + // Assign String by sharing data reference + String &operator=(const String&); + // Assign C-string by copying contents + String &operator=(const char *cstr); + inline char operator[](size_t index) const + { + assert(index < _len); + return _cstr[index]; + } + inline bool operator==(const char *cstr) const + { + return Compare(cstr) == 0; + } + inline bool operator!=(const char *cstr) const + { + return Compare(cstr) != 0; + } + inline bool operator <(const char *cstr) const + { + return Compare(cstr) < 0; + } + +private: + // Creates new empty string with buffer enough to fit given length + void Create(size_t buffer_length); + // Release string and copy data to the new buffer + void Copy(size_t buffer_length, size_t offset = 0); + // Aligns data at given offset + void Align(size_t offset); + + // Ensure this string is a compact independent copy, with ref counter = 1 + void BecomeUnique(); + // Ensure this string is independent, and there's enough space before + // or after the current string data + void ReserveAndShift(bool left, size_t more_length); + + char *_cstr; // pointer to actual string data + size_t _len; // valid string length, in characters, excluding null-term + + // Header of a reference-counted buffer + struct BufHeader + { + size_t RefCount = 0; // reference count + size_t Capacity = 0; // available space, in characters + }; + + // Union that groups mutually exclusive data (currently only ref counted buffer) + union + { + char *_buf; // reference-counted data (raw ptr) + BufHeader *_bufHead; // the header of a reference-counted data + }; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__STRING_H diff --git a/engines/ags/shared/util/string_compat.c b/engines/ags/shared/util/string_compat.c new file mode 100644 index 00000000000..0f925e3ce93 --- /dev/null +++ b/engines/ags/shared/util/string_compat.c @@ -0,0 +1,58 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include "util/string_compat.h" +#include +#include +#include "core/platform.h" + +char *ags_strlwr(char *s) +{ + char *p = s; + for (; *p; p++) + *p = tolower(*p); + return s; +} + +char *ags_strupr(char *s) +{ + char *p = s; + for (; *p; p++) + *p = toupper(*p); + return s; +} + +int ags_stricmp(const char *s1, const char *s2) +{ +#if AGS_PLATFORM_OS_WINDOWS + return stricmp(s1, s2); +#else + return strcasecmp(s1, s2); +#endif +} + +int ags_strnicmp(const char *s1, const char *s2, size_t n) +{ +#if AGS_PLATFORM_OS_WINDOWS + return strnicmp(s1, s2, n); +#else + return strncasecmp(s1, s2, n); +#endif +} + +char *ags_strdup(const char *s) +{ + char *result = (char *)malloc(strlen(s) + 1); + strcpy(result, s); + return result; +} diff --git a/engines/ags/shared/util/string_compat.h b/engines/ags/shared/util/string_compat.h new file mode 100644 index 00000000000..209fab8bb37 --- /dev/null +++ b/engines/ags/shared/util/string_compat.h @@ -0,0 +1,33 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __AGS_CN_UTIL__STRINGCOMPAT_H +#define __AGS_CN_UTIL__STRINGCOMPAT_H + +#include "core/types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +char *ags_strlwr(char *s); +char *ags_strupr(char *s); +int ags_stricmp(const char *, const char *); +int ags_strnicmp(const char *, const char *, size_t); +char *ags_strdup(const char *s); + +#ifdef __cplusplus +} +#endif + +#endif // __AGS_CN_UTIL__STRINGCOMPAT_H diff --git a/engines/ags/shared/util/string_types.h b/engines/ags/shared/util/string_types.h new file mode 100644 index 00000000000..2795be49e87 --- /dev/null +++ b/engines/ags/shared/util/string_types.h @@ -0,0 +1,114 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#ifndef __AGS_CN_UTIL__STRINGTYPES_H +#define __AGS_CN_UTIL__STRINGTYPES_H + +#include +#include +#include + +#include +#include "util/string.h" + +namespace FNV +{ + +const uint32_t PRIME_NUMBER = 2166136261U; +const uint32_t SECONDARY_NUMBER = 16777619U; + +inline size_t Hash(const char *data, const size_t len) +{ + uint32_t hash = PRIME_NUMBER; + for (size_t i = 0; i < len; ++i) + hash = (SECONDARY_NUMBER * hash) ^ (uint8_t)(data[i]); + return hash; +} + +inline size_t Hash_LowerCase(const char *data, const size_t len) +{ + uint32_t hash = PRIME_NUMBER; + for (size_t i = 0; i < len; ++i) + hash = (SECONDARY_NUMBER * hash) ^ (uint8_t)(tolower(data[i])); + return hash; +} + +} // namespace FNV + + +// A std::hash specialization for AGS String +namespace std +{ +#ifdef AGS_NEEDS_TR1 +namespace tr1 +{ +#endif +// std::hash for String object +template<> +struct hash : public unary_function +{ + size_t operator ()(const AGS::Common::String &key) const + { + return FNV::Hash(key.GetCStr(), key.GetLength()); + } +}; +#ifdef AGS_NEEDS_TR1 +} +#endif +} + + +namespace AGS +{ +namespace Common +{ + +// +// Various comparison functors +// + +// Test case-insensitive String equality +struct StrEqNoCase : public std::binary_function +{ + bool operator()(const String &s1, const String &s2) const + { + return s1.CompareNoCase(s2) == 0; + } +}; + +// Case-insensitive String less +struct StrLessNoCase : public std::binary_function +{ + bool operator()(const String &s1, const String &s2) const + { + return s1.CompareNoCase(s2) < 0; + } +}; + +// Compute case-insensitive hash for a String object +struct HashStrNoCase : public std::unary_function +{ + size_t operator ()(const String &key) const + { + return FNV::Hash_LowerCase(key.GetCStr(), key.GetLength()); + } +}; + +typedef std::vector StringV; +typedef std::unordered_map StringMap; +typedef std::unordered_map StringIMap; + +} // namespace Common +} // namespace AGS + +#endif //__AGS_CN_UTIL__STRINGTYPES_H diff --git a/engines/ags/shared/util/string_utils.cpp b/engines/ags/shared/util/string_utils.cpp new file mode 100644 index 00000000000..c022f0c81ab --- /dev/null +++ b/engines/ags/shared/util/string_utils.cpp @@ -0,0 +1,171 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +#include +#include +#include "core/platform.h" +#include "util/math.h" +#include "util/string_utils.h" +#include "util/stream.h" + +using namespace AGS::Common; + +String cbuf_to_string_and_free(char *char_buf) +{ + String s = char_buf; + free(char_buf); + return s; +} + + +namespace AGS +{ +namespace Common +{ + +String StrUtil::IntToString(int d) +{ + return String::FromFormat("%d", d); +} + +int StrUtil::StringToInt(const String &s, int def_val) +{ + if (!s.GetCStr()) + return def_val; + char *stop_ptr; + int val = strtol(s.GetCStr(), &stop_ptr, 0); + return (stop_ptr == s.GetCStr() + s.GetLength()) ? val : def_val; +} + +StrUtil::ConversionError StrUtil::StringToInt(const String &s, int &val, int def_val) +{ + val = def_val; + if (!s.GetCStr()) + return StrUtil::kFailed; + char *stop_ptr; + errno = 0; + long lval = strtol(s.GetCStr(), &stop_ptr, 0); + if (stop_ptr != s.GetCStr() + s.GetLength()) + return StrUtil::kFailed; + if (lval > INT_MAX || lval < INT_MIN || errno == ERANGE) + return StrUtil::kOutOfRange; + val = (int)lval; + return StrUtil::kNoError; +} + +String StrUtil::ReadString(Stream *in) +{ + size_t len = in->ReadInt32(); + if (len > 0) + return String::FromStreamCount(in, len); + return String(); +} + +void StrUtil::ReadString(char *cstr, Stream *in, size_t buf_limit) +{ + size_t len = in->ReadInt32(); + if (buf_limit == 0) + { + in->Seek(len); + return; + } + + len = Math::Min(len, buf_limit - 1); + if (len > 0) + in->Read(cstr, len); + cstr[len] = 0; +} + +void StrUtil::ReadString(String &s, Stream *in) +{ + size_t len = in->ReadInt32(); + s.ReadCount(in, len); +} + +void StrUtil::ReadString(char **cstr, Stream *in) +{ + size_t len = in->ReadInt32(); + *cstr = new char[len + 1]; + if (len > 0) + in->Read(*cstr, len); + (*cstr)[len] = 0; +} + +void StrUtil::SkipString(Stream *in) +{ + size_t len = in->ReadInt32(); + in->Seek(len); +} + +void StrUtil::WriteString(const String &s, Stream *out) +{ + size_t len = s.GetLength(); + out->WriteInt32(len); + if (len > 0) + out->Write(s.GetCStr(), len); +} + +void StrUtil::WriteString(const char *cstr, Stream *out) +{ + size_t len = strlen(cstr); + out->WriteInt32(len); + if (len > 0) + out->Write(cstr, len); +} + +void StrUtil::ReadCStr(char *buf, Stream *in, size_t buf_limit) +{ + if (buf_limit == 0) + { + while (in->ReadByte() > 0); + return; + } + + auto ptr = buf; + auto last = buf + buf_limit - 1; + for (;;) + { + if (ptr >= last) { + *ptr = 0; + while (in->ReadByte() > 0); // must still read until 0 + break; + } + + auto ichar = in->ReadByte(); + if (ichar <= 0) { + *ptr = 0; + break; + } + *ptr = static_cast(ichar); + ptr++; + } +} + +void StrUtil::SkipCStr(Stream *in) +{ + while (in->ReadByte() > 0); +} + +void StrUtil::WriteCStr(const char *cstr, Stream *out) +{ + size_t len = strlen(cstr); + out->Write(cstr, len + 1); +} + +void StrUtil::WriteCStr(const String &s, Stream *out) +{ + out->Write(s.GetCStr(), s.GetLength() + 1); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/string_utils.h b/engines/ags/shared/util/string_utils.h new file mode 100644 index 00000000000..b0dbdae9406 --- /dev/null +++ b/engines/ags/shared/util/string_utils.h @@ -0,0 +1,76 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// +// +//============================================================================= +#ifndef __AGS_CN_UTIL__STRINGUTILS_H +#define __AGS_CN_UTIL__STRINGUTILS_H + +#include "util/string.h" + +namespace AGS { namespace Common { class Stream; } } +using namespace AGS; // FIXME later + +//============================================================================= + +// Converts char* to string and frees original malloc-ed array; +// This is used when we get a malloc'd char array from some utility function. +Common::String cbuf_to_string_and_free(char *char_buf); + +namespace AGS +{ +namespace Common +{ +namespace StrUtil +{ + enum ConversionError + { + kNoError, // conversion successful + kFailed, // conversion failed (e.g. wrong format) + kOutOfRange // the resulting value is out of range + }; + + // Convert integer to string, by printing its value + String IntToString(int val); + // Tries to convert whole string into integer value; + // returns def_val on failure + int StringToInt(const String &s, int def_val = 0); + // Tries to convert whole string into integer value; + // Returns error code if any non-digit character was met or if value is out + // of range; the 'val' variable will be set with resulting integer, or + // def_val on failure + ConversionError StringToInt(const String &s, int &val, int def_val); + + // Serialize and unserialize unterminated string prefixed with 32-bit length; + // length is presented as 32-bit integer integer + String ReadString(Stream *in); + void ReadString(char *cstr, Stream *in, size_t buf_limit); + void ReadString(char **cstr, Stream *in); + void ReadString(String &s, Stream *in); + void SkipString(Stream *in); + void WriteString(const String &s, Stream *out); + void WriteString(const char *cstr, Stream *out); + + // Serialize and unserialize string as c-string (null-terminated sequence) + void ReadCStr(char *buf, Stream *in, size_t buf_limit); + void SkipCStr(Stream *in); + void WriteCStr(const char *cstr, Stream *out); + void WriteCStr(const String &s, Stream *out); +} +} // namespace Common +} // namespace AGS + + +#endif // __AGS_CN_UTIL__STRINGUTILS_H diff --git a/engines/ags/shared/util/textreader.h b/engines/ags/shared/util/textreader.h new file mode 100644 index 00000000000..f7273ced30d --- /dev/null +++ b/engines/ags/shared/util/textreader.h @@ -0,0 +1,48 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Specialized interface for reading plain text from the underlying source +// +//============================================================================= +#ifndef __AGS_CN_UTIL__TEXTREADER_H +#define __AGS_CN_UTIL__TEXTREADER_H + +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +class TextReader +{ +public: + virtual ~TextReader() = default; + + virtual bool IsValid() const = 0; + + // Read single character + virtual char ReadChar() = 0; + // Read defined number of characters + virtual String ReadString(size_t length) = 0; + // Read till line break + virtual String ReadLine() = 0; + // Read till end of available data + virtual String ReadAll() = 0; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__TEXTSTREAM_H diff --git a/engines/ags/shared/util/textstreamreader.cpp b/engines/ags/shared/util/textstreamreader.cpp new file mode 100644 index 00000000000..d0f4e5dc2e6 --- /dev/null +++ b/engines/ags/shared/util/textstreamreader.cpp @@ -0,0 +1,154 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "util/math.h" +#include "util/stream.h" +#include "util/textstreamreader.h" + +namespace AGS +{ +namespace Common +{ + +TextStreamReader::TextStreamReader(Stream *stream) + : _stream(stream) +{ +} + +TextStreamReader::~TextStreamReader() +{ + // TODO: use shared ptr + delete _stream; +} + +bool TextStreamReader::IsValid() const +{ + return _stream && _stream->CanRead(); +} + +const Stream *TextStreamReader::GetStream() const +{ + return _stream; +} + +void TextStreamReader::ReleaseStream() +{ + _stream = nullptr; +} + +bool TextStreamReader::EOS() const +{ + return _stream ? _stream->EOS() : true; +} + +char TextStreamReader::ReadChar() +{ + if (_stream) + { + // Skip carriage-returns + char c; + do + { + c = _stream->ReadByte(); + } + while (!_stream->EOS() && c == '\r'); + return c; + } + return '\0'; +} + +String TextStreamReader::ReadString(size_t length) +{ + if (!_stream) + { + return ""; + } + // TODO: remove carriage-return characters + return String::FromStreamCount(_stream, length); +} + +String TextStreamReader::ReadLine() +{ + // TODO + // Probably it is possible to group Stream::ReadString with this, + // both use similar algorythm, difference is only in terminator chars + + if (!_stream) + { + return ""; + } + + String str; + int chars_read_last = 0; + int line_break_position = -1; + // Read a chunk of memory to buffer and seek for null-terminator, + // if not found, repeat until EOS + const int single_chunk_length = 3000; + const int max_chars = 5000000; + char char_buffer[single_chunk_length + 1]; + do + { + chars_read_last = _stream->Read(char_buffer, single_chunk_length); + char *seek_ptr = char_buffer; + int c; + for (c = 0; c < chars_read_last && *seek_ptr != '\n'; ++c, ++seek_ptr); + + int append_length = 0; + int str_len = str.GetLength(); + if (c < chars_read_last && *seek_ptr == '\n') + { + line_break_position = seek_ptr - char_buffer; + if (str_len < max_chars) + { + append_length = Math::Min(line_break_position, max_chars - str_len); + } + } + else + { + append_length = Math::Min(chars_read_last, max_chars - str_len); + } + + if (append_length > 0) + { + char_buffer[append_length] = '\0'; + str.Append(char_buffer); + } + } + while (!EOS() && line_break_position < 0); + + // If null-terminator was found make sure stream is positioned at the next + // byte after line end + if (line_break_position >= 0) + { + // CHECKME: what if stream does not support seek? need an algorythm fork for that + // the seek offset should be negative + _stream->Seek(line_break_position - chars_read_last + 1 /* beyond line feed */); + } + + str.TrimRight('\r'); // skip carriage-return, if any + return str; +} + +String TextStreamReader::ReadAll() +{ + if (_stream) + { + soff_t len = _stream->GetLength() - _stream->GetPosition(); + return ReadString(len > SIZE_MAX ? SIZE_MAX : (size_t)len); + } + return ""; +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/textstreamreader.h b/engines/ags/shared/util/textstreamreader.h new file mode 100644 index 00000000000..4f69dc02b00 --- /dev/null +++ b/engines/ags/shared/util/textstreamreader.h @@ -0,0 +1,60 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Class for reading plain text from the stream +// +//============================================================================= +#ifndef __AGS_CN_UTIL__TEXTSTREAMREADER_H +#define __AGS_CN_UTIL__TEXTSTREAMREADER_H + +#include "util/textreader.h" + +namespace AGS +{ +namespace Common +{ + +class Stream; + +class TextStreamReader : public TextReader +{ +public: + // TODO: use shared ptr + TextStreamReader(Stream *stream); + ~TextStreamReader() override; + + bool IsValid() const override; + const Stream *GetStream() const; + // TODO: use shared ptr instead + void ReleaseStream(); + + bool EOS() const; + + // Read single character + char ReadChar() override; + // Read defined number of characters + String ReadString(size_t length) override; + // Read till line break + String ReadLine() override; + // Read till end of available data + String ReadAll() override; + +private: + Stream *_stream; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__TEXTSTREAM_H diff --git a/engines/ags/shared/util/textstreamwriter.cpp b/engines/ags/shared/util/textstreamwriter.cpp new file mode 100644 index 00000000000..6345c52fb6f --- /dev/null +++ b/engines/ags/shared/util/textstreamwriter.cpp @@ -0,0 +1,121 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include // sprintf +#include "core/platform.h" +#include "util/textstreamwriter.h" +#include "util/stream.h" + +namespace AGS +{ +namespace Common +{ + +#if AGS_PLATFORM_OS_WINDOWS +static const char Endl[2] = {'\r', '\n'}; +#else +static const char Endl[1] = {'\n'}; +#endif + + +TextStreamWriter::TextStreamWriter(Stream *stream) + : _stream(stream) +{ +} + +TextStreamWriter::~TextStreamWriter() +{ + // TODO use shared ptr + delete _stream; +} + +bool TextStreamWriter::IsValid() const +{ + return _stream && _stream->CanWrite(); +} + +const Stream *TextStreamWriter::GetStream() const +{ + return _stream; +} + +void TextStreamWriter::ReleaseStream() +{ + _stream = nullptr; +} + +bool TextStreamWriter::EOS() const +{ + return _stream ? _stream->EOS() : true; +} + +void TextStreamWriter::WriteChar(char c) +{ + if (_stream) + { + _stream->WriteByte(c); + } +} + +void TextStreamWriter::WriteString(const String &str) +{ + if (_stream) + { + // TODO: replace line-feed characters in string with platform-specific line break + _stream->Write(str.GetCStr(), str.GetLength()); + } +} + +void TextStreamWriter::WriteLine(const String &str) +{ + if (!_stream) + { + return; + } + + // TODO: replace line-feed characters in string with platform-specific line break + _stream->Write(str.GetCStr(), str.GetLength()); + _stream->Write(Endl, sizeof(Endl)); +} + +void TextStreamWriter::WriteFormat(const char *fmt, ...) +{ + if (!_stream) + { + return; + } + + // TODO: replace line-feed characters in format string with platform-specific line break + + va_list argptr; + va_start(argptr, fmt); + int need_length = vsnprintf(nullptr, 0, fmt, argptr); + va_start(argptr, fmt); // Reset argptr + char *buffer = new char[need_length + 1]; + vsprintf(buffer, fmt, argptr); + va_end(argptr); + + _stream->Write(buffer, need_length); + delete [] buffer; +} + +void TextStreamWriter::WriteLineBreak() +{ + if (_stream) + _stream->Write(Endl, sizeof(Endl)); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/textstreamwriter.h b/engines/ags/shared/util/textstreamwriter.h new file mode 100644 index 00000000000..4024a05df39 --- /dev/null +++ b/engines/ags/shared/util/textstreamwriter.h @@ -0,0 +1,61 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Class for writing plain text to the stream +// +//============================================================================= +#ifndef __AGS_CN_UTIL__TEXTSTREAMWRITER_H +#define __AGS_CN_UTIL__TEXTSTREAMWRITER_H + +#include "util/textwriter.h" + +namespace AGS +{ +namespace Common +{ + +class Stream; + +class TextStreamWriter : public TextWriter +{ +public: + // TODO: use shared ptr + TextStreamWriter(Stream *stream); + ~TextStreamWriter() override; + + bool IsValid() const override; + const Stream *GetStream() const; + // TODO: use shared ptr instead + void ReleaseStream(); + + bool EOS() const; + + // Write single character + void WriteChar(char c) override; + // Write string as a plain text (without null-terminator) + void WriteString(const String &str) override; + // Write string and add line break at the end + void WriteLine(const String &str) override; + // Write formatted string (see *printf) + void WriteFormat(const char *fmt, ...) override; + void WriteLineBreak() override; + +private: + Stream *_stream; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__TEXTSTREAMWRITER_H diff --git a/engines/ags/shared/util/textwriter.h b/engines/ags/shared/util/textwriter.h new file mode 100644 index 00000000000..b254202ee9e --- /dev/null +++ b/engines/ags/shared/util/textwriter.h @@ -0,0 +1,49 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Specialized interface for writing plain text to the underlying source +// +//============================================================================= +#ifndef __AGS_CN_UTIL__TEXTWRITER_H +#define __AGS_CN_UTIL__TEXTWRITER_H + +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +class TextWriter +{ +public: + virtual ~TextWriter() = default; + + virtual bool IsValid() const = 0; + + // Write single character + virtual void WriteChar(char c) = 0; + // Write string as a plain text (without null-terminator) + virtual void WriteString(const String &str) = 0; + // Write string and add line break at the end + virtual void WriteLine(const String &str) = 0; + // Write formatted string (see *printf) + virtual void WriteFormat(const char *fmt, ...) = 0; + virtual void WriteLineBreak() = 0; +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_UTIL__TEXTWRITER_H diff --git a/engines/ags/shared/util/version.cpp b/engines/ags/shared/util/version.cpp new file mode 100644 index 00000000000..2f8df6110e9 --- /dev/null +++ b/engines/ags/shared/util/version.cpp @@ -0,0 +1,150 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include +#include "util/version.h" + +namespace AGS +{ +namespace Common +{ + +const Version Version::LastOldFormatVersion(3, 2, 2, 1120); + +Version::Version() + : Major(0) + , Minor(0) + , Release(0) + , Revision(0) +{ + MakeString(); +} + +Version::Version(int32_t major, int32_t minor, int32_t release) + : Major(major) + , Minor(minor) + , Release(release) + , Revision(0) +{ + MakeString(); +} + +Version::Version(int32_t major, int32_t minor, int32_t release, int32_t revision) + : Major(major) + , Minor(minor) + , Release(release) + , Revision(revision) +{ + MakeString(); +} + +Version::Version(int32_t major, int32_t minor, int32_t release, int32_t revision, const String &special) + : Major(major) + , Minor(minor) + , Release(release) + , Revision(revision) + , Special(special) +{ + MakeString(); +} + +Version::Version(int32_t major, int32_t minor, int32_t release, int32_t revision, const String &special, const String &build_info) + : Major(major) + , Minor(minor) + , Release(release) + , Revision(revision) + , Special(special) + , BuildInfo(build_info) +{ + MakeString(); +} + +Version::Version(const String &version_string) + : Major(0) + , Minor(0) + , Release(0) + , Revision(0) +{ + SetFromString(version_string); +} + +void Version::SetFromString(const String &version_string) +{ + Major = version_string.LeftSection('.').ToInt(); + String second_section = version_string.Section('.', 1, 1); + Minor = second_section.ToInt(); + String third_section = version_string.Section('.', 2, 2); + String fourth_section = version_string.Section('.', 3, 3); + String revision_section; + + bool old_version_format = Major < 3 || fourth_section.IsEmpty(); + if (old_version_format) + { + if (second_section.GetLength() > 1) + { + Release = Minor % 10; + Minor /= 10; + } + else + { + Release = 0; + } + revision_section = third_section; + } + else + { + Release = third_section.ToInt(); + revision_section = fourth_section; + } + + int revision_length = 0; + if (!revision_section.IsEmpty()) + { + const char *seek_ptr = revision_section.GetCStr(); + const char *end_ptr = revision_section.GetCStr() + revision_section.GetLength(); + while (seek_ptr != end_ptr) + { + if (!isdigit(*seek_ptr)) + { + break; + } + revision_length++; + seek_ptr++; + } + } + + Revision = revision_section.Left(revision_length).ToInt(); + // In old version format a special tag was added right after revision digits. + // In new version format a special tag is separated from revision digits with single space char. + Special = revision_section.Mid(revision_length + (old_version_format ? 0 : 1)); + + MakeString(); +} + +void Version::MakeString() +{ + if (Special.IsEmpty()) + { + LongString.Format("%d.%d.%d.%d", Major, Minor, Release, Revision); + } + else + { + LongString.Format("%d.%d.%d.%d %s", Major, Minor, Release, Revision, Special.GetCStr()); + } + BackwardCompatibleString.Format("%d.%02d.%d%s", Major, Minor * 10 + Release, Revision, Special.GetCStr()); + ShortString.Format("%d.%d", Major, Minor); +} + +} // namespace Common +} // namespace AGS diff --git a/engines/ags/shared/util/version.h b/engines/ags/shared/util/version.h new file mode 100644 index 00000000000..69696ac681f --- /dev/null +++ b/engines/ags/shared/util/version.h @@ -0,0 +1,107 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// Class, depicting version of the AGS engine +// +//============================================================================= +#ifndef __AGS_CN_MAIN__VERSION_H +#define __AGS_CN_MAIN__VERSION_H + +#include "util/string.h" + +namespace AGS +{ +namespace Common +{ + +using Common::String; + +struct Version +{ + int32_t Major; + int32_t Minor; + int32_t Release; + int32_t Revision; + String Special; + String BuildInfo; + + String LongString; + String ShortString; + String BackwardCompatibleString; + + // Last engine version, using different version format than AGS Editor (3.22.1120 / 3.2.2.1120) + static const Version LastOldFormatVersion; + + Version(); + Version(int32_t major, int32_t minor, int32_t release); + Version(int32_t major, int32_t minor, int32_t release, int32_t revision); + Version(int32_t major, int32_t minor, int32_t release, int32_t revision, const String &special); + Version(int32_t major, int32_t minor, int32_t release, int32_t revision, const String &special, const String &build_info); + Version(const String &version_string); + + inline int32_t AsNumber() const + { + return Major * 10000 + Minor * 100 + Release; + } + + inline int64_t AsLongNumber() const + { + return (int64_t)Major * 100000000L + (int64_t)Minor * 1000000L + (int64_t)Release * 10000L + Revision; + } + + inline int32_t AsSmallNumber() const + { + return Major * 100 + Minor; + } + + void SetFromString(const String &version_string); + + inline bool operator < (const Version &other) const + { + return AsLongNumber() < other.AsLongNumber(); + } + + inline bool operator <= (const Version &other) const + { + return AsLongNumber() <= other.AsLongNumber(); + } + + inline bool operator > (const Version &other) const + { + return AsLongNumber() > other.AsLongNumber(); + } + + inline bool operator >= (const Version &other) const + { + return AsLongNumber() >= other.AsLongNumber(); + } + + inline bool operator == (const Version &other) const + { + return AsLongNumber() == other.AsLongNumber(); + } + + inline bool operator != (const Version &other) const + { + return AsLongNumber() != other.AsLongNumber(); + } + +private: + void MakeString(); +}; + +} // namespace Common +} // namespace AGS + +#endif // __AGS_CN_MAIN__VERSION_H diff --git a/engines/ags/shared/util/wgt2allg.cpp b/engines/ags/shared/util/wgt2allg.cpp new file mode 100644 index 00000000000..31a81523d10 --- /dev/null +++ b/engines/ags/shared/util/wgt2allg.cpp @@ -0,0 +1,216 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= + +#include "core/assetmanager.h" +#include "gfx/bitmap.h" +#include "util/stream.h" +#include "util/wgt2allg.h" + +using namespace AGS::Common; + +#ifdef __cplusplus +extern "C" +{ +#endif + + void wsetrgb(int coll, int r, int g, int b, color * pall) + { + pall[coll].r = r; + pall[coll].g = g; + pall[coll].b = b; + } + + void wcolrotate(unsigned char start, unsigned char finish, int dir, color * pall) + { + int jj; + if (dir == 0) { + // rotate left + color tempp = pall[start]; + + for (jj = start; jj < finish; jj++) + pall[jj] = pall[jj + 1]; + + pall[finish] = tempp; + } + else { + // rotate right + color tempp = pall[finish]; + + for (jj = finish - 1; jj >= start; jj--) + pall[jj + 1] = pall[jj]; + + pall[start] = tempp; + } + } + + Bitmap *wnewblock(Common::Bitmap *src, int x1, int y1, int x2, int y2) + { + Bitmap *tempbitm; + int twid = (x2 - x1) + 1, thit = (y2 - y1) + 1; + + if (twid < 1) + twid = 1; + + if (thit < 1) + thit = 1; + + tempbitm = BitmapHelper::CreateBitmap(twid, thit); + + if (tempbitm == nullptr) + return nullptr; + + tempbitm->Blit(src, x1, y1, 0, 0, tempbitm->GetWidth(), tempbitm->GetHeight()); + return tempbitm; + } + + int wloadsprites(color * pall, char *filnam, Bitmap ** sarray, int strt, int eend) + { + int vers; + char buff[20]; + int numspri = 0, vv, hh, wdd, htt; + + Stream *in = Common::AssetManager::OpenAsset(filnam); + if (in == nullptr) + return -1; + + vers = in->ReadInt16(); + in->ReadArray(&buff[0], 13, 1); + for (vv = 0; vv < 256; vv++) // there's a filler byte + in->ReadArray(&pall[vv], 3, 1); + + if (vers > 4) + return -1; + + if (vers == 4) + numspri = in->ReadInt16(); + else { + numspri = in->ReadInt16(); + if ((numspri < 2) || (numspri > 200)) + numspri = 200; + } + + for (vv = strt; vv <= eend; vv++) + sarray[vv] = nullptr; + + for (vv = 0; vv <= numspri; vv++) { + int coldep = in->ReadInt16(); + + if (coldep == 0) { + sarray[vv] = nullptr; + if (in->EOS()) + break; + + continue; + } + + if (in->EOS()) + break; + + if (vv > eend) + break; + + wdd = in->ReadInt16(); + htt = in->ReadInt16(); + if (vv < strt) { + in->Seek(wdd * htt); + continue; + } + sarray[vv] = BitmapHelper::CreateBitmap(wdd, htt, coldep * 8); + + if (sarray[vv] == nullptr) { + delete in; + return -1; + } + + for (hh = 0; hh < htt; hh++) + in->ReadArray(&sarray[vv]->GetScanLineForWriting(hh)[0], wdd * coldep, 1); + } + delete in; + return 0; + } + + void wputblock(Common::Bitmap *ds, int xx, int yy, Bitmap *bll, int xray) + { + if (xray) + ds->Blit(bll, xx, yy, Common::kBitmap_Transparency); + else + ds->Blit(bll, 0, 0, xx, yy, bll->GetWidth(), bll->GetHeight()); + } + + Bitmap wputblock_wrapper; // [IKM] argh! :[ + void wputblock_raw(Common::Bitmap *ds, int xx, int yy, BITMAP *bll, int xray) + { + wputblock_wrapper.WrapAllegroBitmap(bll, true); + if (xray) + ds->Blit(&wputblock_wrapper, xx, yy, Common::kBitmap_Transparency); + else + ds->Blit(&wputblock_wrapper, 0, 0, xx, yy, wputblock_wrapper.GetWidth(), wputblock_wrapper.GetHeight()); + } + + const int col_lookups[32] = { + 0x000000, 0x0000A0, 0x00A000, 0x00A0A0, 0xA00000, // 4 + 0xA000A0, 0xA05000, 0xA0A0A0, 0x505050, 0x5050FF, 0x50FF50, 0x50FFFF, // 11 + 0xFF5050, 0xFF50FF, 0xFFFF50, 0xFFFFFF, 0x000000, 0x101010, 0x202020, // 18 + 0x303030, 0x404040, 0x505050, 0x606060, 0x707070, 0x808080, 0x909090, // 25 + 0xA0A0A0, 0xB0B0B0, 0xC0C0C0, 0xD0D0D0, 0xE0E0E0, 0xF0F0F0 + }; + + int __wremap_keep_transparent = 1; + + void wremap(color * pal1, Bitmap *picc, color * pal2) + { + int jj; + unsigned char color_mapped_table[256]; + + for (jj = 0; jj < 256; jj++) + { + if ((pal1[jj].r == 0) && (pal1[jj].g == 0) && (pal1[jj].b == 0)) + { + color_mapped_table[jj] = 0; + } + else + { + color_mapped_table[jj] = bestfit_color(pal2, pal1[jj].r, pal1[jj].g, pal1[jj].b); + } + } + + if (__wremap_keep_transparent > 0) { + // keep transparency + color_mapped_table[0] = 0; + // any other pixels which are being mapped to 0, map to 16 instead + for (jj = 1; jj < 256; jj++) { + if (color_mapped_table[jj] == 0) + color_mapped_table[jj] = 16; + } + } + + int pic_size = picc->GetWidth() * picc->GetHeight(); + for (jj = 0; jj < pic_size; jj++) { + int xxl = jj % (picc->GetWidth()), yyl = jj / (picc->GetWidth()); + int rr = picc->GetPixel(xxl, yyl); + picc->PutPixel(xxl, yyl, color_mapped_table[rr]); + } + } + + void wremapall(color * pal1, Bitmap *picc, color * pal2) + { + __wremap_keep_transparent--; + wremap(pal1, picc, pal2); + __wremap_keep_transparent++; + } + + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/engines/ags/shared/util/wgt2allg.h b/engines/ags/shared/util/wgt2allg.h new file mode 100644 index 00000000000..8036d4ffb13 --- /dev/null +++ b/engines/ags/shared/util/wgt2allg.h @@ -0,0 +1,79 @@ +//============================================================================= +// +// Adventure Game Studio (AGS) +// +// Copyright (C) 1999-2011 Chris Jones and 2011-20xx others +// The full list of copyright holders can be found in the Copyright.txt +// file, which is part of this source code distribution. +// +// The AGS source code is provided under the Artistic License 2.0. +// A copy of this license can be found in the file License.txt and at +// http://www.opensource.org/licenses/artistic-license-2.0.php +// +//============================================================================= +// +// WGT -> Allegro portability interface +// +// wsavesprites and wloadsprites are hi-color compliant +// +//============================================================================= + +#include "core/platform.h" + +#define _WGT45_ + +#ifndef __WGT4_H +#define __WGT4_H + +#include "allegro.h" + +namespace AGS { namespace Common { class Bitmap; }} +using namespace AGS; // FIXME later + + +#if defined WGT2ALLEGRO_NOFUNCTIONS +#error WGT2ALLEGRO_NOFUNCTIONS macro is obsolete and should not be defined anymore. +#endif + +#define color RGB + +//============================================================================= + +// [IKM] 2012-09-13: this function is now defined in engine and editor separately +extern void __my_setcolor(int *ctset, int newcol, int wantColDep); + +#ifdef __cplusplus +extern "C" +{ +#endif + + extern void wsetrgb(int coll, int r, int g, int b, color * pall); + extern void wcolrotate(unsigned char start, unsigned char finish, int dir, color * pall); + + extern Common::Bitmap *wnewblock(Common::Bitmap *src, int x1, int y1, int x2, int y2); + + extern int wloadsprites(color * pall, char *filnam, Common::Bitmap ** sarray, int strt, int eend); + + extern void wputblock(Common::Bitmap *ds, int xx, int yy, Common::Bitmap *bll, int xray); + // CHECKME: temporary solution for plugin system + extern void wputblock_raw(Common::Bitmap *ds, int xx, int yy, BITMAP *bll, int xray); + extern const int col_lookups[32]; + + //extern void wsetcolor(int nval); + + extern int __wremap_keep_transparent; + extern void wremap(color * pal1, Common::Bitmap *picc, color * pal2); + extern void wremapall(color * pal1, Common::Bitmap *picc, color * pal2); + +#ifdef __cplusplus +} +#endif + +#define XRAY 1 +#define NORMAL 0 + +// archive attributes to search for - al_findfirst breaks with 0 +#define FA_SEARCH -1 + + +#endif // __WGT4_H