diff options
author | Marisa-Chan | 2014-06-13 21:43:04 +0700 |
---|---|---|
committer | Marisa-Chan | 2014-06-13 21:43:04 +0700 |
commit | 45589950c0fb1a449351e6a00ef10d42290d8bae (patch) | |
tree | 44e4eedcb7e69d5fc386155b000ed038af07251d /engines/zvision/scripting | |
parent | 48360645dcd5f8fddb135b6e31ae5cae4be8d77f (diff) | |
parent | 5c005ad3a3f1df0bc968c85c1cf0fc48e36ab0b2 (diff) | |
download | scummvm-rg350-45589950c0fb1a449351e6a00ef10d42290d8bae.tar.gz scummvm-rg350-45589950c0fb1a449351e6a00ef10d42290d8bae.tar.bz2 scummvm-rg350-45589950c0fb1a449351e6a00ef10d42290d8bae.zip |
Merge remote-tracking branch 'upstream/master' into zvision
Conflicts:
engines/zvision/animation/rlf_animation.cpp
engines/zvision/animation_control.h
engines/zvision/core/console.cpp
engines/zvision/core/events.cpp
engines/zvision/cursors/cursor.cpp
engines/zvision/cursors/cursor_manager.cpp
engines/zvision/cursors/cursor_manager.h
engines/zvision/fonts/truetype_font.cpp
engines/zvision/graphics/render_manager.cpp
engines/zvision/graphics/render_manager.h
engines/zvision/inventory/inventory_manager.h
engines/zvision/inventory_manager.h
engines/zvision/meta_animation.h
engines/zvision/module.mk
engines/zvision/scripting/actions.cpp
engines/zvision/scripting/control.h
engines/zvision/scripting/controls/animation_control.cpp
engines/zvision/scripting/controls/animation_control.h
engines/zvision/scripting/controls/input_control.cpp
engines/zvision/scripting/controls/lever_control.cpp
engines/zvision/scripting/controls/timer_node.cpp
engines/zvision/scripting/controls/timer_node.h
engines/zvision/scripting/puzzle.h
engines/zvision/scripting/scr_file_handling.cpp
engines/zvision/scripting/script_manager.cpp
engines/zvision/scripting/script_manager.h
engines/zvision/sidefx.cpp
engines/zvision/sound/zork_raw.cpp
engines/zvision/sound/zork_raw.h
engines/zvision/video/video.cpp
engines/zvision/video/zork_avi_decoder.h
engines/zvision/zvision.cpp
engines/zvision/zvision.h
Diffstat (limited to 'engines/zvision/scripting')
-rw-r--r-- | engines/zvision/scripting/actions.cpp | 681 | ||||
-rw-r--r-- | engines/zvision/scripting/actions.h | 418 | ||||
-rw-r--r-- | engines/zvision/scripting/control.cpp | 130 | ||||
-rw-r--r-- | engines/zvision/scripting/control.h | 125 | ||||
-rw-r--r-- | engines/zvision/scripting/controls/input_control.cpp | 143 | ||||
-rw-r--r-- | engines/zvision/scripting/controls/input_control.h | 64 | ||||
-rw-r--r-- | engines/zvision/scripting/controls/lever_control.cpp | 382 | ||||
-rw-r--r-- | engines/zvision/scripting/controls/lever_control.h | 119 | ||||
-rw-r--r-- | engines/zvision/scripting/controls/push_toggle_control.cpp | 141 | ||||
-rw-r--r-- | engines/zvision/scripting/controls/push_toggle_control.h | 79 | ||||
-rw-r--r-- | engines/zvision/scripting/controls/timer_node.cpp | 80 | ||||
-rw-r--r-- | engines/zvision/scripting/controls/timer_node.h | 59 | ||||
-rw-r--r-- | engines/zvision/scripting/puzzle.h | 81 | ||||
-rw-r--r-- | engines/zvision/scripting/scr_file_handling.cpp | 353 | ||||
-rw-r--r-- | engines/zvision/scripting/script_manager.cpp | 720 | ||||
-rw-r--r-- | engines/zvision/scripting/script_manager.h | 333 |
16 files changed, 3908 insertions, 0 deletions
diff --git a/engines/zvision/scripting/actions.cpp b/engines/zvision/scripting/actions.cpp new file mode 100644 index 0000000000..f60a69744a --- /dev/null +++ b/engines/zvision/scripting/actions.cpp @@ -0,0 +1,681 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/actions.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" +#include "zvision/render_manager.h" +#include "zvision/zork_raw.h" +#include "zvision/zork_avi_decoder.h" +#include "zvision/timer_node.h" +#include "zvision/music_node.h" +#include "zvision/syncsound_node.h" +#include "zvision/animation_node.h" +#include "zvision/ttytext_node.h" + +#include "common/file.h" + +#include "audio/decoders/wave.h" + + +namespace ZVision { + +////////////////////////////////////////////////////////////////////////////// +// ActionAdd +////////////////////////////////////////////////////////////////////////////// + +ActionAdd::ActionAdd(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + sscanf(line.c_str(), "%u,%d", &_key, &_value); +} + +bool ActionAdd::execute() { + _engine->getScriptManager()->setStateValue(_key, _engine->getScriptManager()->getStateValue(_key) + _value); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionAssign +////////////////////////////////////////////////////////////////////////////// + +ActionAssign::ActionAssign(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char buf[64]; + memset(buf, 0, 64); + sscanf(line.c_str(), "%u, %s", &_key, buf); + _value = new ValueSlot(_engine->getScriptManager(), buf); +} + +ActionAssign::~ActionAssign() { + if (_value) + delete _value; +} + +bool ActionAssign::execute() { + _engine->getScriptManager()->setStateValue(_key, _value->getValue()); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionAttenuate +////////////////////////////////////////////////////////////////////////////// + +ActionAttenuate::ActionAttenuate(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + sscanf(line.c_str(), "%u, %d", &_key, &_attenuation); +} + +bool ActionAttenuate::execute() { + SideFX *fx = _engine->getScriptManager()->getSideFX(_key); + if (fx && fx->getType() == SideFX::SIDEFX_AUDIO) { + MusicNode *mus = (MusicNode *)fx; + mus->setVolume(255 - (abs(_attenuation) >> 7)); + } + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionChangeLocation +////////////////////////////////////////////////////////////////////////////// + +ActionChangeLocation::ActionChangeLocation(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + sscanf(line.c_str(), "%c, %c, %c%c, %u", &_world, &_room, &_node, &_view, &_offset); +} + +bool ActionChangeLocation::execute() { + // We can't directly call ScriptManager::ChangeLocationIntern() because doing so clears all the Puzzles, and thus would corrupt the current puzzle checking + _engine->getScriptManager()->changeLocation(_world, _room, _node, _view, _offset); + // Tell the puzzle system to stop checking any more puzzles + return false; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionCrossfade +////////////////////////////////////////////////////////////////////////////// + +ActionCrossfade::ActionCrossfade(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + sscanf(line.c_str(), + "%u %u %d %d %d %d %d", + &_keyOne, &_keyTwo, &_oneStartVolume, &_twoStartVolume, &_oneEndVolume, &_twoEndVolume, &_timeInMillis); +} + +bool ActionCrossfade::execute() { + if (_keyOne) { + SideFX *fx = _engine->getScriptManager()->getSideFX(_keyOne); + if (fx && fx->getType() == SideFX::SIDEFX_AUDIO) { + MusicNode *mus = (MusicNode *)fx; + if (_oneStartVolume >= 0) + mus->setVolume((_oneStartVolume * 255) / 100); + + mus->setFade(_timeInMillis, (_oneEndVolume * 255) / 100); + } + } + + if (_keyTwo) { + SideFX *fx = _engine->getScriptManager()->getSideFX(_keyTwo); + if (fx && fx->getType() == SideFX::SIDEFX_AUDIO) { + MusicNode *mus = (MusicNode *)fx; + if (_twoStartVolume >= 0) + mus->setVolume((_twoStartVolume * 255) / 100); + + mus->setFade(_timeInMillis, (_twoEndVolume * 255) / 100); + } + } + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionDisableControl +////////////////////////////////////////////////////////////////////////////// + +ActionDisableControl::ActionDisableControl(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionDisableControl::execute() { + _engine->getScriptManager()->setStateFlag(_key, Puzzle::DISABLED); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionEnableControl +////////////////////////////////////////////////////////////////////////////// + +ActionEnableControl::ActionEnableControl(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionEnableControl::execute() { + _engine->getScriptManager()->unsetStateFlag(_key, Puzzle::DISABLED); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionInventory +////////////////////////////////////////////////////////////////////////////// + +ActionInventory::ActionInventory(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char buf[25]; + sscanf(line.c_str(), "%25s %d", buf, &_key); + + if (strcmp(buf, "add") == 0) { + _type = 0; + } else if (strcmp(buf, "addi") == 0) { + _type = 1; + } else if (strcmp(buf, "drop") == 0) { + _type = 2; + } else if (strcmp(buf, "dropi") == 0) { + _type = 3; + } else if (strcmp(buf, "cycle") == 0) { + _type = 4; + } + +} + +bool ActionInventory::execute() { + switch (_type) { + case 0: // add + _engine->getScriptManager()->invertory_add(_key); + break; + case 1: // addi + _engine->getScriptManager()->invertory_add(_engine->getScriptManager()->getStateValue(_key)); + break; + case 2: // drop + if (_key >= 0) + _engine->getScriptManager()->invertory_drop(_key); + else + _engine->getScriptManager()->invertory_drop(_engine->getScriptManager()->getStateValue(StateKey_InventoryItem)); + break; + case 3: // dropi + _engine->getScriptManager()->invertory_drop(_engine->getScriptManager()->getStateValue(_key)); + break; + case 4: // cycle + _engine->getScriptManager()->invertory_cycle(); + break; + default: + break; + } + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionKill +////////////////////////////////////////////////////////////////////////////// + +ActionKill::ActionKill(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + _type = 0; + char keytype[25]; + sscanf(line.c_str(), "%25s", keytype); + if (keytype[0] == '"') { + if (!scumm_stricmp(keytype, "\"ANIM\"")) + _type = SideFX::SIDEFX_ANIM; + else if (!scumm_stricmp(keytype, "\"AUDIO\"")) + _type = SideFX::SIDEFX_AUDIO; + else if (!scumm_stricmp(keytype, "\"DISTORT\"")) + _type = SideFX::SIDEFX_DISTORT; + else if (!scumm_stricmp(keytype, "\"PANTRACK\"")) + _type = SideFX::SIDEFX_PANTRACK; + else if (!scumm_stricmp(keytype, "\"REGION\"")) + _type = SideFX::SIDEFX_REGION; + else if (!scumm_stricmp(keytype, "\"TIMER\"")) + _type = SideFX::SIDEFX_TIMER; + else if (!scumm_stricmp(keytype, "\"TTYTEXT\"")) + _type = SideFX::SIDEFX_TTYTXT; + else if (!scumm_stricmp(keytype, "\"ALL\"")) + _type = SideFX::SIDEFX_ALL; + } else + _key = atoi(keytype); +} + +bool ActionKill::execute() { + if (_type) + _engine->getScriptManager()->killSideFxType((SideFX::SideFXType)_type); + else + _engine->getScriptManager()->killSideFx(_key); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionMusic +////////////////////////////////////////////////////////////////////////////// + +ActionMusic::ActionMusic(ZVision *engine, int32 slotkey, const Common::String &line, bool global) : + ResultAction(engine, slotkey), + _volume(255), + _universe(global) { + uint type; + char fileNameBuffer[25]; + uint loop; + uint volume = 255; + + sscanf(line.c_str(), "%u %25s %u %u", &type, fileNameBuffer, &loop, &volume); + + // type 4 are midi sound effect files + if (type == 4) { + _soundType = Audio::Mixer::kSFXSoundType; + _fileName = Common::String::format("midi/%s/%u.wav", fileNameBuffer, loop); + _loop = false; + } else { + // TODO: See what the other types are so we can specify the correct Mixer::SoundType. In the meantime use kPlainSoundType + _soundType = Audio::Mixer::kPlainSoundType; + _fileName = Common::String(fileNameBuffer); + _loop = loop == 1 ? true : false; + } + + // Volume is optional. If it doesn't appear, assume full volume + if (volume != 255) { + // Volume in the script files is mapped to [0, 100], but the ScummVM mixer uses [0, 255] + _volume = volume * 255 / 100; + } +} + +ActionMusic::~ActionMusic() { + if (!_universe) + _engine->getScriptManager()->killSideFx(_slotkey); +} + +bool ActionMusic::execute() { + if (_engine->getScriptManager()->getSideFX(_slotkey)) + return true; + + if (!_engine->getSearchManager()->hasFile(_fileName)) + return true; + + _engine->getScriptManager()->addSideFX(new MusicNode(_engine, _slotkey, _fileName, _loop, _volume)); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionPanTrack +////////////////////////////////////////////////////////////////////////////// + +ActionPanTrack::ActionPanTrack(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey), + _pos(0), + _mus_slot(0) { + + sscanf(line.c_str(), "%u %d", &_mus_slot, &_pos); +} + +ActionPanTrack::~ActionPanTrack() { + _engine->getScriptManager()->killSideFx(_slotkey); +} + +bool ActionPanTrack::execute() { + if (_engine->getScriptManager()->getSideFX(_slotkey)) + return true; + + _engine->getScriptManager()->addSideFX(new PanTrackNode(_engine, _slotkey, _mus_slot, _pos)); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionPreloadAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionPreloadAnimation::ActionPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char fileName[25]; + + // The two %*u are always 0 and dont seem to have a use + sscanf(line.c_str(), "%25s %*u %*u %d %d", fileName, &_mask, &_framerate); + + if (_mask > 0) { + byte r, g, b; + Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0).colorToRGB(_mask, r, g, b); + _mask = _engine->_pixelFormat.RGBToColor(r, g, b); + } + + _fileName = Common::String(fileName); +} + +ActionPreloadAnimation::~ActionPreloadAnimation() { + _engine->getScriptManager()->deleteSideFx(_slotkey); +} + +bool ActionPreloadAnimation::execute() { + AnimationNode *nod = (AnimationNode *)_engine->getScriptManager()->getSideFX(_slotkey); + + if (!nod) { + nod = new AnimationNode(_engine, _slotkey, _fileName, _mask, _framerate, false); + _engine->getScriptManager()->addSideFX(nod); + } else + nod->stop(); + _engine->getScriptManager()->setStateValue(_slotkey, 2); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionPlayAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionPlayAnimation::ActionPlayAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char fileName[25]; + + // The two %*u are always 0 and dont seem to have a use + sscanf(line.c_str(), + "%25s %u %u %u %u %u %u %d %*u %*u %d %d", + fileName, &_x, &_y, &_x2, &_y2, &_start, &_end, &_loopCount, &_mask, &_framerate); + + if (_mask > 0) { + byte r, g, b; + Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0).colorToRGB(_mask, r, g, b); + _mask = _engine->_pixelFormat.RGBToColor(r, g, b); + } + + _fileName = Common::String(fileName); +} + +ActionPlayAnimation::~ActionPlayAnimation() { + _engine->getScriptManager()->deleteSideFx(_slotkey); +} + +bool ActionPlayAnimation::execute() { + AnimationNode *nod = (AnimationNode *)_engine->getScriptManager()->getSideFX(_slotkey); + + if (!nod) { + nod = new AnimationNode(_engine, _slotkey, _fileName, _mask, _framerate); + _engine->getScriptManager()->addSideFX(nod); + } else + nod->stop(); + + if (nod) + nod->addPlayNode(_slotkey, _x, _y, _x2, _y2, _start, _end, _loopCount); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionPlayPreloadAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionPlayPreloadAnimation::ActionPlayPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + sscanf(line.c_str(), + "%u %u %u %u %u %u %u %u", + &_controlKey, &_x1, &_y1, &_x2, &_y2, &_startFrame, &_endFrame, &_loopCount); +} + +bool ActionPlayPreloadAnimation::execute() { + AnimationNode *nod = (AnimationNode *)_engine->getScriptManager()->getSideFX(_controlKey); + + if (nod) + nod->addPlayNode(_slotkey, _x1, _y1, _x2, _y2, _startFrame, _endFrame, _loopCount); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionQuit +////////////////////////////////////////////////////////////////////////////// + +bool ActionQuit::execute() { + _engine->quitGame(); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionRandom +////////////////////////////////////////////////////////////////////////////// + +ActionRandom::ActionRandom(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char max_buf[64]; + memset(max_buf, 0, 64); + sscanf(line.c_str(), "%s", max_buf); + _max = new ValueSlot(_engine->getScriptManager(), max_buf); +} + +ActionRandom::~ActionRandom() { + if (_max) + delete _max; +} + +bool ActionRandom::execute() { + uint randNumber = _engine->getRandomSource()->getRandomNumber(_max->getValue()); + _engine->getScriptManager()->setStateValue(_slotkey, randNumber); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionSetPartialScreen +////////////////////////////////////////////////////////////////////////////// + +ActionSetPartialScreen::ActionSetPartialScreen(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char fileName[25]; + int color; + + sscanf(line.c_str(), "%u %u %25s %*u %d", &_x, &_y, fileName, &color); + + _fileName = Common::String(fileName); + + if (color >= 0) { + byte r, g, b; + Graphics::PixelFormat(2, 5, 5, 5, 0, 10, 5, 0, 0).colorToRGB(color, r, g, b); + _backgroundColor = _engine->_pixelFormat.RGBToColor(r, g, b); + } else { + _backgroundColor = color; + } + + if (color > 65535) { + warning("Background color for ActionSetPartialScreen is bigger than a uint16"); + } +} + +bool ActionSetPartialScreen::execute() { + RenderManager *renderManager = _engine->getRenderManager(); + if (_backgroundColor >= 0) + renderManager->renderImageToBackground(_fileName, _x, _y, _backgroundColor); + else + renderManager->renderImageToBackground(_fileName, _x, _y); + + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionSetScreen +////////////////////////////////////////////////////////////////////////////// + +ActionSetScreen::ActionSetScreen(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char fileName[25]; + sscanf(line.c_str(), "%25s", fileName); + + _fileName = Common::String(fileName); +} + +bool ActionSetScreen::execute() { + _engine->getRenderManager()->setBackgroundImage(_fileName); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionStop +////////////////////////////////////////////////////////////////////////////// + +ActionStop::ActionStop(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + _key = 0; + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionStop::execute() { + _engine->getScriptManager()->stopSideFx(_key); + return true; +} + + +////////////////////////////////////////////////////////////////////////////// +// ActionStreamVideo +////////////////////////////////////////////////////////////////////////////// + +ActionStreamVideo::ActionStreamVideo(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char fileName[25]; + uint skipline; //skipline - render video with skip every second line, not skippable. + + sscanf(line.c_str(), "%25s %u %u %u %u %u %u", fileName, &_x1, &_y1, &_x2, &_y2, &_flags, &skipline); + + _fileName = Common::String(fileName); + _skippable = true; +} + +bool ActionStreamVideo::execute() { + ZorkAVIDecoder decoder; + Common::File *_file = _engine->getSearchManager()->openFile(_fileName); + + if (_file) { + if (!decoder.loadStream(_file)) { + return true; + } + + Common::Rect destRect = Common::Rect(_x1, _y1, _x2 + 1, _y2 + 1); + + Common::String subname = _fileName; + subname.setChar('s', subname.size() - 3); + subname.setChar('u', subname.size() - 2); + subname.setChar('b', subname.size() - 1); + + Subtitle *sub = NULL; + + if (_engine->getSearchManager()->hasFile(subname)) + sub = new Subtitle(_engine, subname); + + _engine->playVideo(decoder, destRect, _skippable, sub); + + if (sub) + delete sub; + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionSyncSound +////////////////////////////////////////////////////////////////////////////// + +ActionSyncSound::ActionSyncSound(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char fileName[25]; + int not_used; + + sscanf(line.c_str(), "%d %d %25s", &_syncto, ¬_used, fileName); + + _fileName = Common::String(fileName); +} + +bool ActionSyncSound::execute() { + SideFX *fx = _engine->getScriptManager()->getSideFX(_syncto); + if (!fx) + return true; + + if (!(fx->getType() & SideFX::SIDEFX_ANIM)) + return true; + + AnimationNode *animnode = (AnimationNode *)fx; + if (animnode->getFrameDelay() > 200) // Hack for fix incorrect framedelay in some animpreload + animnode->setNewFrameDelay(66); // ~15fps + + _engine->getScriptManager()->addSideFX(new SyncSoundNode(_engine, _slotkey, _fileName, _syncto)); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionTimer +////////////////////////////////////////////////////////////////////////////// + +ActionTimer::ActionTimer(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char time_buf[64]; + memset(time_buf, 0, 64); + sscanf(line.c_str(), "%s", time_buf); + _time = new ValueSlot(_engine->getScriptManager(), time_buf); +} + +ActionTimer::~ActionTimer() { + if (_time) + delete _time; + _engine->getScriptManager()->killSideFx(_slotkey); +} + +bool ActionTimer::execute() { + if (_engine->getScriptManager()->getSideFX(_slotkey)) + return true; + _engine->getScriptManager()->addSideFX(new TimerNode(_engine, _slotkey, _time->getValue())); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionTtyText +////////////////////////////////////////////////////////////////////////////// + +ActionTtyText::ActionTtyText(ZVision *engine, int32 slotkey, const Common::String &line) : + ResultAction(engine, slotkey) { + char filename[64]; + int32 x1, y1, x2, y2; + sscanf(line.c_str(), "%d %d %d %d %s %u", &x1, &y1, &x2, &y2, filename, &_delay); + _r = Common::Rect(x1, y1, x2, y2); + _filename = Common::String(filename); +} + +ActionTtyText::~ActionTtyText() { + _engine->getScriptManager()->killSideFx(_slotkey); +} + +bool ActionTtyText::execute() { + if (_engine->getScriptManager()->getSideFX(_slotkey)) + return true; + _engine->getScriptManager()->addSideFX(new ttyTextNode(_engine, _slotkey, _filename, _r, _delay)); + return true; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/actions.h b/engines/zvision/scripting/actions.h new file mode 100644 index 0000000000..c60aedcdf3 --- /dev/null +++ b/engines/zvision/scripting/actions.h @@ -0,0 +1,418 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_ACTIONS_H +#define ZVISION_ACTIONS_H + +#include "common/str.h" +#include "common/rect.h" + +#include "audio/mixer.h" + + +namespace ZVision { + +// Forward declaration of ZVision. This file is included before ZVision is declared +class ZVision; +class ValueSlot; + +/** + * The base class that represents any action that a Puzzle can take. + * This class is purely virtual. + */ +class ResultAction { +public: + ResultAction(ZVision *engine, int32 slotkey) : _engine(engine), _slotkey(slotkey) {} + virtual ~ResultAction() {} + /** + * This is called by the script system whenever a Puzzle's criteria are found to be true. + * It should execute any necessary actions and return a value indicating whether the script + * system should continue to test puzzles. In 99% of cases this will be 'true'. + * + * @param engine A pointer to the base engine so the ResultAction can access all the necessary methods + * @return Should the script system continue to test any remaining puzzles (true) or immediately break and go on to the next frame (false) + */ + virtual bool execute() = 0; +protected: + ZVision *_engine; + int32 _slotkey; +}; + + +// The different types of actions +// DEBUG, +// DISABLE_CONTROL, +// DISABLE_VENUS, +// DISPLAY_MESSAGE, +// DISSOLVE, +// DISTORT, +// ENABLE_CONTROL, +// FLUSH_MOUSE_EVENTS, +// INVENTORY, +// KILL, +// MENU_BAR_ENABLE, +// MUSIC, +// PAN_TRACK, +// PLAY_PRELOAD, +// PREFERENCES, +// QUIT, +// RANDOM, +// REGION, +// RESTORE_GAME, +// ROTATE_TO, +// SAVE_GAME, +// SET_PARTIAL_SCREEN, +// SET_SCREEN, +// SET_VENUS, +// STOP, +// STREAM_VIDEO, +// SYNC_SOUND, +// TTY_TEXT, +// UNIVERSE_MUSIC, + +class ActionAdd : public ResultAction { +public: + ActionAdd(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; + int _value; +}; + +class ActionAssign : public ResultAction { +public: + ActionAssign(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionAssign(); + bool execute(); + +private: + uint32 _key; + ValueSlot *_value; +}; + +class ActionAttenuate : public ResultAction { +public: + ActionAttenuate(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; + int32 _attenuation; +}; + +class ActionChangeLocation : public ResultAction { +public: + ActionChangeLocation(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + char _world; + char _room; + char _node; + char _view; + uint32 _offset; +}; + +class ActionCrossfade : public ResultAction { +public: + ActionCrossfade(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _keyOne; + uint32 _keyTwo; + int32 _oneStartVolume; + int32 _twoStartVolume; + int32 _oneEndVolume; + int32 _twoEndVolume; + int32 _timeInMillis; +}; + +class ActionDebug : public ResultAction { +public: + ActionDebug(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: +}; + +class ActionDelayRender : public ResultAction { +public: + ActionDelayRender(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + // TODO: Check if this should actually be frames or if it should be milliseconds/seconds + uint32 framesToDelay; +}; + +class ActionDisableControl : public ResultAction { +public: + ActionDisableControl(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; +}; + +class ActionDisableVenus : public ResultAction { +public: + ActionDisableVenus(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: +}; + +class ActionDisplayMessage : public ResultAction { +public: + ActionDisplayMessage(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: +}; + +class ActionDissolve : public ResultAction { +public: + ActionDissolve(ZVision *engine); + bool execute(); +}; + +class ActionDistort : public ResultAction { +public: + ActionDistort(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: +}; + +class ActionEnableControl : public ResultAction { +public: + ActionEnableControl(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; +}; + +class ActionInventory : public ResultAction { +public: + ActionInventory(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); +private: + uint8 _type; + int32 _key; +}; + +class ActionKill : public ResultAction { +public: + ActionKill(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; + uint32 _type; +}; + +class ActionMusic : public ResultAction { +public: + ActionMusic(ZVision *engine, int32 slotkey, const Common::String &line, bool global); + ~ActionMusic(); + bool execute(); + +private: + uint32 _key; + Audio::Mixer::SoundType _soundType; + Common::String _fileName; + bool _loop; + byte _volume; + bool _universe; +}; + +class ActionPanTrack : public ResultAction { +public: + ActionPanTrack(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionPanTrack(); + bool execute(); + +private: + int32 _pos; + uint32 _mus_slot; +}; + +class ActionPlayAnimation : public ResultAction { +public: + ActionPlayAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionPlayAnimation(); + bool execute(); + +private: + uint32 _key; + Common::String _fileName; + uint32 _x; + uint32 _y; + uint32 _x2; + uint32 _y2; + uint32 _start; + uint32 _end; + int32 _mask; + int32 _framerate; + int32 _loopCount; +}; + +class ActionPlayPreloadAnimation : public ResultAction { +public: + ActionPlayPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _animationKey; + uint32 _controlKey; + uint32 _x1; + uint32 _y1; + uint32 _x2; + uint32 _y2; + uint _startFrame; + uint _endFrame; + uint _loopCount; +}; + +class ActionPreloadAnimation : public ResultAction { +public: + ActionPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionPreloadAnimation(); + bool execute(); + +private: + uint32 _key; + Common::String _fileName; + int32 _mask; + int32 _framerate; +}; + +class ActionQuit : public ResultAction { +public: + ActionQuit(ZVision *engine, int32 slotkey) : ResultAction(engine, slotkey) {} + bool execute(); +}; + +// TODO: See if this exists in ZGI. It doesn't in ZNem +class ActionUnloadAnimation : public ResultAction { +public: + ActionUnloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); +}; + +class ActionRandom : public ResultAction { +public: + ActionRandom(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionRandom(); + bool execute(); + +private: + uint32 _key; + ValueSlot *_max; +}; + +class ActionSetPartialScreen : public ResultAction { +public: + ActionSetPartialScreen(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint _x; + uint _y; + Common::String _fileName; + int32 _backgroundColor; +}; + +class ActionSetScreen : public ResultAction { +public: + ActionSetScreen(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + Common::String _fileName; +}; + +class ActionStop : public ResultAction { +public: + ActionStop(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; +}; + +class ActionStreamVideo : public ResultAction { +public: + ActionStreamVideo(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + enum { + DIFFERENT_DIMENSIONS = 0x1 // 0x1 flags that the destRect dimensions are different from the original video dimensions + }; + + Common::String _fileName; + uint _x1; + uint _y1; + uint _x2; + uint _y2; + uint _flags; + bool _skippable; +}; + +class ActionSyncSound : public ResultAction { +public: + ActionSyncSound(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + int _syncto; + Common::String _fileName; +}; + +class ActionTimer : public ResultAction { +public: + ActionTimer(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionTimer(); + bool execute(); +private: + uint32 _key; + ValueSlot *_time; +}; + +class ActionTtyText : public ResultAction { +public: + ActionTtyText(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionTtyText(); + bool execute(); + +private: + Common::String _filename; + uint32 _delay; + Common::Rect _r; +}; +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/control.cpp b/engines/zvision/scripting/control.cpp new file mode 100644 index 0000000000..ae717d6f8d --- /dev/null +++ b/engines/zvision/scripting/control.cpp @@ -0,0 +1,130 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/control.h" + +#include "zvision/zvision.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/utility/utility.h" + +#include "common/stream.h" + + +namespace ZVision { + +void Control::parseFlatControl(ZVision *engine) { + engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT); +} + +void Control::parsePanoramaControl(ZVision *engine, Common::SeekableReadStream &stream) { + RenderTable *renderTable = engine->getRenderManager()->getRenderTable(); + renderTable->setRenderState(RenderTable::PANORAMA); + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("angle*", true)) { + float fov; + sscanf(line.c_str(), "angle(%f)", &fov); + renderTable->setPanoramaFoV(fov); + } else if (line.matchString("linscale*", true)) { + float scale; + sscanf(line.c_str(), "linscale(%f)", &scale); + renderTable->setPanoramaScale(scale); + } else if (line.matchString("reversepana*", true)) { + uint reverse; + sscanf(line.c_str(), "reversepana(%u)", &reverse); + if (reverse == 1) { + renderTable->setPanoramaReverse(true); + } + } else if (line.matchString("zeropoint*", true)) { + // TODO: Implement + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + renderTable->generateRenderTable(); +} + +void Control::parseTiltControl(ZVision *engine, Common::SeekableReadStream &stream) { + RenderTable *renderTable = engine->getRenderManager()->getRenderTable(); + renderTable->setRenderState(RenderTable::TILT); + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("angle*", true)) { + float fov; + sscanf(line.c_str(), "angle(%f)", &fov); + renderTable->setTiltFoV(fov); + } else if (line.matchString("linscale*", true)) { + float scale; + sscanf(line.c_str(), "linscale(%f)", &scale); + renderTable->setTiltScale(scale); + } else if (line.matchString("reversepana*", true)) { + uint reverse; + sscanf(line.c_str(), "reversepana(%u)", &reverse); + if (reverse == 1) { + renderTable->setTiltReverse(true); + } + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + renderTable->generateRenderTable(); +} + +void Control::getParams(Common::String &input_str, Common::String ¶meter, Common::String &values) { + const char *chrs = input_str.c_str(); + uint lbr; + + for (lbr = 0; lbr < input_str.size(); lbr++) + if (chrs[lbr] == '(') + break; + + if (lbr >= input_str.size()) + return; + + uint rbr; + + for (rbr = lbr + 1; rbr < input_str.size(); rbr++) + if (chrs[rbr] == ')') + break; + + if (rbr >= input_str.size()) + return; + + parameter = Common::String(chrs, chrs + lbr); + values = Common::String(chrs + lbr + 1, chrs + rbr); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/control.h b/engines/zvision/scripting/control.h new file mode 100644 index 0000000000..93ad5f5df1 --- /dev/null +++ b/engines/zvision/scripting/control.h @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_CONTROL_H +#define ZVISION_CONTROL_H + +#include "common/keyboard.h" +#include "common/str.h" + + +namespace Common { +class SeekableReadStream; +struct Point; +class WriteStream; +} + +namespace ZVision { + +class ZVision; + +class Control { +public: + Control() : _engine(0), _key(0) {} + Control(ZVision *engine, uint32 key) : _engine(engine), _key(key) {} + virtual ~Control() {} + + uint32 getKey() { + return _key; + } + + virtual void focus() {} + virtual void unfocus() {} + /** + * Called when LeftMouse is pushed. Default is NOP. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + virtual bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + return false; + } + /** + * Called when LeftMouse is lifted. Default is NOP. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + virtual bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + return false; + } + /** + * Called on every MouseMove. Default is NOP. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + * @return Was the cursor changed? + */ + virtual bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + return false; + } + /** + * Called when a key is pressed. Default is NOP. + * + * @param keycode The key that was pressed + */ + virtual void onKeyDown(Common::KeyState keyState) {} + /** + * Called when a key is released. Default is NOP. + * + * @param keycode The key that was pressed + */ + virtual void onKeyUp(Common::KeyState keyState) {} + /** + * Processes the node given the deltaTime since last frame. Default is NOP. + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + virtual bool process(uint32 deltaTimeInMillis) { + return false; + } + +protected: + ZVision *_engine; + uint32 _key; + + void getParams(Common::String &input_str, Common::String ¶meter, Common::String &values); +// Static member functions +public: + static void parseFlatControl(ZVision *engine); + static void parsePanoramaControl(ZVision *engine, Common::SeekableReadStream &stream); + static void parseTiltControl(ZVision *engine, Common::SeekableReadStream &stream); +}; + +// TODO: Implement InputControl +// TODO: Implement SaveControl +// TODO: Implement SlotControl +// TODO: Implement SafeControl +// TODO: Implement FistControl +// TODO: Implement HotMovieControl +// TODO: Implement PaintControl +// TODO: Implement TilterControl + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/input_control.cpp b/engines/zvision/scripting/controls/input_control.cpp new file mode 100644 index 0000000000..a366e06923 --- /dev/null +++ b/engines/zvision/scripting/controls/input_control.cpp @@ -0,0 +1,143 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/input_control.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" +#include "zvision/string_manager.h" +#include "zvision/render_manager.h" +#include "zvision/utility.h" + +#include "common/str.h" +#include "common/stream.h" +#include "common/rect.h" + + +namespace ZVision { + +InputControl::InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key), + _nextTabstop(0), + _focused(false), + _textChanged(false), + _cursorOffset(0) { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("*rectangle*", true)) { + int x1; + int y1; + int x2; + int y2; + + sscanf(line.c_str(), "%*[^(](%d %d %d %d)", &x1, &y1, &x2, &y2); + + _textRectangle = Common::Rect(x1, y1, x2, y2); + } else if (line.matchString("*aux_hotspot*", true)) { + int x1; + int y1; + int x2; + int y2; + + sscanf(line.c_str(), "%*[^(](%d %d %d %d)", &x1, &y1, &x2, &y2); + + _headerRectangle = Common::Rect(x1, y1, x2, y2); + } else if (line.matchString("*string_init*", true)) { + uint fontFormatNumber; + + sscanf(line.c_str(), "%*[^(](%u)", &fontFormatNumber); + + _textStyle = _engine->getStringManager()->getTextStyle(fontFormatNumber); + } else if (line.matchString("*next_tabstop*", true)) { + sscanf(line.c_str(), "%*[^(](%u)", &_nextTabstop); + } else if (line.matchString("*cursor_animation*", true)) { + char fileName[25]; + + sscanf(line.c_str(), "%*[^(](%25s %*u)", fileName); + + _cursorAnimationFileName = Common::String(fileName); + } else if (line.matchString("*cursor_dimensions*", true)) { + // Ignore, use the dimensions in the animation file + } else if (line.matchString("*cursor_animation_frames*", true)) { + // Ignore, use the frame count in the animation file + } else if (line.matchString("*focus*", true)) { + _focused = true; + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } +} + +bool InputControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + _engine->getScriptManager()->focusControl(_key); + return false; +} + +void InputControl::onKeyDown(Common::KeyState keyState) { + if (!_focused) { + return; + } + + if (keyState.keycode == Common::KEYCODE_BACKSPACE) { + _currentInputText.deleteLastChar(); + } else if (keyState.keycode == Common::KEYCODE_TAB) { + _focused = false; + // Focus the next input control + _engine->getScriptManager()->focusControl(_nextTabstop); + } else { + // Otherwise, append the new character to the end of the current text + + uint16 asciiValue = keyState.ascii; + // We only care about text values + if (asciiValue >= 32 && asciiValue <= 126) { + _currentInputText += (char)asciiValue; + _textChanged = true; + } + } +} + +bool InputControl::process(uint32 deltaTimeInMillis) { + if (!_focused) { + return false; + } + + // First see if we need to render the text + if (_textChanged) { + // Blit the text using the RenderManager + //Common::Rect destRect = _engine->getRenderManager()->renderTextToWorkingWindow(_key, _currentInputText, _textStyle.font, _textRectangle.left, _textRectangle.top, _textStyle.color, _textRectangle.width()); + + //_cursorOffset = destRect.left - _textRectangle.left; + } + + // Render the next frame of the animation + // TODO: Implement animation handling + + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/input_control.h b/engines/zvision/scripting/controls/input_control.h new file mode 100644 index 0000000000..4a63f228a0 --- /dev/null +++ b/engines/zvision/scripting/controls/input_control.h @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_INPUT_CONTROL_H +#define ZVISION_INPUT_CONTROL_H + +#include "zvision/scripting/control.h" +#include "zvision/strings/string_manager.h" + +#include "common/rect.h" + + +namespace ZVision { + +class InputControl : public Control { +public: + InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + +private: + Common::Rect _textRectangle; + Common::Rect _headerRectangle; + StringManager::TextStyle _textStyle; + uint32 _nextTabstop; + Common::String _cursorAnimationFileName; + bool _focused; + + Common::String _currentInputText; + bool _textChanged; + uint _cursorOffset; + +public: + void focus() { + _focused = true; + } + void unfocus() { + _focused = false; + } + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + void onKeyDown(Common::KeyState keyState); + bool process(uint32 deltaTimeInMillis); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/lever_control.cpp b/engines/zvision/scripting/controls/lever_control.cpp new file mode 100644 index 0000000000..f68f256229 --- /dev/null +++ b/engines/zvision/scripting/controls/lever_control.cpp @@ -0,0 +1,382 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/lever_control.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" +#include "zvision/render_manager.h" +#include "zvision/cursor_manager.h" +#include "zvision/meta_animation.h" +#include "zvision/utility.h" + +#include "common/stream.h" +#include "common/file.h" +#include "common/tokenizer.h" +#include "common/system.h" + +#include "graphics/surface.h" + + +namespace ZVision { + +LeverControl::LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key), + _frameInfo(0), + _frameCount(0), + _startFrame(0), + _currentFrame(0), + _lastRenderedFrame(0), + _mouseIsCaptured(false), + _isReturning(false), + _accumulatedTime(0), + _returnRoutesCurrentFrame(0) { + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("*descfile*", true)) { + char levFileName[25]; + sscanf(line.c_str(), "%*[^(](%25[^)])", levFileName); + + parseLevFile(levFileName); + } else if (line.matchString("*cursor*", true)) { + char cursorName[25]; + sscanf(line.c_str(), "%*[^(](%25[^)])", cursorName); + + _cursorName = Common::String(cursorName); + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + renderFrame(_currentFrame); +} + +LeverControl::~LeverControl() { + if (_animation) + delete _animation; + + delete[] _frameInfo; +} + +void LeverControl::parseLevFile(const Common::String &fileName) { + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + warning("LEV file %s could could be opened", fileName.c_str()); + return; + } + + Common::String line; + + while (!file.eos()) { + line = file.readLine(); + + if (line.matchString("*animation_id*", true)) { + // Not used + } else if (line.matchString("*filename*", true)) { + char fileNameBuffer[25]; + sscanf(line.c_str(), "%*[^:]:%25[^~]~", fileNameBuffer); + + Common::String animationFileName(fileNameBuffer); + + if (animationFileName.hasSuffix(".avi") || animationFileName.hasSuffix(".rlf")) + _animation = new MetaAnimation(animationFileName, _engine); + + } else if (line.matchString("*skipcolor*", true)) { + // Not used + } else if (line.matchString("*anim_coords*", true)) { + int left, top, right, bottom; + sscanf(line.c_str(), "%*[^:]:%d %d %d %d~", &left, &top, &right, &bottom); + + _animationCoords.left = left; + _animationCoords.top = top; + _animationCoords.right = right; + _animationCoords.bottom = bottom; + } else if (line.matchString("*mirrored*", true)) { + uint mirrored; + sscanf(line.c_str(), "%*[^:]:%u~", &mirrored); + + _mirrored = mirrored == 0 ? false : true; + } else if (line.matchString("*frames*", true)) { + sscanf(line.c_str(), "%*[^:]:%u~", &_frameCount); + + _frameInfo = new FrameInfo[_frameCount]; + } else if (line.matchString("*elsewhere*", true)) { + // Not used + } else if (line.matchString("*out_of_control*", true)) { + // Not used + } else if (line.matchString("*start_pos*", true)) { + sscanf(line.c_str(), "%*[^:]:%u~", &_startFrame); + _currentFrame = _startFrame; + } else if (line.matchString("*hotspot_deltas*", true)) { + uint x; + uint y; + sscanf(line.c_str(), "%*[^:]:%u %u~", &x, &y); + + _hotspotDelta.x = x; + _hotspotDelta.y = y; + } else { + uint frameNumber; + uint x, y; + + line.toLowercase(); + + if (sscanf(line.c_str(), "%u:%u %u", &frameNumber, &x, &y) == 3) { + _frameInfo[frameNumber].hotspot.left = x; + _frameInfo[frameNumber].hotspot.top = y; + _frameInfo[frameNumber].hotspot.right = x + _hotspotDelta.x; + _frameInfo[frameNumber].hotspot.bottom = y + _hotspotDelta.y; + } + + Common::StringTokenizer tokenizer(line, " ^=()~"); + tokenizer.nextToken(); + tokenizer.nextToken(); + + Common::String token = tokenizer.nextToken(); + while (!tokenizer.empty()) { + if (token == "d") { + token = tokenizer.nextToken(); + + uint angle; + uint toFrame; + sscanf(token.c_str(), "%u,%u", &toFrame, &angle); + + _frameInfo[frameNumber].directions.push_back(Direction(angle, toFrame)); + } else if (token.hasPrefix("p")) { + // Format: P(<from> to <to>) + tokenizer.nextToken(); + tokenizer.nextToken(); + token = tokenizer.nextToken(); + uint to = atoi(token.c_str()); + + _frameInfo[frameNumber].returnRoute.push_back(to); + } + + token = tokenizer.nextToken(); + } + } + + // Don't read lines in this place because last will not be parsed. + } +} + +bool LeverControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) { + _mouseIsCaptured = true; + _lastMousePos = backgroundImageSpacePos; + } + return false; +} + +bool LeverControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_mouseIsCaptured) { + _mouseIsCaptured = false; + _engine->getScriptManager()->setStateValue(_key, _currentFrame); + + _isReturning = true; + _returnRoutesCurrentProgress = _frameInfo[_currentFrame].returnRoute.begin(); + _returnRoutesCurrentFrame = _currentFrame; + } + return false; +} + +bool LeverControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + bool cursorWasChanged = false; + + if (_mouseIsCaptured) { + // Make sure the square distance between the last point and the current point is greater than 64 + // This is a heuristic. This determines how responsive the lever is to mouse movement. + // TODO: Fiddle with the heuristic to get a good lever responsiveness 'feel' + if (_lastMousePos.sqrDist(backgroundImageSpacePos) >= 64) { + int angle = calculateVectorAngle(_lastMousePos, backgroundImageSpacePos); + _lastMousePos = backgroundImageSpacePos; + + for (Common::List<Direction>::iterator iter = _frameInfo[_currentFrame].directions.begin(); iter != _frameInfo[_currentFrame].directions.end(); ++iter) { + if (angle >= (int)iter->angle - ANGLE_DELTA && angle <= (int)iter->angle + ANGLE_DELTA) { + _currentFrame = iter->toFrame; + renderFrame(_currentFrame); + break; + } + } + } + } else if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(_engine->getCursorManager()->getCursorId(_cursorName)); + cursorWasChanged = true; + } + + return cursorWasChanged; +} + +bool LeverControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_isReturning) { + _accumulatedTime += deltaTimeInMillis; + while (_accumulatedTime >= ANIMATION_FRAME_TIME) { + _accumulatedTime -= ANIMATION_FRAME_TIME; + if (_returnRoutesCurrentFrame == *_returnRoutesCurrentProgress) { + _returnRoutesCurrentProgress++; + } + if (_returnRoutesCurrentProgress == _frameInfo[_currentFrame].returnRoute.end()) { + _isReturning = false; + _currentFrame = _returnRoutesCurrentFrame; + return false; + } + + uint toFrame = *_returnRoutesCurrentProgress; + if (_returnRoutesCurrentFrame < toFrame) { + _returnRoutesCurrentFrame++; + } else if (_returnRoutesCurrentFrame > toFrame) { + _returnRoutesCurrentFrame--; + } + + _engine->getScriptManager()->setStateValue(_key, _returnRoutesCurrentFrame); + renderFrame(_returnRoutesCurrentFrame); + } + } + + return false; +} + +int LeverControl::calculateVectorAngle(const Common::Point &pointOne, const Common::Point &pointTwo) { + // Check for the easy angles first + if (pointOne.x == pointTwo.x && pointOne.y == pointTwo.y) + return -1; // This should never happen + else if (pointOne.x == pointTwo.x) { + if (pointTwo.y < pointOne.y) + return 90; + else + return 270; + } else if (pointOne.y == pointTwo.y) { + if (pointTwo.x > pointOne.x) + return 0; + else + return 180; + } else { + // Calculate the angle with trig + int16 xDist = pointTwo.x - pointOne.x; + int16 yDist = pointTwo.y - pointOne.y; + + // Calculate the angle using arctan + // Then convert to degrees. (180 / 3.14159 = 57.2958) + int angle = int(atan((float)yDist / (float)abs(xDist)) * 57); + + // Calculate what quadrant pointTwo is in + uint quadrant = ((yDist > 0 ? 1 : 0) << 1) | (xDist < 0 ? 1 : 0); + + // Explanation of quadrants: + // + // yDist > 0 | xDist < 0 | Quadrant number + // 0 | 0 | 0 + // 0 | 1 | 1 + // 1 | 0 | 2 + // 1 | 1 | 3 + // + // Note: I know this doesn't line up with traditional mathematical quadrants + // but doing it this way allows you can use a switch and is a bit cleaner IMO. + // + // The graph below shows the 4 quadrants pointTwo can end up in as well + // as what the angle as calculated above refers to. + // Note: The calculated angle in quadrants 0 and 3 is negative + // due to arctan(-x) = -theta + // + // Origin => (pointOne.x, pointOne.y) + // * => (pointTwo.x, pointTwo.y) + // + // 90 | + // ^ | + // * | * | + // \ | / | + // \ | / | + // \ | / | + // Quadrant 1 \ | / Quadrant 0 | + // \ | / | + // \ | / | + // angle ( \|/ ) -angle | + // 180 <----------------------------------------> 0 | + // -angle ( /|\ ) angle | + // / | \ | + // / | \ | + // Quadrant 3 / | \ Quadrant 2 | + // / | \ | + // / | \ | + // / | \ | + // * | * | + // ^ | + // 270 | + + // Convert the local angles to unit circle angles + switch (quadrant) { + case 0: + angle = -angle; + break; + case 1: + angle = angle + 180; + break; + case 2: + angle = 360 - angle; + break; + case 3: + angle = 180 + angle; + break; + } + + return angle; + } +} + +void LeverControl::renderFrame(uint frameNumber) { + if (frameNumber == 0) { + _lastRenderedFrame = frameNumber; + } else if (frameNumber < _lastRenderedFrame && _mirrored) { + _lastRenderedFrame = frameNumber; + frameNumber = (_frameCount * 2) - frameNumber - 1; + } else { + _lastRenderedFrame = frameNumber; + } + + const Graphics::Surface *frameData; + int x = _animationCoords.left; + int y = _animationCoords.top; + + frameData = _animation->getFrameData(frameNumber); + if (frameData) + _engine->getRenderManager()->blitSurfaceToBkg(*frameData, x, y); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/lever_control.h b/engines/zvision/scripting/controls/lever_control.h new file mode 100644 index 0000000000..712d688523 --- /dev/null +++ b/engines/zvision/scripting/controls/lever_control.h @@ -0,0 +1,119 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_LEVER_CONTROL_H +#define ZVISION_LEVER_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/list.h" +#include "common/rect.h" + + +namespace ZVision { + +class ZorkAVIDecoder; +class MetaAnimation; + +class LeverControl : public Control { +public: + LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~LeverControl(); + +private: + + struct Direction { + Direction(uint a, uint t) : angle(a), toFrame(t) {} + + uint angle; + uint toFrame; + }; + + struct FrameInfo { + Common::Rect hotspot; + Common::List<Direction> directions; + Common::List<uint> returnRoute; + }; + + enum { + ANGLE_DELTA = 30, // How far off a mouse angle can be and still be considered valid. This is in both directions, so the total buffer zone is (2 * ANGLE_DELTA) + ANIMATION_FRAME_TIME = 30 // In millis + }; + +private: + MetaAnimation *_animation; + + Common::String _cursorName; + Common::Rect _animationCoords; + bool _mirrored; + uint _frameCount; + uint _startFrame; + Common::Point _hotspotDelta; + FrameInfo *_frameInfo; + + uint _currentFrame; + uint _lastRenderedFrame; + bool _mouseIsCaptured; + bool _isReturning; + Common::Point _lastMousePos; + Common::List<uint>::iterator _returnRoutesCurrentProgress; + uint _returnRoutesCurrentFrame; + uint32 _accumulatedTime; + +public: + bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool process(uint32 deltaTimeInMillis); + +private: + void parseLevFile(const Common::String &fileName); + /** + * Calculates the angle a vector makes with the negative y-axis + * + * 90 + * pointTwo * ^ + * \ | + * \ | + * \ | + * \ | + * angle ( \|pointOne + * 180 <-----------*-----------> 0 + * | + * | + * | + * | + * | + * ^ + * 270 + * + * @param pointOne The origin of the vector + * @param pointTwo The end of the vector + * @return The angle the vector makes with the negative y-axis + */ + static int calculateVectorAngle(const Common::Point &pointOne, const Common::Point &pointTwo); + void renderFrame(uint frameNumber); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/push_toggle_control.cpp b/engines/zvision/scripting/controls/push_toggle_control.cpp new file mode 100644 index 0000000000..16cd971ad5 --- /dev/null +++ b/engines/zvision/scripting/controls/push_toggle_control.cpp @@ -0,0 +1,141 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/controls/push_toggle_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/cursors/cursor_manager.h" +#include "zvision/utility/utility.h" + +#include "common/stream.h" + + +namespace ZVision { + +PushToggleControl::PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key), + _countTo(2), + _event(Common::EVENT_LBUTTONUP) { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + line.toLowercase(); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("*_hotspot*", true)) { + uint x; + uint y; + uint width; + uint height; + + sscanf(line.c_str(), "%*[^(](%u,%u,%u,%u)", &x, &y, &width, &height); + + _hotspot = Common::Rect(x, y, x + width, y + height); + } else if (line.matchString("cursor*", true)) { + char nameBuffer[25]; + + sscanf(line.c_str(), "%*[^(](%25[^)])", nameBuffer); + + _hoverCursor = Common::String(nameBuffer); + } else if (line.matchString("animation*", true)) { + // Not used + } else if (line.matchString("sound*", true)) { + // Not used + } else if (line.matchString("count_to*", true)) { + sscanf(line.c_str(), "%*[^(](%u)", &_countTo); + } else if (line.matchString("mouse_event*", true)) { + char nameBuffer[25]; + + sscanf(line.c_str(), "%*[^(](%25[^)])", nameBuffer); + + Common::String evntStr(nameBuffer); + if (evntStr.equalsIgnoreCase("up")) { + _event = Common::EVENT_LBUTTONUP; + } else if (evntStr.equalsIgnoreCase("down")) { + _event = Common::EVENT_LBUTTONDOWN; + } else if (evntStr.equalsIgnoreCase("double")) { + // Not used + } + } else if (line.matchString("venus_id*", true)) { + // Not used + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + if (_hotspot.isEmpty() || _hoverCursor.empty()) { + warning("Push_toggle cursor %u was parsed incorrectly", key); + } +} + +PushToggleControl::~PushToggleControl() { +} + +bool PushToggleControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_event != Common::EVENT_LBUTTONUP) + return false; + + if (_hotspot.contains(backgroundImageSpacePos)) { + int32 val = _engine->getScriptManager()->getStateValue(_key); + val = (val + 1) % _countTo; + _engine->getScriptManager()->setStateValue(_key, val); + return true; + } + return false; +} + +bool PushToggleControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_event != Common::EVENT_LBUTTONDOWN) + return false; + + if (_hotspot.contains(backgroundImageSpacePos)) { + int32 val = _engine->getScriptManager()->getStateValue(_key); + val = (val + 1) % _countTo; + _engine->getScriptManager()->setStateValue(_key, val); + return true; + } + return false; +} + +bool PushToggleControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_hotspot.contains(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(_engine->getCursorManager()->getCursorId(_hoverCursor)); + return true; + } + + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/push_toggle_control.h b/engines/zvision/scripting/controls/push_toggle_control.h new file mode 100644 index 0000000000..6ba1bd77fa --- /dev/null +++ b/engines/zvision/scripting/controls/push_toggle_control.h @@ -0,0 +1,79 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_PUSH_TOGGLE_CONTROL_H +#define ZVISION_PUSH_TOGGLE_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/rect.h" +#include "common/events.h" + + +namespace ZVision { + +class PushToggleControl : public Control { +public: + PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~PushToggleControl(); + + /** + * Called when LeftMouse is pushed. Default is NOP. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called when LeftMouse is lifted. Calls ScriptManager::setStateValue(_key, 1); + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called on every MouseMove. Tests if the mouse is inside _hotspot, and if so, sets the cursor. + * + * @param engine The base engine + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + * @return Was the cursor changed? + */ + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + +private: + /** + * The area that will trigger the event + * This is in image space coordinates, NOT screen space + */ + Common::Rect _hotspot; + /** The cursor to use when hovering over _hotspot */ + Common::String _hoverCursor; + /** Button maximal values count */ + uint _countTo; + + Common::EventType _event; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/timer_node.cpp b/engines/zvision/scripting/controls/timer_node.cpp new file mode 100644 index 0000000000..a94f6db19b --- /dev/null +++ b/engines/zvision/scripting/controls/timer_node.cpp @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/timer_node.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" + +#include "common/stream.h" + + +namespace ZVision { + +TimerNode::TimerNode(ZVision *engine, uint32 key, uint timeInSeconds) + : SideFX(engine, key, SIDEFX_TIMER) { + if (_engine->getGameId() == GID_NEMESIS) + _timeLeft = timeInSeconds * 1000; + else if (_engine->getGameId() == GID_GRANDINQUISITOR) + _timeLeft = timeInSeconds * 100; + + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 1); +} + +TimerNode::~TimerNode() { + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 2); + int32 timeLeft = _timeLeft / (_engine->getGameId() == GID_NEMESIS ? 1000 : 100); + if (timeLeft > 0) + _engine->getScriptManager()->setStateValue(_key, timeLeft); // If timer was stopped by stop or kill +} + +bool TimerNode::process(uint32 deltaTimeInMillis) { + _timeLeft -= deltaTimeInMillis; + + if (_timeLeft <= 0) + return stop(); + + return false; +} + +bool TimerNode::stop() { + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 2); + return true; +} + +void TimerNode::serialize(Common::WriteStream *stream) { + stream->writeUint32BE(MKTAG('T', 'I', 'M', 'R')); + stream->writeUint32LE(8); // size + stream->writeUint32LE(_key); + stream->writeUint32LE(_timeLeft); +} + +void TimerNode::deserialize(Common::SeekableReadStream *stream) { + _timeLeft = stream->readUint32LE(); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/timer_node.h b/engines/zvision/scripting/controls/timer_node.h new file mode 100644 index 0000000000..f6584becda --- /dev/null +++ b/engines/zvision/scripting/controls/timer_node.h @@ -0,0 +1,59 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_TIMER_NODE_H +#define ZVISION_TIMER_NODE_H + +#include "zvision/sidefx.h" + +namespace ZVision { + +class ZVision; + +class TimerNode : public SideFX { +public: + TimerNode(ZVision *engine, uint32 key, uint timeInSeconds); + ~TimerNode(); + + /** + * Decrement the timer by the delta time. If the timer is finished, set the status + * in _globalState and let this node be deleted + * + * @param deltaTimeInMillis The number of milliseconds that have passed since last frame + * @return If true, the node can be deleted after process() finishes + */ + bool process(uint32 deltaTimeInMillis); + void serialize(Common::WriteStream *stream); + void deserialize(Common::SeekableReadStream *stream); + inline bool needsSerialization() { + return true; + } + + bool stop(); + +private: + int32 _timeLeft; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/puzzle.h b/engines/zvision/scripting/puzzle.h new file mode 100644 index 0000000000..4d50756b87 --- /dev/null +++ b/engines/zvision/scripting/puzzle.h @@ -0,0 +1,81 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_PUZZLE_H +#define ZVISION_PUZZLE_H + +#include "zvision/actions.h" + +#include "common/list.h" +#include "common/ptr.h" + + +namespace ZVision { + +struct Puzzle { + Puzzle() : key(0) {} + + ~Puzzle() { + for (Common::List<ResultAction *>::iterator iter = resultActions.begin(); iter != resultActions.end(); ++iter) { + delete *iter; + } + } + + /** How criteria should be decided */ + enum CriteriaOperator { + EQUAL_TO, + NOT_EQUAL_TO, + GREATER_THAN, + LESS_THAN + }; + + /** Criteria for a Puzzle result to be fired */ + struct CriteriaEntry { + /** The key of a global state */ + uint32 key; + /** + * What we're comparing the value of the global state against + * This can either be a pure value or it can be the key of another global state + */ + uint32 argument; + /** How to do the comparison */ + CriteriaOperator criteriaOperator; + /** Whether 'argument' is the key of a global state (true) or a pure value (false) */ + bool argumentIsAKey; + }; + + enum StateFlags { + ONCE_PER_INST = 0x01, + DISABLED = 0x02, + DO_ME_NOW = 0x04 + }; + + uint32 key; + Common::List<Common::List <CriteriaEntry> > criteriaList; + // This has to be list of pointers because ResultAction is abstract + Common::List<ResultAction *> resultActions; + bool addedBySetState; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/scr_file_handling.cpp b/engines/zvision/scripting/scr_file_handling.cpp new file mode 100644 index 0000000000..9d1d0bf856 --- /dev/null +++ b/engines/zvision/scripting/scr_file_handling.cpp @@ -0,0 +1,353 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" + +#include "zvision/zvision.h" +#include "zvision/script_manager.h" + +#include "zvision/utility.h" +#include "zvision/puzzle.h" +#include "zvision/actions.h" +#include "zvision/push_toggle_control.h" +#include "zvision/lever_control.h" +#include "zvision/slot_control.h" + +#include "common/textconsole.h" +#include "common/file.h" +#include "common/tokenizer.h" + + +namespace ZVision { + +void ScriptManager::parseScrFile(const Common::String &fileName, script_scope &scope) { + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + warning("Script file not found: %s", fileName.c_str()); + return; + } + + while (!file.eos()) { + Common::String line = file.readLine(); + if (file.err()) { + warning("Error parsing scr file: %s", fileName.c_str()); + return; + } + + trimCommentsAndWhiteSpace(&line); + if (line.empty()) + continue; + + if (line.matchString("puzzle:*", true)) { + Puzzle *puzzle = new Puzzle(); + sscanf(line.c_str(), "puzzle:%u", &(puzzle->key)); + if (getStateFlag(puzzle->key) & Puzzle::ONCE_PER_INST) + setStateValue(puzzle->key, 0); + parsePuzzle(puzzle, file); + scope._puzzles.push_back(puzzle); + + } else if (line.matchString("control:*", true)) { + Control *ctrl = parseControl(line, file); + if (ctrl) + scope._controls.push_back(ctrl); + } + } + scope.proc_count = 0; +} + +void ScriptManager::parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stream) { + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("criteria {", true)) { + parseCriteria(stream, puzzle->criteriaList); + } else if (line.matchString("results {", true)) { + parseResults(stream, puzzle->resultActions); + } else if (line.matchString("flags {", true)) { + setStateFlag(puzzle->key, parseFlags(stream)); + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + puzzle->addedBySetState = 0; +} + +bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList) const { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + // Criteria can be empty + if (line.contains('}')) { + return false; + } + + // Create a new List to hold the CriteriaEntries + criteriaList.push_back(Common::List<Puzzle::CriteriaEntry>()); + + while (!stream.eos() && !line.contains('}')) { + Puzzle::CriteriaEntry entry; + + // Split the string into tokens using ' ' as a delimiter + Common::StringTokenizer tokenizer(line); + Common::String token; + + // Parse the id out of the first token + token = tokenizer.nextToken(); + sscanf(token.c_str(), "[%u]", &(entry.key)); + + // Parse the operator out of the second token + token = tokenizer.nextToken(); + if (token.c_str()[0] == '=') + entry.criteriaOperator = Puzzle::EQUAL_TO; + else if (token.c_str()[0] == '!') + entry.criteriaOperator = Puzzle::NOT_EQUAL_TO; + else if (token.c_str()[0] == '>') + entry.criteriaOperator = Puzzle::GREATER_THAN; + else if (token.c_str()[0] == '<') + entry.criteriaOperator = Puzzle::LESS_THAN; + + // First determine if the last token is an id or a value + // Then parse it into 'argument' + token = tokenizer.nextToken(); + if (token.contains('[')) { + sscanf(token.c_str(), "[%u]", &(entry.argument)); + entry.argumentIsAKey = true; + } else { + sscanf(token.c_str(), "%u", &(entry.argument)); + entry.argumentIsAKey = false; + } + + criteriaList.back().push_back(entry); + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + return true; +} + +void ScriptManager::parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList) const { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + line.toLowercase(); + + // TODO: Re-order the if-then statements in order of highest occurrence + while (!stream.eos() && !line.contains('}')) { + if (line.empty()) { + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + line.toLowercase(); + continue; + } + + const char *chrs = line.c_str(); + uint pos; + for (pos = 0; pos < line.size(); pos++) + if (chrs[pos] == ':') + break; + + if (pos < line.size()) { + + uint startpos = pos + 1; + + for (pos = startpos; pos < line.size(); pos++) + if (chrs[pos] == ':' || chrs[pos] == '(') + break; + + if (pos < line.size()) { + int32 slot = 11; + Common::String args = ""; + Common::String act(chrs + startpos, chrs + pos); + + startpos = pos + 1; + + if (chrs[pos] == ':') { + for (pos = startpos; pos < line.size(); pos++) + if (chrs[pos] == '(') + break; + Common::String s_slot(chrs + startpos, chrs + pos); + slot = atoi(s_slot.c_str()); + + startpos = pos + 1; + } + + if (pos < line.size()) { + for (pos = startpos; pos < line.size(); pos++) + if (chrs[pos] == ')') + break; + + args = Common::String(chrs + startpos, chrs + pos); + } + + + + // Parse for the action type + if (act.matchString("add", true)) { + actionList.push_back(new ActionAdd(_engine, slot, args)); + } else if (act.matchString("animplay", true)) { + actionList.push_back(new ActionPlayAnimation(_engine, slot, args)); + } else if (act.matchString("animpreload", true)) { + actionList.push_back(new ActionPreloadAnimation(_engine, slot, args)); + } else if (act.matchString("animunload", true)) { + //actionList.push_back(new ActionUnloadAnimation(_engine, slot, args)); + } else if (act.matchString("attenuate", true)) { + actionList.push_back(new ActionAttenuate(_engine, slot, args)); + } else if (act.matchString("assign", true)) { + actionList.push_back(new ActionAssign(_engine, slot, args)); + } else if (act.matchString("change_location", true)) { + actionList.push_back(new ActionChangeLocation(_engine, slot, args)); + } else if (act.matchString("crossfade", true)) { + actionList.push_back(new ActionCrossfade(_engine, slot, args)); + } else if (act.matchString("debug", true)) { + // TODO: Implement ActionDebug + } else if (act.matchString("delay_render", true)) { + // TODO: Implement ActionDelayRender + } else if (act.matchString("disable_control", true)) { + actionList.push_back(new ActionDisableControl(_engine, slot, args)); + } else if (act.matchString("disable_venus", true)) { + // TODO: Implement ActionDisableVenus + } else if (act.matchString("display_message", true)) { + // TODO: Implement ActionDisplayMessage + } else if (act.matchString("dissolve", true)) { + // TODO: Implement ActionDissolve + } else if (act.matchString("distort", true)) { + // TODO: Implement ActionDistort + } else if (act.matchString("enable_control", true)) { + actionList.push_back(new ActionEnableControl(_engine, slot, args)); + } else if (act.matchString("flush_mouse_events", true)) { + // TODO: Implement ActionFlushMouseEvents + } else if (act.matchString("inventory", true)) { + actionList.push_back(new ActionInventory(_engine, slot, args)); + } else if (act.matchString("kill", true)) { + actionList.push_back(new ActionKill(_engine, slot, args)); + } else if (act.matchString("menu_bar_enable", true)) { + // TODO: Implement ActionMenuBarEnable + } else if (act.matchString("music", true)) { + actionList.push_back(new ActionMusic(_engine, slot, args, false)); + } else if (act.matchString("pan_track", true)) { + actionList.push_back(new ActionPanTrack(_engine, slot, args)); + } else if (act.matchString("playpreload", true)) { + actionList.push_back(new ActionPlayPreloadAnimation(_engine, slot, args)); + } else if (act.matchString("preferences", true)) { + // TODO: Implement ActionPreferences + } else if (act.matchString("quit", true)) { + actionList.push_back(new ActionQuit(_engine, slot)); + } else if (act.matchString("random", true)) { + actionList.push_back(new ActionRandom(_engine, slot, args)); + } else if (act.matchString("region", true)) { + // TODO: Implement ActionRegion + } else if (act.matchString("restore_game", true)) { + // TODO: Implement ActionRestoreGame + } else if (act.matchString("rotate_to", true)) { + // TODO: Implement ActionRotateTo + } else if (act.matchString("save_game", true)) { + // TODO: Implement ActionSaveGame + } else if (act.matchString("set_partial_screen", true)) { + actionList.push_back(new ActionSetPartialScreen(_engine, slot, args)); + } else if (act.matchString("set_screen", true)) { + actionList.push_back(new ActionSetScreen(_engine, slot, args)); + } else if (act.matchString("set_venus", true)) { + // TODO: Implement ActionSetVenus + } else if (act.matchString("stop", true)) { + actionList.push_back(new ActionStop(_engine, slot, args)); + } else if (act.matchString("streamvideo", true)) { + actionList.push_back(new ActionStreamVideo(_engine, slot, args)); + } else if (act.matchString("syncsound", true)) { + actionList.push_back(new ActionSyncSound(_engine, slot, args)); + } else if (act.matchString("timer", true)) { + actionList.push_back(new ActionTimer(_engine, slot, args)); + } else if (act.matchString("ttytext", true)) { + actionList.push_back(new ActionTtyText(_engine, slot, args)); + } else if (act.matchString("universe_music", true)) { + actionList.push_back(new ActionMusic(_engine, slot, args, true)); + } else if (act.matchString("copy_file", true)) { + // Not used. Purposely left empty + } else { + warning("Unhandled result action type: %s", line.c_str()); + } + } + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + line.toLowercase(); + } + + return; +} + +uint ScriptManager::parseFlags(Common::SeekableReadStream &stream) const { + uint flags = 0; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + while (!stream.eos() && !line.contains('}')) { + if (line.matchString("ONCE_PER_INST", true)) { + flags |= Puzzle::ONCE_PER_INST; + } else if (line.matchString("DO_ME_NOW", true)) { + flags |= Puzzle::DO_ME_NOW; + } else if (line.matchString("DISABLED", true)) { + flags |= Puzzle::DISABLED; + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + return flags; +} + +Control *ScriptManager::parseControl(Common::String &line, Common::SeekableReadStream &stream) { + uint32 key; + char controlTypeBuffer[20]; + + sscanf(line.c_str(), "control:%u %s {", &key, controlTypeBuffer); + + Common::String controlType(controlTypeBuffer); + + if (controlType.equalsIgnoreCase("push_toggle")) { + return new PushToggleControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("flat")) { + Control::parseFlatControl(_engine); + return NULL; + } else if (controlType.equalsIgnoreCase("pana")) { + Control::parsePanoramaControl(_engine, stream); + return NULL; + } else if (controlType.equalsIgnoreCase("tilt")) { + Control::parseTiltControl(_engine, stream); + return NULL; + } else if (controlType.equalsIgnoreCase("lever")) { + return new LeverControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("slot")) { + return new SlotControl(_engine, key, stream); + } + return NULL; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/script_manager.cpp b/engines/zvision/scripting/script_manager.cpp new file mode 100644 index 0000000000..1a567397a8 --- /dev/null +++ b/engines/zvision/scripting/script_manager.cpp @@ -0,0 +1,720 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#include "common/scummsys.h" + +#include "zvision/script_manager.h" + +#include "zvision/zvision.h" +#include "zvision/render_manager.h" +#include "zvision/cursor_manager.h" +#include "zvision/save_manager.h" +#include "zvision/actions.h" +#include "zvision/utility.h" +#include "zvision/timer_node.h" + +#include "common/algorithm.h" +#include "common/hashmap.h" +#include "common/debug.h" +#include "common/stream.h" + + +namespace ZVision { + +ScriptManager::ScriptManager(ZVision *engine) + : _engine(engine), + _currentlyFocusedControl(0), + _activeControls(NULL) { +} + +ScriptManager::~ScriptManager() { + cleanScriptScope(universe); + cleanScriptScope(world); + cleanScriptScope(room); + cleanScriptScope(nodeview); +} + +void ScriptManager::initialize() { + cleanScriptScope(universe); + cleanScriptScope(world); + cleanScriptScope(room); + cleanScriptScope(nodeview); + + _currentLocation.node = 0; + _currentLocation.world = 0; + _currentLocation.room = 0; + _currentLocation.view = 0; + + parseScrFile("universe.scr", universe); + changeLocation('g', 'a', 'r', 'y', 0); +} + +void ScriptManager::update(uint deltaTimeMillis) { + if (_currentLocation.node != _nextLocation.node || + _currentLocation.room != _nextLocation.room || + _currentLocation.view != _nextLocation.view || + _currentLocation.world != _nextLocation.world) + do_changeLocation(); + + updateNodes(deltaTimeMillis); + execScope(nodeview); + execScope(room); + execScope(world); + execScope(universe); + updateControls(deltaTimeMillis); +} + +void ScriptManager::execScope(script_scope &scope) { + // Swap queues + PuzzleList *tmp = scope.exec_queue; + scope.exec_queue = scope.scope_queue; + scope.scope_queue = tmp; + scope.scope_queue->clear(); + + for (PuzzleList::iterator PuzzleIter = scope._puzzles.begin(); PuzzleIter != scope._puzzles.end(); ++PuzzleIter) + (*PuzzleIter)->addedBySetState = 0; + + if (scope.proc_count < 2 || getStateValue(76)) { + for (PuzzleList::iterator PuzzleIter = scope._puzzles.begin(); PuzzleIter != scope._puzzles.end(); ++PuzzleIter) + checkPuzzleCriteria(*PuzzleIter, scope.proc_count); + } else { + for (PuzzleList::iterator PuzzleIter = scope.exec_queue->begin(); PuzzleIter != scope.exec_queue->end(); ++PuzzleIter) + checkPuzzleCriteria(*PuzzleIter, scope.proc_count); + } + + if (scope.proc_count < 2) { + scope.proc_count++; + } +} + +void ScriptManager::referenceTableAddPuzzle(uint32 key, puzzle_ref ref) { + if (_referenceTable.contains(key)) { + Common::Array<puzzle_ref> *arr = &_referenceTable[key]; + for (uint32 i = 0; i < arr->size(); i++) + if ((*arr)[i].puz == ref.puz) + return; + } + + _referenceTable[key].push_back(ref); +} + +void ScriptManager::addPuzzlesToReferenceTable(script_scope &scope) { + // Iterate through each local Puzzle + for (PuzzleList::iterator PuzzleIter = scope._puzzles.begin(); PuzzleIter != scope._puzzles.end(); ++PuzzleIter) { + Puzzle *puzzlePtr = (*PuzzleIter); + + puzzle_ref ref; + ref.scope = &scope; + ref.puz = puzzlePtr; + + referenceTableAddPuzzle(puzzlePtr->key, ref); + + // Iterate through each CriteriaEntry and add a reference from the criteria key to the Puzzle + for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = (*PuzzleIter)->criteriaList.begin(); criteriaIter != (*PuzzleIter)->criteriaList.end(); ++criteriaIter) + for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) + referenceTableAddPuzzle(entryIter->key, ref); + } +} + +void ScriptManager::updateNodes(uint deltaTimeMillis) { + // If process() returns true, it means the node can be deleted + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end();) { + if ((*iter)->process(deltaTimeMillis)) { + delete(*iter); + // Remove the node + iter = _activeSideFx.erase(iter); + } else { + ++iter; + } + } +} + +void ScriptManager::updateControls(uint deltaTimeMillis) { + if (!_activeControls) + return; + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); iter++) + (*iter)->process(deltaTimeMillis); +} + +void ScriptManager::checkPuzzleCriteria(Puzzle *puzzle, uint counter) { + // Check if the puzzle is already finished + // Also check that the puzzle isn't disabled + if (getStateValue(puzzle->key) == 1 && + (getStateFlag(puzzle->key) & Puzzle::DISABLED) == 0) { + return; + } + + // Check each Criteria + if (counter == 0 && (getStateFlag(puzzle->key) & Puzzle::DO_ME_NOW) == 0) + return; + + bool criteriaMet = false; + for (Common::List<Common::List<Puzzle::CriteriaEntry> >::iterator criteriaIter = puzzle->criteriaList.begin(); criteriaIter != puzzle->criteriaList.end(); ++criteriaIter) { + criteriaMet = false; + + for (Common::List<Puzzle::CriteriaEntry>::iterator entryIter = criteriaIter->begin(); entryIter != criteriaIter->end(); ++entryIter) { + // Get the value to compare against + int argumentValue; + if (entryIter->argumentIsAKey) + argumentValue = getStateValue(entryIter->argument); + else + argumentValue = entryIter->argument; + + // Do the comparison + switch (entryIter->criteriaOperator) { + case Puzzle::EQUAL_TO: + criteriaMet = getStateValue(entryIter->key) == argumentValue; + break; + case Puzzle::NOT_EQUAL_TO: + criteriaMet = getStateValue(entryIter->key) != argumentValue; + break; + case Puzzle::GREATER_THAN: + criteriaMet = getStateValue(entryIter->key) > argumentValue; + break; + case Puzzle::LESS_THAN: + criteriaMet = getStateValue(entryIter->key) < argumentValue; + break; + } + + // If one check returns false, don't keep checking + if (!criteriaMet) { + break; + } + } + + // If any of the Criteria are *fully* met, then execute the results + if (criteriaMet) { + break; + } + } + + // criteriaList can be empty. Aka, the puzzle should be executed immediately + if (puzzle->criteriaList.empty() || criteriaMet) { + debug(1, "Puzzle %u criteria passed. Executing its ResultActions", puzzle->key); + + // Set the puzzle as completed + setStateValue(puzzle->key, 1); + + bool shouldContinue = true; + for (Common::List<ResultAction *>::iterator resultIter = puzzle->resultActions.begin(); resultIter != puzzle->resultActions.end(); ++resultIter) { + shouldContinue = shouldContinue && (*resultIter)->execute(); + if (!shouldContinue) { + break; + } + } + + if (!shouldContinue) { + return; + } + } +} + +void ScriptManager::cleanStateTable() { + for (StateMap::iterator iter = _globalState.begin(); iter != _globalState.end(); ++iter) { + // If the value is equal to zero, we can purge it since getStateValue() + // will return zero if _globalState doesn't contain a key + if (iter->_value == 0) { + // Remove the node + _globalState.erase(iter); + } + } +} + +void ScriptManager::cleanScriptScope(script_scope &scope) { + scope._priv_queue_one.clear(); + scope._priv_queue_two.clear(); + scope.scope_queue = &scope._priv_queue_one; + scope.exec_queue = &scope._priv_queue_two; + for (PuzzleList::iterator iter = scope._puzzles.begin(); iter != scope._puzzles.end(); ++iter) + delete(*iter); + + scope._puzzles.clear(); + + for (ControlList::iterator iter = scope._controls.begin(); iter != scope._controls.end(); ++iter) + delete(*iter); + + scope._controls.clear(); + + scope.proc_count = 0; +} + +int ScriptManager::getStateValue(uint32 key) { + if (_globalState.contains(key)) + return _globalState[key]; + else + return 0; +} + +void ScriptManager::queuePuzzles(uint32 key) { + if (_referenceTable.contains(key)) { + Common::Array<puzzle_ref> *arr = &_referenceTable[key]; + for (int32 i = arr->size() - 1; i >= 0; i--) + if (!(*arr)[i].puz->addedBySetState) { + (*arr)[i].scope->scope_queue->push_back((*arr)[i].puz); + (*arr)[i].puz->addedBySetState = true; + } + } +} + +void ScriptManager::setStateValue(uint32 key, int value) { + if (value == 0) + _globalState.erase(key); + else + _globalState[key] = value; + + queuePuzzles(key); +} + +void ScriptManager::setStateValueSilent(uint32 key, int value) { + if (value == 0) + _globalState.erase(key); + else + _globalState[key] = value; +} + +uint ScriptManager::getStateFlag(uint32 key) { + if (_globalStateFlags.contains(key)) + return _globalStateFlags[key]; + else + return 0; +} + +void ScriptManager::setStateFlag(uint32 key, uint value) { + queuePuzzles(key); + + _globalStateFlags[key] |= value; +} + +void ScriptManager::setStateFlagSilent(uint32 key, uint value) { + if (value == 0) + _globalStateFlags.erase(key); + else + _globalStateFlags[key] = value; +} + +void ScriptManager::unsetStateFlag(uint32 key, uint value) { + queuePuzzles(key); + + if (_globalStateFlags.contains(key)) { + _globalStateFlags[key] &= ~value; + + if (_globalStateFlags[key] == 0) + _globalStateFlags.erase(key); + } +} + +Control *ScriptManager::getControl(uint32 key) { + + return nullptr; +} + +void ScriptManager::focusControl(uint32 key) { + if (!_activeControls) + return; + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); ++iter) { + uint32 controlKey = (*iter)->getKey(); + + if (controlKey == key) { + (*iter)->focus(); + } else if (controlKey == _currentlyFocusedControl) { + (*iter)->unfocus(); + } + } + + _currentlyFocusedControl = key; +} + +void ScriptManager::addSideFX(SideFX *fx) { + _activeSideFx.push_back(fx); +} + +SideFX *ScriptManager::getSideFX(uint32 key) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) { + if ((*iter)->getKey() == key) { + return (*iter); + } + } + + return nullptr; +} + +void ScriptManager::deleteSideFx(uint32 key) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) { + if ((*iter)->getKey() == key) { + delete(*iter); + _activeSideFx.erase(iter); + break; + } + } +} + +void ScriptManager::stopSideFx(uint32 key) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) { + if ((*iter)->getKey() == key) { + bool ret = (*iter)->stop(); + if (ret) { + delete(*iter); + _activeSideFx.erase(iter); + } + break; + } + } +} + +void ScriptManager::killSideFx(uint32 key) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) { + if ((*iter)->getKey() == key) { + (*iter)->kill(); + delete(*iter); + _activeSideFx.erase(iter); + break; + } + } +} + +void ScriptManager::killSideFxType(SideFX::SideFXType type) { + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end();) { + if ((*iter)->getType() & type) { + (*iter)->kill(); + delete(*iter); + iter = _activeSideFx.erase(iter); + } else { + ++iter; + } + } +} + +void ScriptManager::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (!_activeControls) + return; + for (ControlList::iterator iter = _activeControls->reverse_begin(); iter != _activeControls->end(); iter--) { + if ((*iter)->onMouseDown(screenSpacePos, backgroundImageSpacePos)) + return; + } +} + +void ScriptManager::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (!_activeControls) + return; + for (ControlList::iterator iter = _activeControls->reverse_begin(); iter != _activeControls->end(); iter--) { + if ((*iter)->onMouseUp(screenSpacePos, backgroundImageSpacePos)) + return; + } +} + +bool ScriptManager::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (!_activeControls) + return false; + + for (ControlList::iterator iter = _activeControls->reverse_begin(); iter != _activeControls->end(); iter--) { + if ((*iter)->onMouseMove(screenSpacePos, backgroundImageSpacePos)) + return true; + } + + return false; +} + +void ScriptManager::onKeyDown(Common::KeyState keyState) { + if (!_activeControls) + return; + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); ++iter) { + (*iter)->onKeyDown(keyState); + } +} + +void ScriptManager::onKeyUp(Common::KeyState keyState) { + if (!_activeControls) + return; + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); ++iter) { + (*iter)->onKeyUp(keyState); + } +} + +void ScriptManager::changeLocation(char _world, char _room, char _node, char _view, uint32 offset) { + _nextLocation.world = _world; + _nextLocation.room = _room; + _nextLocation.node = _node; + _nextLocation.view = _view; + _nextLocation.offset = offset; + // If next location 0000 - it's indicate to go to previous location. + if (_nextLocation.world == '0' && _nextLocation.room == '0' && _nextLocation.node == '0' && _nextLocation.view == '0') { + if (getStateValue(StateKey_World) != 'g' || getStateValue(StateKey_Room) != 'j') { + _nextLocation.world = getStateValue(StateKey_LastWorld); + _nextLocation.room = getStateValue(StateKey_LastRoom); + _nextLocation.node = getStateValue(StateKey_LastNode); + _nextLocation.view = getStateValue(StateKey_LastView); + _nextLocation.offset = getStateValue(StateKey_LastViewPos); + } else { + _nextLocation.world = getStateValue(StateKey_Menu_LastWorld); + _nextLocation.room = getStateValue(StateKey_Menu_LastRoom); + _nextLocation.node = getStateValue(StateKey_Menu_LastNode); + _nextLocation.view = getStateValue(StateKey_Menu_LastView); + _nextLocation.offset = getStateValue(StateKey_Menu_LastViewPos); + } + } +} + +void ScriptManager::do_changeLocation() { + assert(_nextLocation.world != 0); + debug(1, "Changing location to: %c %c %c %c %u", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view, _nextLocation.offset); + + _engine->setRenderDelay(2); + + if (getStateValue(StateKey_World) != 'g' || getStateValue(StateKey_Room) != 'j') { + if (_nextLocation.world != 'g' || _nextLocation.room != 'j') { + setStateValue(StateKey_LastWorld, getStateValue(StateKey_World)); + setStateValue(StateKey_LastRoom, getStateValue(StateKey_Room)); + setStateValue(StateKey_LastNode, getStateValue(StateKey_Node)); + setStateValue(StateKey_LastView, getStateValue(StateKey_View)); + setStateValue(StateKey_LastViewPos, getStateValue(StateKey_ViewPos)); + } else { + setStateValue(StateKey_Menu_LastWorld, getStateValue(StateKey_World)); + setStateValue(StateKey_Menu_LastRoom, getStateValue(StateKey_Room)); + setStateValue(StateKey_Menu_LastNode, getStateValue(StateKey_Node)); + setStateValue(StateKey_Menu_LastView, getStateValue(StateKey_View)); + setStateValue(StateKey_Menu_LastViewPos, getStateValue(StateKey_ViewPos)); + } + } + + // Auto save + //_engine->getSaveManager()->autoSave(); + + setStateValue(StateKey_World, _nextLocation.world); + setStateValue(StateKey_Room, _nextLocation.room); + setStateValue(StateKey_Node, _nextLocation.node); + setStateValue(StateKey_View, _nextLocation.view); + setStateValue(StateKey_ViewPos, _nextLocation.offset); + + _referenceTable.clear(); + addPuzzlesToReferenceTable(universe); + + if (_nextLocation.world != _currentLocation.world) { + cleanScriptScope(nodeview); + cleanScriptScope(room); + cleanScriptScope(world); + + Common::String fileName = Common::String::format("%c%c%c%c.scr", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view); + parseScrFile(fileName, nodeview); + addPuzzlesToReferenceTable(nodeview); + + fileName = Common::String::format("%c%c.scr", _nextLocation.world, _nextLocation.room); + parseScrFile(fileName, room); + addPuzzlesToReferenceTable(room); + + fileName = Common::String::format("%c.scr", _nextLocation.world); + parseScrFile(fileName, world); + addPuzzlesToReferenceTable(world); + } else if (_nextLocation.room != _currentLocation.room) { + cleanScriptScope(nodeview); + cleanScriptScope(room); + + addPuzzlesToReferenceTable(world); + + Common::String fileName = Common::String::format("%c%c%c%c.scr", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view); + parseScrFile(fileName, nodeview); + addPuzzlesToReferenceTable(nodeview); + + fileName = Common::String::format("%c%c.scr", _nextLocation.world, _nextLocation.room); + parseScrFile(fileName, room); + addPuzzlesToReferenceTable(room); + + } else if (_nextLocation.node != _currentLocation.node || _nextLocation.view != _currentLocation.view) { + cleanScriptScope(nodeview); + + addPuzzlesToReferenceTable(room); + addPuzzlesToReferenceTable(world); + + Common::String fileName = Common::String::format("%c%c%c%c.scr", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view); + parseScrFile(fileName, nodeview); + addPuzzlesToReferenceTable(nodeview); + } + + _activeControls = &nodeview._controls; + + // Revert to the idle cursor + _engine->getCursorManager()->changeCursor(CursorIndex_Idle); + + // Change the background position + _engine->getRenderManager()->setBackgroundPosition(_nextLocation.offset); + + if (_currentLocation.world == 0 && _currentLocation.room == 0 && _currentLocation.node == 0 && _currentLocation.view == 0) { + _currentLocation = _nextLocation; + execScope(world); + execScope(room); + execScope(nodeview); + } else if (_nextLocation.world != _currentLocation.world) { + _currentLocation = _nextLocation; + execScope(room); + execScope(nodeview); + } else if (_nextLocation.room != _currentLocation.room) { + _currentLocation = _nextLocation; + execScope(room); + execScope(nodeview); + } else if (_nextLocation.node != _currentLocation.node || _nextLocation.view != _currentLocation.view) { + _currentLocation = _nextLocation; + execScope(nodeview); + } +} + +void ScriptManager::serialize(Common::WriteStream *stream) { + stream->writeUint32BE(MKTAG('Z', 'N', 'S', 'G')); + stream->writeUint32LE(4); + stream->writeUint32LE(0); + stream->writeUint32BE(MKTAG('L', 'O', 'C', ' ')); + stream->writeUint32LE(8); + stream->writeByte(getStateValue(StateKey_World)); + stream->writeByte(getStateValue(StateKey_Room)); + stream->writeByte(getStateValue(StateKey_Node)); + stream->writeByte(getStateValue(StateKey_View)); + stream->writeUint32LE(getStateValue(StateKey_ViewPos)); + + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); ++iter) + (*iter)->serialize(stream); + + stream->writeUint32BE(MKTAG('F', 'L', 'A', 'G')); + + int32 slots = 20000; + if (_engine->getGameId() == GID_NEMESIS) + slots = 30000; + + stream->writeUint32LE(slots * 2); + + for (int32 i = 0; i < slots; i++) + stream->writeUint16LE(getStateFlag(i)); + + stream->writeUint32BE(MKTAG('P', 'U', 'Z', 'Z')); + + stream->writeUint32LE(slots * 2); + + for (int32 i = 0; i < slots; i++) + stream->writeSint16LE(getStateValue(i)); +} + +void ScriptManager::deserialize(Common::SeekableReadStream *stream) { + // Clear out the current table values + _globalState.clear(); + _globalStateFlags.clear(); + + cleanScriptScope(nodeview); + cleanScriptScope(room); + cleanScriptScope(world); + + _currentLocation.node = 0; + _currentLocation.world = 0; + _currentLocation.room = 0; + _currentLocation.view = 0; + + for (SideFXList::iterator iter = _activeSideFx.begin(); iter != _activeSideFx.end(); iter++) + delete(*iter); + + _activeSideFx.clear(); + + _referenceTable.clear(); + + if (stream->readUint32BE() != MKTAG('Z', 'N', 'S', 'G') || stream->readUint32LE() != 4) { + changeLocation('g', 'a', 'r', 'y', 0); + return; + } + + stream->seek(4, SEEK_CUR); + + if (stream->readUint32BE() != MKTAG('L', 'O', 'C', ' ') || stream->readUint32LE() != 8) { + changeLocation('g', 'a', 'r', 'y', 0); + return; + } + + Location next_loc; + + next_loc.world = stream->readByte(); + next_loc.room = stream->readByte(); + next_loc.node = stream->readByte(); + next_loc.view = stream->readByte(); + next_loc.offset = stream->readUint32LE() & 0x0000FFFF; + + // What the fck, eos is not 'return pos >= size' + // while (!stream->eos()) {*/ + while (stream->pos() < stream->size()) { + uint32 tag = stream->readUint32BE(); + uint32 tag_size = stream->readUint32LE(); + switch (tag) { + case MKTAG('T', 'I', 'M', 'R'): { + uint32 key = stream->readUint32LE(); + uint32 time = stream->readUint32LE(); + if (_engine->getGameId() == GID_GRANDINQUISITOR) + time /= 100; + else if (_engine->getGameId() == GID_NEMESIS) + time /= 1000; + addSideFX(new TimerNode(_engine, key, time)); + } + break; + case MKTAG('F', 'L', 'A', 'G'): + for (uint32 i = 0; i < tag_size / 2; i++) + setStateFlagSilent(i, stream->readUint16LE()); + break; + case MKTAG('P', 'U', 'Z', 'Z'): + for (uint32 i = 0; i < tag_size / 2; i++) + setStateValueSilent(i, stream->readUint16LE()); + break; + default: + stream->seek(tag_size, SEEK_CUR); + } + } + + _nextLocation = next_loc; + + do_changeLocation(); + // Place for read prefs + _engine->setRenderDelay(10); + setStateValue(StateKey_RestoreFlag, 1); +} + +Location ScriptManager::getCurrentLocation() const { + Location location = _currentLocation; + location.offset = _engine->getRenderManager()->getCurrentBackgroundOffset(); + + return location; +} + +ValueSlot::ValueSlot(ScriptManager *sc_man, const char *slot_val): + _sc_man(sc_man) { + value = 0; + slot = false; + const char *is_slot = strstr(slot_val, "["); + if (is_slot) { + slot = true; + value = atoi(is_slot + 1); + } else { + slot = false; + value = atoi(slot_val); + } +} +int16 ValueSlot::getValue() { + if (slot) { + if (value >= 0) + return _sc_man->getStateValue(value); + else + return 0; + } else + return value; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/script_manager.h b/engines/zvision/scripting/script_manager.h new file mode 100644 index 0000000000..6adade5745 --- /dev/null +++ b/engines/zvision/scripting/script_manager.h @@ -0,0 +1,333 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_SCRIPT_MANAGER_H +#define ZVISION_SCRIPT_MANAGER_H + +#include "zvision/puzzle.h" +#include "zvision/control.h" +#include "zvision/sidefx.h" + +#include "common/hashmap.h" +#include "common/queue.h" + + +namespace Common { +class String; +class SeekableReadStream; +} + +namespace ZVision { + +class ZVision; + +enum StateKey { + StateKey_World = 3, + StateKey_Room = 4, + StateKey_Node = 5, + StateKey_View = 6, + StateKey_ViewPos = 7, + StateKey_KeyPress = 8, + StateKey_InventoryItem = 9, + StateKey_LMouse = 10, + StateKey_NotSet = 11, // This key doesn't set + StateKey_Rounds = 12, + StateKey_Venus = 13, + StateKey_RMouse = 18, + StateKey_MenuState = 19, + StateKey_RestoreFlag = 20, + StateKey_Quitting = 39, + StateKey_LastWorld = 40, + StateKey_LastRoom = 41, + StateKey_LastNode = 42, + StateKey_LastView = 43, + StateKey_LastViewPos = 44, + StateKey_Menu_LastWorld = 45, + StateKey_Menu_LastRoom = 46, + StateKey_Menu_LastNode = 47, + StateKey_Menu_LastView = 48, + StateKey_Menu_LastViewPos = 49, + StateKey_KbdRotateSpeed = 50, + StateKey_Subtitles = 51, + StateKey_StreamSkipKey = 52, + StateKey_RotateSpeed = 53, + StateKey_Volume = 56, + StateKey_Qsound = 57, + StateKey_VenusEnable = 58, + StateKey_HighQuality = 59, + StateKey_VideoLineSkip = 65, + StateKey_Platform = 66, + StateKey_InstallLevel = 67, + StateKey_CountryCode = 68, + StateKey_CPU = 69, + StateKey_MovieCursor = 70, + StateKey_NoTurnAnim = 71, + StateKey_WIN958 = 72, + StateKey_ShowErrorDlg = 73, + StateKey_DebugCheats = 74, + StateKey_JapanFonts = 75, + StateKey_Brightness = 77, + StateKey_EF9_B = 91, + StateKey_EF9_G = 92, + StateKey_EF9_R = 93, + StateKey_EF9_Speed = 94, + StateKey_Inv_Cnt_Slot = 100, + StateKey_Inv_1_Slot = 101, + StateKey_Inv_49_Slot = 149, + StateKey_Inv_TotalSlots = 150 +}; + +struct Location { + Location() : world('g'), room('a'), node('r'), view('y'), offset(0) {} + + char world; + char room; + char node; + char view; + uint32 offset; +}; + +typedef Common::List<Puzzle *> PuzzleList; +typedef Common::Queue<Puzzle *> PuzzleQueue; +typedef Common::List<Control *> ControlList; +typedef Common::HashMap<uint32, int32> StateMap; +typedef Common::List<SideFX *> SideFXList; + +class ScriptManager { +public: + ScriptManager(ZVision *engine); + ~ScriptManager(); + +private: + ZVision *_engine; + + struct script_scope { + uint32 proc_count; + + PuzzleList *scope_queue; // For adding puzzles to queue + PuzzleList *exec_queue; // Switch to it when execute + PuzzleList _priv_queue_one; + PuzzleList _priv_queue_two; + + PuzzleList _puzzles; + ControlList _controls; + }; + + struct puzzle_ref { + Puzzle *puz; + script_scope *scope; + }; + + typedef Common::HashMap<uint32, Common::Array<puzzle_ref> > PuzzleMap; + + /** + * Holds the global state variable. Do NOT directly modify this. Use the accessors and + * mutators getStateValue() and setStateValue(). This ensures that Puzzles that reference a + * particular state key are checked after the key is modified. + */ + StateMap _globalState; + /** Holds execute flags */ + StateMap _globalStateFlags; + /** References _globalState keys to Puzzles */ + PuzzleMap _referenceTable; + /** Holds the currently active controls */ + ControlList *_activeControls; + + script_scope universe; + script_scope world; + script_scope room; + script_scope nodeview; + + /** Holds the currently active timers, musics, other */ + SideFXList _activeSideFx; + + Location _currentLocation; + Location _nextLocation; + + uint32 _currentlyFocusedControl; + +public: + void initialize(); + void update(uint deltaTimeMillis); + void queuePuzzles(uint32 key); + + int getStateValue(uint32 key); + void setStateValue(uint32 key, int value); + + uint getStateFlag(uint32 key); + void setStateFlag(uint32 key, uint value); + void unsetStateFlag(uint32 key, uint value); + + void addControl(Control *control); + Control *getControl(uint32 key); + + void enableControl(uint32 key); + void disableControl(uint32 key); + + void focusControl(uint32 key); + + void addSideFX(SideFX *fx); + SideFX *getSideFX(uint32 key); + void deleteSideFx(uint32 key); + void stopSideFx(uint32 key); + void killSideFx(uint32 key); + void killSideFxType(SideFX::SideFXType type); + + /** + * Called when LeftMouse is pushed. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + void onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called when LeftMouse is lifted. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + */ + void onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called on every MouseMove. + * + * @param screenSpacePos The position of the mouse in screen space + * @param backgroundImageSpacePos The position of the mouse in background image space + * @return Was the cursor changed? + */ + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + /** + * Called when a key is pressed. + * + * @param keycode The key that was pressed + */ + void onKeyDown(Common::KeyState keyState); + /** + * Called when a key is released. + * + * @param keycode The key that was pressed + */ + void onKeyUp(Common::KeyState keyState); + + /** Mark next location */ + void changeLocation(char world, char room, char node, char view, uint32 offset); + + void serialize(Common::WriteStream *stream); + void deserialize(Common::SeekableReadStream *stream); + + Location getCurrentLocation() const; + +private: + void referenceTableAddPuzzle(uint32 key, puzzle_ref ref); + void addPuzzlesToReferenceTable(script_scope &scope); + void updateNodes(uint deltaTimeMillis); + void updateControls(uint deltaTimeMillis); + void checkPuzzleCriteria(Puzzle *puzzle, uint counter); + void cleanStateTable(); + void cleanScriptScope(script_scope &scope); + void execScope(script_scope &scope); + + /** Perform change location */ + void do_changeLocation(); + + int8 invertory_getCount(); + void invertory_setCount(int8 cnt); + int16 invertory_getItem(int8 id); + void invertory_setItem(int8 id, int16 item); + + void setStateFlagSilent(uint32 key, uint value); + void setStateValueSilent(uint32 key, int value); + +public: + void invertory_add(int16 item); + void invertory_drop(int16 item); + void invertory_cycle(); + + // TODO: Make this private. It was only made public so Console::cmdParseAllScrFiles() could use it + /** + * Parses a script file into triggers and events + * + * @param fileName Name of the .scr file + * @param isGlobal Are the puzzles included in the file global (true). AKA, the won't be purged during location changes + */ + void parseScrFile(const Common::String &fileName, script_scope &scope); + +private: + /** + * Parses the stream into a Puzzle object + * Helper method for parseScrFile. + * + * @param puzzle The object to store what is parsed + * @param stream Scr file stream + */ + void parsePuzzle(Puzzle *puzzle, Common::SeekableReadStream &stream); + + /** + * Parses the stream into a Criteria object + * Helper method for parsePuzzle. + * + * @param criteria Pointer to the Criteria object to fill + * @param stream Scr file stream + * @return Whether any criteria were read + */ + bool parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList) const; + + /** + * Parses the stream into a ResultAction objects + * Helper method for parsePuzzle. + * + * @param stream Scr file stream + * @param actionList The list where the results will be added + * @return Created Results object + */ + void parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList) const; + + /** + * Helper method for parsePuzzle. Parses the stream into a bitwise or of the StateFlags enum + * + * @param stream Scr file stream + * @return Bitwise OR of all the flags set within the puzzle + */ + uint parseFlags(Common::SeekableReadStream &stream) const; + + /** + * Helper method for parseScrFile. Parses the stream into a Control object + * + * @param line The line initially read + * @param stream Scr file stream + */ + Control *parseControl(Common::String &line, Common::SeekableReadStream &stream); +}; + +class ValueSlot { +public: + ValueSlot(ScriptManager *sc_man, const char *slot_val); + int16 getValue(); +private: + int16 value; + bool slot; + ScriptManager *_sc_man; +}; + + +} // End of namespace ZVision + +#endif |