diff options
Diffstat (limited to 'engines/zvision/scripting')
46 files changed, 9242 insertions, 0 deletions
diff --git a/engines/zvision/scripting/actions.cpp b/engines/zvision/scripting/actions.cpp new file mode 100644 index 0000000000..e1380b0eb2 --- /dev/null +++ b/engines/zvision/scripting/actions.cpp @@ -0,0 +1,1099 @@ +/* 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 "video/video_decoder.h" + +#include "zvision/scripting/actions.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/file/save_manager.h" +#include "zvision/scripting/menu.h" +#include "zvision/scripting/effects/timer_effect.h" +#include "zvision/scripting/effects/music_effect.h" +#include "zvision/scripting/effects/syncsound_effect.h" +#include "zvision/scripting/effects/animation_effect.h" +#include "zvision/scripting/effects/distort_effect.h" +#include "zvision/scripting/effects/ttytext_effect.h" +#include "zvision/scripting/effects/region_effect.h" +#include "zvision/scripting/controls/titler_control.h" +#include "zvision/graphics/render_table.h" +#include "zvision/graphics/graphics_effect.h" +#include "zvision/graphics/effects/fog.h" +#include "zvision/graphics/effects/light.h" +#include "zvision/graphics/effects/wave.h" +#include "zvision/graphics/cursors/cursor_manager.h" + +namespace ZVision { + +ResultAction::ResultAction(ZVision *engine, int32 slotKey) : + _engine(engine), + _slotKey(slotKey), + _scriptManager(engine->getScriptManager()) { +} + +////////////////////////////////////////////////////////////////////////////// +// ActionAdd +////////////////////////////////////////////////////////////////////////////// + +ActionAdd::ActionAdd(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _key = 0; + _value = 0; + + sscanf(line.c_str(), "%u,%d", &_key, &_value); +} + +bool ActionAdd::execute() { + _scriptManager->setStateValue(_key, _scriptManager->getStateValue(_key) + _value); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionAssign +////////////////////////////////////////////////////////////////////////////// + +ActionAssign::ActionAssign(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _key = 0; + + char buf[64]; + memset(buf, 0, 64); + sscanf(line.c_str(), "%u, %s", &_key, buf); + _value = new ValueSlot(_scriptManager, buf); +} + +ActionAssign::~ActionAssign() { + delete _value; +} + +bool ActionAssign::execute() { + _scriptManager->setStateValue(_key, _value->getValue()); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionAttenuate +////////////////////////////////////////////////////////////////////////////// + +ActionAttenuate::ActionAttenuate(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _key = 0; + _attenuation = 0; + + sscanf(line.c_str(), "%u, %d", &_key, &_attenuation); +} + +bool ActionAttenuate::execute() { + ScriptingEffect *fx = _scriptManager->getSideFX(_key); + if (fx && fx->getType() == ScriptingEffect::SCRIPTING_EFFECT_AUDIO) { + MusicNodeBASE *mus = (MusicNodeBASE *)fx; + mus->setVolume(255 * (10000 - abs(_attenuation)) / 10000 ); + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionChangeLocation +////////////////////////////////////////////////////////////////////////////// + +ActionChangeLocation::ActionChangeLocation(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _world = 'g'; + _room = 'a'; + _node = 'r'; + _view = 'y'; + _offset = 0; + + 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 + _scriptManager->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) { + _keyOne = 0; + _keyTwo = 0; + _oneStartVolume = 0; + _twoStartVolume = 0; + _oneEndVolume = 0; + _twoEndVolume = 0; + _timeInMillis = 0; + + sscanf(line.c_str(), + "%u %u %d %d %d %d %d", + &_keyOne, &_keyTwo, &_oneStartVolume, &_twoStartVolume, &_oneEndVolume, &_twoEndVolume, &_timeInMillis); +} + +bool ActionCrossfade::execute() { + if (_keyOne) { + ScriptingEffect *fx = _scriptManager->getSideFX(_keyOne); + if (fx && fx->getType() == ScriptingEffect::SCRIPTING_EFFECT_AUDIO) { + MusicNodeBASE *mus = (MusicNodeBASE *)fx; + if (_oneStartVolume >= 0) + mus->setVolume((_oneStartVolume * 255) / 100); + + mus->setFade(_timeInMillis, (_oneEndVolume * 255) / 100); + } + } + + if (_keyTwo) { + ScriptingEffect *fx = _scriptManager->getSideFX(_keyTwo); + if (fx && fx->getType() == ScriptingEffect::SCRIPTING_EFFECT_AUDIO) { + MusicNodeBASE *mus = (MusicNodeBASE *)fx; + if (_twoStartVolume >= 0) + mus->setVolume((_twoStartVolume * 255) / 100); + + mus->setFade(_timeInMillis, (_twoEndVolume * 255) / 100); + } + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionCursor +////////////////////////////////////////////////////////////////////////////// + +ActionCursor::ActionCursor(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + Common::String up = line; + up.toUppercase(); + _action = 0; + + if (up[0] == 'B') + _action = 2; + else if (up[0] == 'I') + _action = 3; + else if (up[0] == 'U') + _action = 0; + else if (up[0] == 'H') + _action = 1; +} + +bool ActionCursor::execute() { + switch (_action) { + case 1: + _engine->getCursorManager()->showMouse(false); + break; + default: + _engine->getCursorManager()->showMouse(true); + break; + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionDelayRender +////////////////////////////////////////////////////////////////////////////// + +ActionDelayRender::ActionDelayRender(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _framesToDelay = 0; + sscanf(line.c_str(), "%u", &_framesToDelay); + // Limit to 10 frames maximum. This fixes the script bug in ZGI scene px10 + // (outside Frobozz Electric building), where this is set to 100 (bug #6791). + _framesToDelay = MIN<uint32>(_framesToDelay, 10); +} + +bool ActionDelayRender::execute() { + _engine->setRenderDelay(_framesToDelay); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionDisableControl +////////////////////////////////////////////////////////////////////////////// + +ActionDisableControl::ActionDisableControl(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _key = 0; + + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionDisableControl::execute() { + _scriptManager->setStateFlag(_key, Puzzle::DISABLED); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionDisplayMessage +////////////////////////////////////////////////////////////////////////////// + +ActionDisplayMessage::ActionDisplayMessage(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _control = 0; + _msgid = 0; + + sscanf(line.c_str(), "%hd %hd", &_control, &_msgid); +} + +bool ActionDisplayMessage::execute() { + Control *ctrl = _scriptManager->getControl(_control); + if (ctrl && ctrl->getType() == Control::CONTROL_TITLER) { + TitlerControl *titler = (TitlerControl *)ctrl; + titler->setString(_msgid); + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionDissolve +////////////////////////////////////////////////////////////////////////////// + +ActionDissolve::ActionDissolve(ZVision *engine) : + ResultAction(engine, 0) { +} + +bool ActionDissolve::execute() { + // Cause black screen flick + // _engine->getRenderManager()->bkgFill(0, 0, 0); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionDistort - only used by Zork: Nemesis for the "treatment" puzzle in the Sanitarium (aj30) +////////////////////////////////////////////////////////////////////////////// + +ActionDistort::ActionDistort(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _distSlot = 0; + _speed = 0; + _startAngle = 60.0; + _endAngle = 60.0; + _startLineScale = 1.0; + _endLineScale = 1.0; + + sscanf(line.c_str(), "%hd %hd %f %f %f %f", &_distSlot, &_speed, &_startAngle, &_endAngle, &_startLineScale, &_endLineScale); +} + +ActionDistort::~ActionDistort() { + _scriptManager->killSideFx(_distSlot); +} + +bool ActionDistort::execute() { + if (_scriptManager->getSideFX(_distSlot)) + return true; + + _scriptManager->addSideFX(new DistortNode(_engine, _distSlot, _speed, _startAngle, _endAngle, _startLineScale, _endLineScale)); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionEnableControl +////////////////////////////////////////////////////////////////////////////// + +ActionEnableControl::ActionEnableControl(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _key = 0; + + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionEnableControl::execute() { + _scriptManager->unsetStateFlag(_key, Puzzle::DISABLED); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionFlushMouseEvents +////////////////////////////////////////////////////////////////////////////// + +ActionFlushMouseEvents::ActionFlushMouseEvents(ZVision *engine, int32 slotKey) : + ResultAction(engine, slotKey) { +} + +bool ActionFlushMouseEvents::execute() { + _scriptManager->flushEvent(Common::EVENT_LBUTTONUP); + _scriptManager->flushEvent(Common::EVENT_LBUTTONDOWN); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionInventory +////////////////////////////////////////////////////////////////////////////// + +ActionInventory::ActionInventory(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _type = -1; + _key = 0; + + char buf[25]; + sscanf(line.c_str(), "%24s %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 + _scriptManager->inventoryAdd(_key); + break; + case 1: // addi + _scriptManager->inventoryAdd(_scriptManager->getStateValue(_key)); + break; + case 2: // drop + if (_key >= 0) + _scriptManager->inventoryDrop(_key); + else + _scriptManager->inventoryDrop(_scriptManager->getStateValue(StateKey_InventoryItem)); + break; + case 3: // dropi + _scriptManager->inventoryDrop(_scriptManager->getStateValue(_key)); + break; + case 4: // cycle + _scriptManager->inventoryCycle(); + break; + default: + break; + } + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionKill - only used by ZGI +////////////////////////////////////////////////////////////////////////////// + +ActionKill::ActionKill(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _key = 0; + _type = 0; + char keytype[25]; + sscanf(line.c_str(), "%24s", keytype); + if (keytype[0] == '"') { + if (!scumm_stricmp(keytype, "\"ANIM\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_ANIM; + else if (!scumm_stricmp(keytype, "\"AUDIO\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_AUDIO; + else if (!scumm_stricmp(keytype, "\"DISTORT\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_DISTORT; + else if (!scumm_stricmp(keytype, "\"PANTRACK\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_PANTRACK; + else if (!scumm_stricmp(keytype, "\"REGION\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_REGION; + else if (!scumm_stricmp(keytype, "\"TIMER\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_TIMER; + else if (!scumm_stricmp(keytype, "\"TTYTEXT\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_TTYTXT; + else if (!scumm_stricmp(keytype, "\"ALL\"")) + _type = ScriptingEffect::SCRIPTING_EFFECT_ALL; + } else + _key = atoi(keytype); +} + +bool ActionKill::execute() { + if (_type) + _scriptManager->killSideFxType((ScriptingEffect::ScriptingEffectType)_type); + else + _scriptManager->killSideFx(_key); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionMenuBarEnable +////////////////////////////////////////////////////////////////////////////// + +ActionMenuBarEnable::ActionMenuBarEnable(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _menus = 0xFFFF; + + sscanf(line.c_str(), "%hu", &_menus); +} + +bool ActionMenuBarEnable::execute() { + _engine->getMenuHandler()->setEnable(_menus); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionMusic +////////////////////////////////////////////////////////////////////////////// + +ActionMusic::ActionMusic(ZVision *engine, int32 slotKey, const Common::String &line, bool global) : + ResultAction(engine, slotKey), + _note(0), + _prog(0), + _universe(global) { + uint type = 0; + char fileNameBuffer[25]; + uint loop = 0; + char volumeBuffer[15]; + + // Volume is optional. If it doesn't appear, assume full volume + strcpy(volumeBuffer, "100"); + + sscanf(line.c_str(), "%u %24s %u %14s", &type, fileNameBuffer, &loop, volumeBuffer); + + // Type 4 actions are MIDI commands, not files. These are only used by + // Zork: Nemesis, for the flute and piano puzzles (tj4e and ve6f, as well + // as vr) + if (type == 4) { + _midi = true; + int note; + int prog; + sscanf(line.c_str(), "%u %d %d %14s", &type, &prog, ¬e, volumeBuffer); + _volume = new ValueSlot(_scriptManager, volumeBuffer); + _note = note; + _prog = prog; + } else { + _midi = false; + _fileName = Common::String(fileNameBuffer); + _loop = loop == 1 ? true : false; + if (volumeBuffer[0] != '[' && atoi(volumeBuffer) > 100) { + // I thought I saw a case like this in Zork Nemesis, so + // let's guard against it. + warning("ActionMusic: Adjusting volume for %s from %s to 100", _fileName.c_str(), volumeBuffer); + strcpy(volumeBuffer, "100"); + } + _volume = new ValueSlot(_scriptManager, volumeBuffer); + } + + // WORKAROUND for a script bug in Zork Nemesis, rooms mq70/mq80. + // Fixes an edge case where the player goes to the dark room with the grue + // without holding a torch, and then quickly runs away before the grue's + // sound effect finishes. Fixes script bug #6794. + if (engine->getGameId() == GID_NEMESIS && _slotKey == 14822 && _scriptManager->getStateValue(_slotKey) == 2) + _scriptManager->setStateValue(_slotKey, 0); + +} + +ActionMusic::~ActionMusic() { + if (!_universe) + _scriptManager->killSideFx(_slotKey); + delete _volume; +} + +bool ActionMusic::execute() { + if (_scriptManager->getSideFX(_slotKey)) { + _scriptManager->killSideFx(_slotKey); + _scriptManager->setStateValue(_slotKey, 2); + } + + uint volume = _volume->getValue(); + + if (_midi) { + _scriptManager->addSideFX(new MusicMidiNode(_engine, _slotKey, _prog, _note, volume)); + } else { + if (!_engine->getSearchManager()->hasFile(_fileName)) + return true; + + // Volume in the script files is mapped to [0, 100], but the ScummVM mixer uses [0, 255] + _scriptManager->addSideFX(new MusicNode(_engine, _slotKey, _fileName, _loop, volume * 255 / 100)); + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionPanTrack +////////////////////////////////////////////////////////////////////////////// + +ActionPanTrack::ActionPanTrack(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey), + _pos(0), + _musicSlot(0) { + + sscanf(line.c_str(), "%u %d", &_musicSlot, &_pos); +} + +ActionPanTrack::~ActionPanTrack() { + _scriptManager->killSideFx(_slotKey); +} + +bool ActionPanTrack::execute() { + if (_scriptManager->getSideFX(_slotKey)) + return true; + + _scriptManager->addSideFX(new PanTrackNode(_engine, _slotKey, _musicSlot, _pos)); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionPreferences +////////////////////////////////////////////////////////////////////////////// + +ActionPreferences::ActionPreferences(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + if (line.compareToIgnoreCase("save") == 0) + _save = true; + else + _save = false; +} + +bool ActionPreferences::execute() { + if (_save) + _engine->saveSettings(); + else + _engine->loadSettings(); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionPreloadAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionPreloadAnimation::ActionPreloadAnimation(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _mask = 0; + _framerate = 0; + + char fileName[25]; + + // The two %*u are usually 0 and dont seem to have a use + sscanf(line.c_str(), "%24s %*u %*u %d %d", fileName, &_mask, &_framerate); + + // Mask 0 means "no transparency" in this case. Since we use a common blitting + // code for images and animations, we set it to -1 to avoid confusion with + // color 0, which is used as a mask in some images + if (_mask == 0) + _mask = -1; + + _fileName = Common::String(fileName); +} + +ActionPreloadAnimation::~ActionPreloadAnimation() { + _scriptManager->deleteSideFx(_slotKey); +} + +bool ActionPreloadAnimation::execute() { + AnimationEffect *nod = (AnimationEffect *)_scriptManager->getSideFX(_slotKey); + + if (!nod) { + nod = new AnimationEffect(_engine, _slotKey, _fileName, _mask, _framerate, false); + _scriptManager->addSideFX(nod); + } else + nod->stop(); + _scriptManager->setStateValue(_slotKey, 2); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionUnloadAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionUnloadAnimation::ActionUnloadAnimation(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _key = 0; + + sscanf(line.c_str(), "%u", &_key); +} + +bool ActionUnloadAnimation::execute() { + AnimationEffect *nod = (AnimationEffect *)_scriptManager->getSideFX(_key); + + if (nod && nod->getType() == ScriptingEffect::SCRIPTING_EFFECT_ANIM) + _scriptManager->deleteSideFx(_key); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionPlayAnimation +////////////////////////////////////////////////////////////////////////////// + +ActionPlayAnimation::ActionPlayAnimation(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _x = 0; + _y = 0; + _x2 = 0; + _y2 = 0; + _start = 0; + _end = 0; + _loopCount = 0; + _mask = 0; + _framerate = 0; + + char fileName[25]; + + // The two %*u are always 0 and dont seem to have a use + sscanf(line.c_str(), + "%24s %u %u %u %u %u %u %d %*u %*u %d %d", + fileName, &_x, &_y, &_x2, &_y2, &_start, &_end, &_loopCount, &_mask, &_framerate); + + // Mask 0 means "no transparency" in this case. Since we use a common blitting + // code for images and animations, we set it to -1 to avoid confusion with + // color 0, which is used as a mask in some images + if (_mask == 0) + _mask = -1; + + _fileName = Common::String(fileName); + + // WORKAROUND for bug #6769, location me1g.scr (the "Alchemical debacle" + // video in ZGI). We only scale up by 2x, in AnimationEffect::process(), + // but the dimensions of the target frame are off by 2 pixels. We fix that + // here, so that the video can be scaled. + if (_fileName == "me1ga011.avi" && _y2 == 213) + _y2 = 215; +} + +ActionPlayAnimation::~ActionPlayAnimation() { + _scriptManager->deleteSideFx(_slotKey); +} + +bool ActionPlayAnimation::execute() { + AnimationEffect *nod = (AnimationEffect *)_scriptManager->getSideFX(_slotKey); + + if (!nod) { + nod = new AnimationEffect(_engine, _slotKey, _fileName, _mask, _framerate); + _scriptManager->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) { + _controlKey = 0; + _x1 = 0; + _y1 = 0; + _x2 = 0; + _y2 = 0; + _startFrame = 0; + _endFrame = 0; + _loopCount = 0; + + sscanf(line.c_str(), + "%u %u %u %u %u %u %u %u", + &_controlKey, &_x1, &_y1, &_x2, &_y2, &_startFrame, &_endFrame, &_loopCount); +} + +bool ActionPlayPreloadAnimation::execute() { + AnimationEffect *nod = (AnimationEffect *)_scriptManager->getSideFX(_controlKey); + + if (nod) + nod->addPlayNode(_slotKey, _x1, _y1, _x2, _y2, _startFrame, _endFrame, _loopCount); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionQuit +////////////////////////////////////////////////////////////////////////////// + +bool ActionQuit::execute() { + _engine->quitGame(); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionRegion - only used by Zork: Nemesis +////////////////////////////////////////////////////////////////////////////// + +ActionRegion::ActionRegion(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _delay = 0; + _type = 0; + _unk1 = 0; + _unk2 = 0; + + char art[64]; + char custom[64]; + + int32 x1 = 0, x2 = 0, y1 = 0, y2 = 0; + + sscanf(line.c_str(), "%s %d %d %d %d %hu %hu %hu %hu %s", art, &x1, &y1, &x2, &y2, &_delay, &_type, &_unk1, &_unk2, custom); + _art = Common::String(art); + _custom = Common::String(custom); + _rect = Common::Rect(x1, y1, x2 + 1, y2 + 1); +} + +ActionRegion::~ActionRegion() { + _scriptManager->killSideFx(_slotKey); +} + +bool ActionRegion::execute() { + if (_scriptManager->getSideFX(_slotKey)) + return true; + + GraphicsEffect *effect = NULL; + switch (_type) { + case 0: { + uint16 centerX, centerY, frames; + double amplitude, waveln, speed; + sscanf(_custom.c_str(), "%hu,%hu,%hu,%lf,%lf,%lf,", ¢erX, ¢erY, &frames, &litude, &waveln, &speed); + effect = new WaveFx(_engine, _slotKey, _rect, _unk1, frames, centerX, centerY, amplitude, waveln, speed); + } + break; + case 1: { + uint16 aX, aY, aD; + if (_engine->getRenderManager()->getRenderTable()->getRenderState() == RenderTable::PANORAMA) + sscanf(_art.c_str(), "useart[%hu,%hu,%hu]", &aY, &aX, &aD); + else + sscanf(_art.c_str(), "useart[%hu,%hu,%hu]", &aX, &aY, &aD); + int8 minD; + int8 maxD; + EffectMap *_map = _engine->getRenderManager()->makeEffectMap(Common::Point(aX, aY), aD, _rect, &minD, &maxD); + effect = new LightFx(_engine, _slotKey, _rect, _unk1, _map, atoi(_custom.c_str()), minD, maxD); + } + break; + case 9: { + int16 dum1; + int32 dum2; + char buf[64]; + sscanf(_custom.c_str(), "%hd,%d,%s", &dum1, &dum2, buf); + Graphics::Surface tempMask; + _engine->getRenderManager()->readImageToSurface(_art, tempMask); + if (_rect.width() != tempMask.w) + _rect.setWidth(tempMask.w); + if (_rect.height() != tempMask.h) + _rect.setHeight(tempMask.h); + + EffectMap *_map = _engine->getRenderManager()->makeEffectMap(tempMask, 0); + effect = new FogFx(_engine, _slotKey, _rect, _unk1, _map, Common::String(buf)); + } + break; + default: + break; + } + + if (effect) { + _scriptManager->addSideFX(new RegionNode(_engine, _slotKey, effect, _delay)); + _engine->getRenderManager()->addEffect(effect); + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionRandom +////////////////////////////////////////////////////////////////////////////// + +ActionRandom::ActionRandom(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + char maxBuffer[64]; + memset(maxBuffer, 0, 64); + sscanf(line.c_str(), "%s", maxBuffer); + _max = new ValueSlot(_scriptManager, maxBuffer); +} + +ActionRandom::~ActionRandom() { + delete _max; +} + +bool ActionRandom::execute() { + uint randNumber = _engine->getRandomSource()->getRandomNumber(_max->getValue()); + _scriptManager->setStateValue(_slotKey, randNumber); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionRestoreGame +////////////////////////////////////////////////////////////////////////////// + +ActionRestoreGame::ActionRestoreGame(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + char buf[128]; + sscanf(line.c_str(), "%s", buf); + _fileName = Common::String(buf); +} + +bool ActionRestoreGame::execute() { + _engine->getSaveManager()->loadGame(-1); + return false; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionRotateTo +////////////////////////////////////////////////////////////////////////////// + +ActionRotateTo::ActionRotateTo(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _time = 0; + _toPos = 0; + + sscanf(line.c_str(), "%d, %d", &_toPos, &_time); +} + +bool ActionRotateTo::execute() { + _engine->getRenderManager()->rotateTo(_toPos, _time); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionSetPartialScreen +////////////////////////////////////////////////////////////////////////////// + +ActionSetPartialScreen::ActionSetPartialScreen(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _x = 0; + _y = 0; + + char fileName[25]; + + sscanf(line.c_str(), "%u %u %24s %*u %d", &_x, &_y, fileName, &_backgroundColor); + + _fileName = Common::String(fileName); + + if (_backgroundColor > 65535) { + warning("Background color for ActionSetPartialScreen is bigger than a uint16"); + } +} + +bool ActionSetPartialScreen::execute() { + RenderManager *renderManager = _engine->getRenderManager(); + + if (_engine->getGameId() == GID_NEMESIS) { + if (_backgroundColor) + renderManager->renderImageToBackground(_fileName, _x, _y, 0, 0); + else + renderManager->renderImageToBackground(_fileName, _x, _y); + } else { + if (_backgroundColor >= 0) + renderManager->renderImageToBackground(_fileName, _x, _y, _backgroundColor); + else if (_backgroundColor == -2) + renderManager->renderImageToBackground(_fileName, _x, _y, 0, 0); + 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(), "%24s", 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() { + _scriptManager->stopSideFx(_key); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionStreamVideo +////////////////////////////////////////////////////////////////////////////// + +ActionStreamVideo::ActionStreamVideo(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _x1 = 0; + _x2 = 0; + _y1 = 0; + _y2 = 0; + _flags = 0; + + char fileName[25]; + uint skipline = 0; //skipline - render video with skip every second line, not skippable. + + sscanf(line.c_str(), "%24s %u %u %u %u %u %u", fileName, &_x1, &_y1, &_x2, &_y2, &_flags, &skipline); + + _fileName = Common::String(fileName); + _skippable = true; +} + +bool ActionStreamVideo::execute() { + Video::VideoDecoder *decoder; + 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); + bool subtitleExists = _engine->getSearchManager()->hasFile(subname); + bool switchToHires = false; + +// NOTE: We only show the hires MPEG2 videos when libmpeg2 is compiled in, +// otherwise we fall back to the lowres ones +#ifdef USE_MPEG2 + Common::String hiresFileName = _fileName; + hiresFileName.setChar('d', hiresFileName.size() - 8); + hiresFileName.setChar('v', hiresFileName.size() - 3); + hiresFileName.setChar('o', hiresFileName.size() - 2); + hiresFileName.setChar('b', hiresFileName.size() - 1); + + if (_scriptManager->getStateValue(StateKey_MPEGMovies) == 1 &&_engine->getSearchManager()->hasFile(hiresFileName)) { + // TODO: Enable once AC3 support is implemented + if (!_engine->getSearchManager()->hasFile(_fileName)) // Check for the regular video + return true; + warning("The hires videos of the DVD version of ZGI aren't supported yet, using lowres"); + //_fileName = hiresFileName; + //switchToHires = true; + } else if (!_engine->getSearchManager()->hasFile(_fileName)) + return true; +#else + if (!_engine->getSearchManager()->hasFile(_fileName)) + return true; +#endif + + decoder = _engine->loadAnimation(_fileName); + Subtitle *sub = (subtitleExists) ? new Subtitle(_engine, subname, switchToHires) : NULL; + + _engine->getCursorManager()->showMouse(false); + + if (switchToHires) { + _engine->initHiresScreen(); + destRect = Common::Rect(40, -40, 760, 440); + Common::Rect workingWindow = _engine->_workingWindow; + workingWindow.translate(0, -40); + _engine->getRenderManager()->initSubArea(HIRES_WINDOW_WIDTH, HIRES_WINDOW_HEIGHT, workingWindow); + } + + _engine->playVideo(*decoder, destRect, _skippable, sub); + + if (switchToHires) { + _engine->initScreen(); + _engine->getRenderManager()->initSubArea(WINDOW_WIDTH, WINDOW_HEIGHT, _engine->_workingWindow); + } + + _engine->getCursorManager()->showMouse(true); + + delete decoder; + delete sub; + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionSyncSound +////////////////////////////////////////////////////////////////////////////// + +ActionSyncSound::ActionSyncSound(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _syncto = 0; + + char fileName[25]; + int notUsed = 0; + + sscanf(line.c_str(), "%d %d %24s", &_syncto, ¬Used, fileName); + + _fileName = Common::String(fileName); +} + +bool ActionSyncSound::execute() { + ScriptingEffect *fx = _scriptManager->getSideFX(_syncto); + if (!fx) + return true; + + if (!(fx->getType() & ScriptingEffect::SCRIPTING_EFFECT_ANIM)) + return true; + + _scriptManager->addSideFX(new SyncSoundNode(_engine, _slotKey, _fileName, _syncto)); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionTimer +////////////////////////////////////////////////////////////////////////////// + +ActionTimer::ActionTimer(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + char timeBuffer[64]; + memset(timeBuffer, 0, 64); + sscanf(line.c_str(), "%s", timeBuffer); + _time = new ValueSlot(_scriptManager, timeBuffer); +} + +ActionTimer::~ActionTimer() { + delete _time; + _scriptManager->killSideFx(_slotKey); +} + +bool ActionTimer::execute() { + if (_scriptManager->getSideFX(_slotKey)) + return true; + _scriptManager->addSideFX(new TimerNode(_engine, _slotKey, _time->getValue())); + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// ActionTtyText +////////////////////////////////////////////////////////////////////////////// + +ActionTtyText::ActionTtyText(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { + _delay = 0; + + char filename[64]; + int32 x1 = 0, y1 = 0, x2 = 0, y2 = 0; + sscanf(line.c_str(), "%d %d %d %d %63s %u", &x1, &y1, &x2, &y2, filename, &_delay); + _r = Common::Rect(x1, y1, x2, y2); + _filename = Common::String(filename); +} + +ActionTtyText::~ActionTtyText() { + _scriptManager->killSideFx(_slotKey); +} + +bool ActionTtyText::execute() { + if (_scriptManager->getSideFX(_slotKey)) + return true; + _scriptManager->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..bde1baa291 --- /dev/null +++ b/engines/zvision/scripting/actions.h @@ -0,0 +1,444 @@ +/* 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 ScriptManager; +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); + 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; + ScriptManager *_scriptManager; + int32 _slotKey; +}; + +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 ActionCursor : public ResultAction { +public: + ActionCursor(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint8 _action; +}; + +class ActionDelayRender : public ResultAction { +public: + ActionDelayRender(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _framesToDelay; +}; + +class ActionDisableControl : public ResultAction { +public: + ActionDisableControl(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; +}; + +class ActionDisplayMessage : public ResultAction { +public: + ActionDisplayMessage(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + int16 _control; + int16 _msgid; +}; + +class ActionDissolve : public ResultAction { +public: + ActionDissolve(ZVision *engine); + bool execute(); +}; + +class ActionDistort : public ResultAction { +public: + ActionDistort(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionDistort(); + bool execute(); + +private: + int16 _distSlot; + int16 _speed; + float _startAngle; + float _endAngle; + float _startLineScale; + float _endLineScale; +}; + +class ActionEnableControl : public ResultAction { +public: + ActionEnableControl(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + uint32 _key; +}; + +class ActionFlushMouseEvents : public ResultAction { +public: + ActionFlushMouseEvents(ZVision *engine, int32 slotkey); + bool execute(); +}; + +class ActionInventory : public ResultAction { +public: + ActionInventory(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); +private: + int8 _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 ActionMenuBarEnable : public ResultAction { +public: + ActionMenuBarEnable(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); +private: + uint16 _menus; +}; + +class ActionMusic : public ResultAction { +public: + ActionMusic(ZVision *engine, int32 slotkey, const Common::String &line, bool global); + ~ActionMusic(); + bool execute(); + +private: + Common::String _fileName; + bool _loop; + ValueSlot *_volume; + bool _universe; + bool _midi; + int8 _note; + int8 _prog; +}; + +class ActionPanTrack : public ResultAction { +public: + ActionPanTrack(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionPanTrack(); + bool execute(); + +private: + int32 _pos; + uint32 _musicSlot; +}; + +class ActionPlayAnimation : public ResultAction { +public: + ActionPlayAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionPlayAnimation(); + bool execute(); + +private: + 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 _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: + Common::String _fileName; + int32 _mask; + int32 _framerate; +}; + +class ActionPreferences : public ResultAction { +public: + ActionPreferences(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + bool _save; +}; + +class ActionQuit : public ResultAction { +public: + ActionQuit(ZVision *engine, int32 slotkey) : ResultAction(engine, slotkey) {} + bool execute(); +}; + +class ActionRegion : public ResultAction { +public: + ActionRegion(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionRegion(); + bool execute(); + +private: + Common::String _art; + Common::String _custom; + Common::Rect _rect; + uint16 _delay; + uint16 _type; + uint16 _unk1; + uint16 _unk2; +}; + +// Only used by ZGI (locations cd6e, cd6k, dg2f, dg4e, dv1j) +class ActionUnloadAnimation : public ResultAction { +public: + ActionUnloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); +private: + uint32 _key; +}; + +class ActionRandom : public ResultAction { +public: + ActionRandom(ZVision *engine, int32 slotkey, const Common::String &line); + ~ActionRandom(); + bool execute(); + +private: + ValueSlot *_max; +}; + +class ActionRestoreGame : public ResultAction { +public: + ActionRestoreGame(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + Common::String _fileName; +}; + +class ActionRotateTo : public ResultAction { +public: + ActionRotateTo(ZVision *engine, int32 slotkey, const Common::String &line); + bool execute(); + +private: + int32 _toPos; + int32 _time; +}; + +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: + 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..81123eb99b --- /dev/null +++ b/engines/zvision/scripting/control.cpp @@ -0,0 +1,138 @@ +/* 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/scripting/script_manager.h" + +#include "zvision/zvision.h" +#include "zvision/graphics/render_manager.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(); + engine->getScriptManager()->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)) { + uint point; + sscanf(line.c_str(), "zeropoint(%u)", &point); + renderTable->setPanoramaZeroPoint(point); + } + + line = stream.readLine(); + engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + } + + renderTable->generateRenderTable(); +} + +// Only used in Zork Nemesis, handles tilt controls (ZGI doesn't have a tilt view) +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(); + engine->getScriptManager()->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(); + engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + } + + renderTable->generateRenderTable(); +} + +void Control::getParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values) { + const char *chrs = inputStr.c_str(); + uint lbr; + + for (lbr = 0; lbr < inputStr.size(); lbr++) + if (chrs[lbr] == '(') + break; + + if (lbr >= inputStr.size()) + return; + + uint rbr; + + for (rbr = lbr + 1; rbr < inputStr.size(); rbr++) + if (chrs[rbr] == ')') + break; + + if (rbr >= inputStr.size()) + return; + + parameter = Common::String(chrs, chrs + lbr); + values = Common::String(chrs + lbr + 1, chrs + rbr); +} + +void Control::setVenus() { + if (_venusId >= 0) + if (_engine->getScriptManager()->getStateValue(_venusId) > 0) + _engine->getScriptManager()->setStateValue(StateKey_Venus, _venusId); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/control.h b/engines/zvision/scripting/control.h new file mode 100644 index 0000000000..108b83fd00 --- /dev/null +++ b/engines/zvision/scripting/control.h @@ -0,0 +1,147 @@ +/* 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; + +/** + * The base class for all Controls. + * + * Controls are the things that the user interacts with. Ex: A lever on the door + */ +class Control { +public: + + enum ControlType { + CONTROL_UNKNOW, + CONTROL_INPUT, + CONTROL_PUSHTGL, + CONTROL_SLOT, + CONTROL_LEVER, + CONTROL_SAVE, + CONTROL_SAFE, + CONTROL_FIST, + CONTROL_TITLER, + CONTROL_HOTMOV, + CONTROL_PAINT + }; + + Control(ZVision *engine, uint32 key, ControlType type) : _engine(engine), _key(key), _type(type), _venusId(-1) {} + virtual ~Control() {} + + uint32 getKey() { + return _key; + } + + ControlType getType() { + return _type; + } + + 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 bool onKeyDown(Common::KeyState keyState) { + return false; + } + /** + * Called when a key is released. Default is NOP. + * + * @param keycode The key that was pressed + */ + virtual bool onKeyUp(Common::KeyState keyState) { + return false; + } + /** + * 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; + } + + void setVenus(); + +protected: + ZVision *_engine; + uint32 _key; + int32 _venusId; + + void getParams(const Common::String &inputStr, 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); +private: + ControlType _type; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/fist_control.cpp b/engines/zvision/scripting/controls/fist_control.cpp new file mode 100644 index 0000000000..f79c82dc79 --- /dev/null +++ b/engines/zvision/scripting/controls/fist_control.cpp @@ -0,0 +1,304 @@ +/* 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/scripting/script_manager.h" +#include "zvision/scripting/controls/fist_control.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" +#include "zvision/video/rlf_decoder.h" + +#include "common/stream.h" +#include "common/file.h" +#include "common/system.h" +#include "graphics/surface.h" +#include "video/video_decoder.h" + +namespace ZVision { + +FistControl::FistControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_FIST) { + _cursor = CursorIndex_Idle; + _animation = NULL; + _soundKey = 0; + _fiststatus = 0; + _order = 0; + _fistnum = 0; + + _animationId = 0; + + clearFistArray(_fistsUp); + clearFistArray(_fistsDwn); + + _numEntries = 0; + _entries.clear(); + + _anmRect = Common::Rect(); + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("sound_key", true)) { + _soundKey = atoi(values.c_str()); + } else if (param.matchString("cursor", true)) { + _cursor = _engine->getCursorManager()->getCursorId(values); + } else if (param.matchString("descfile", true)) { + readDescFile(values); + } else if (param.matchString("animation_id", true)) { + _animationId = atoi(values.c_str()); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } +} + +FistControl::~FistControl() { + if (_animation) + delete _animation; + + clearFistArray(_fistsUp); + clearFistArray(_fistsDwn); + _entries.clear(); +} + +bool FistControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_animation && _animation->isPlaying()) { + if (_animation->endOfVideo()) { + _animation->stop(); + _engine->getScriptManager()->setStateValue(_animationId, 2); + return false; + } + + if (_animation->needsUpdate()) { + const Graphics::Surface *frameData = _animation->decodeNextFrame(); + if (frameData) + // WORKAROUND: Ignore the target frame dimensions for the finger animations. + // The target dimensions specify an area smaller than expected, thus if we + // scale the finger videos to fit these dimensions, they are not aligned + // correctly. Not scaling these videos yields a result identical to the + // original. Fixes bug #6784. + _engine->getRenderManager()->blitSurfaceToBkg(*frameData, _anmRect.left, _anmRect.top); + } + } + + return false; +} + +bool FistControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (mouseIn(screenSpacePos, backgroundImageSpacePos) >= 0) { + _engine->getCursorManager()->changeCursor(_cursor); + return true; + } + + return false; +} + +bool FistControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + int fistNumber = mouseIn(screenSpacePos, backgroundImageSpacePos); + + if (fistNumber >= 0) { + setVenus(); + + uint32 oldStatus = _fiststatus; + _fiststatus ^= (1 << fistNumber); + + for (int i = 0; i < _numEntries; i++) + if (_entries[i]._bitsStrt == oldStatus && _entries[i]._bitsEnd == _fiststatus) { + if (_animation) { + _animation->stop(); + _animation->seekToFrame(_entries[i]._anmStrt); + _animation->setEndFrame(_entries[i]._anmEnd); + _animation->start(); + } + + _engine->getScriptManager()->setStateValue(_animationId, 1); + _engine->getScriptManager()->setStateValue(_soundKey, _entries[i]._sound); + break; + } + + _engine->getScriptManager()->setStateValue(_key, _fiststatus); + } + + return false; +} + +void FistControl::readDescFile(const Common::String &fileName) { + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + warning("Desc file %s could could be opened", fileName.c_str()); + return; + } + + Common::String line; + Common::String param; + Common::String values; + + while (!file.eos()) { + line = file.readLine(); + getFistParams(line, param, values); + + if (param.matchString("animation_id", true)) { + // Not used + } else if (param.matchString("animation", true)) { + _animation = _engine->loadAnimation(values); + } else if (param.matchString("anim_rect", true)) { + int left, top, right, bottom; + sscanf(values.c_str(), "%d %d %d %d", &left, &top, &right, &bottom); + _anmRect = Common::Rect(left, top, right, bottom); + } else if (param.matchString("num_fingers", true)) { + sscanf(values.c_str(), "%d", &_fistnum); + _fistsUp.resize(_fistnum); + _fistsDwn.resize(_fistnum); + } else if (param.matchString("entries", true)) { + sscanf(values.c_str(), "%d", &_numEntries); + _entries.resize(_numEntries); + } else if (param.matchString("eval_order_ascending", true)) { + sscanf(values.c_str(), "%d", &_order); + } else if (param.matchString("up_hs_num_*", true)) { + int fist, num; + num = atoi(values.c_str()); + + sscanf(param.c_str(), "up_hs_num_%d", &fist); + _fistsUp[fist].resize(num); + } else if (param.matchString("up_hs_*", true)) { + int16 fist, box, x1, y1, x2, y2; + sscanf(param.c_str(), "up_hs_%hd_%hd", &fist, &box); + sscanf(values.c_str(), "%hd %hd %hd %hd", &x1, &y1, &x2, &y2); + (_fistsUp[fist])[box] = Common::Rect(x1, y1, x2, y2); + } else if (param.matchString("down_hs_num_*", true)) { + int fist, num; + num = atoi(values.c_str()); + + sscanf(param.c_str(), "down_hs_num_%d", &fist); + _fistsDwn[fist].resize(num); + } else if (param.matchString("down_hs_*", true)) { + int16 fist, box, x1, y1, x2, y2; + sscanf(param.c_str(), "down_hs_%hd_%hd", &fist, &box); + sscanf(values.c_str(), "%hd %hd %hd %hd", &x1, &y1, &x2, &y2); + (_fistsDwn[fist])[box] = Common::Rect(x1, y1, x2, y2); + } else { + int entry, start, end, sound; + char bitsStart[33]; + char bitsEnd[33]; + entry = atoi(param.c_str()); + if (sscanf(values.c_str(), "%s %s %d %d (%d)", bitsStart, bitsEnd, &start, &end, &sound) == 5) { + _entries[entry]._bitsStrt = readBits(bitsStart); + _entries[entry]._bitsEnd = readBits(bitsEnd); + _entries[entry]._anmStrt = start; + _entries[entry]._anmEnd = end; + _entries[entry]._sound = sound; + } + } + } + file.close(); +} + +void FistControl::clearFistArray(Common::Array< Common::Array<Common::Rect> > &arr) { + for (uint i = 0; i < arr.size(); i++) + arr[i].clear(); + + arr.clear(); +} + +uint32 FistControl::readBits(const char *str) { + uint32 bfield = 0; + int len = strlen(str); + for (int i = 0; i < len; i++) + if (str[i] != '0') + bfield |= (1 << i); + return bfield; +} + +int FistControl::mouseIn(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_order) { + for (int i = 0; i < _fistnum; i++) { + if (((_fiststatus >> i) & 1) == 1) { + for (uint j = 0; j < _fistsDwn[i].size(); j++) + if ((_fistsDwn[i])[j].contains(backgroundImageSpacePos)) + return i; + } else { + for (uint j = 0; j < _fistsUp[i].size(); j++) + if ((_fistsUp[i])[j].contains(backgroundImageSpacePos)) + return i; + } + } + } else { + for (int i = _fistnum - 1; i >= 0; i--) { + if (((_fiststatus >> i) & 1) == 1) { + for (uint j = 0; j < _fistsDwn[i].size(); j++) + if ((_fistsDwn[i])[j].contains(backgroundImageSpacePos)) + return i; + } else { + for (uint j = 0; j < _fistsUp[i].size(); j++) + if ((_fistsUp[i])[j].contains(backgroundImageSpacePos)) + return i; + } + } + } + return -1; +} + +void FistControl::getFistParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values) { + const char *chrs = inputStr.c_str(); + uint lbr; + + for (lbr = 0; lbr < inputStr.size(); lbr++) + if (chrs[lbr] == ':') + break; + + if (lbr >= inputStr.size()) + return; + + uint rbr; + + for (rbr = lbr + 1; rbr < inputStr.size(); rbr++) + if (chrs[rbr] == '~') + break; + + if (rbr >= inputStr.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/controls/fist_control.h b/engines/zvision/scripting/controls/fist_control.h new file mode 100644 index 0000000000..d7cbcb1f71 --- /dev/null +++ b/engines/zvision/scripting/controls/fist_control.h @@ -0,0 +1,84 @@ +/* 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_FIST_CONTROL_H +#define ZVISION_FIST_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/array.h" +#include "common/rect.h" + +namespace Video { + class VideoDecoder; +} + +namespace ZVision { + +// Only used in Zork Nemesis, handles the door lock puzzle with the skeletal fingers (td9e) +class FistControl : public Control { +public: + FistControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~FistControl(); + +private: + uint32 _fiststatus; + int _fistnum; + int16 _cursor; + int _order; + + Common::Array< Common::Array<Common::Rect> > _fistsUp; + Common::Array< Common::Array<Common::Rect> > _fistsDwn; + + int32 _numEntries; + + struct entries { + uint32 _bitsStrt; + uint32 _bitsEnd; + int32 _anmStrt; + int32 _anmEnd; + int32 _sound; + }; + + Common::Array<entries> _entries; + + Video::VideoDecoder *_animation; + Common::Rect _anmRect; + int32 _soundKey; + int32 _animationId; + +public: + 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 readDescFile(const Common::String &fileName); + void clearFistArray(Common::Array< Common::Array<Common::Rect> > &arr); + uint32 readBits(const char *str); + int mouseIn(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + void getFistParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/hotmov_control.cpp b/engines/zvision/scripting/controls/hotmov_control.cpp new file mode 100644 index 0000000000..182447a990 --- /dev/null +++ b/engines/zvision/scripting/controls/hotmov_control.cpp @@ -0,0 +1,188 @@ +/* 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/hotmov_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" + +#include "common/stream.h" +#include "common/file.h" +#include "common/system.h" +#include "graphics/surface.h" +#include "video/video_decoder.h" + +namespace ZVision { + +HotMovControl::HotMovControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_HOTMOV) { + _animation = NULL; + _cycle = 0; + _frames.clear(); + _cyclesCount = 0; + _framesCount = 0; + + _engine->getScriptManager()->setStateValue(_key, 0); + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("hs_frame_list", true)) { + readHsFile(values); + } else if (param.matchString("rectangle", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _rectangle = Common::Rect(x, y, width, height); + } else if (param.matchString("num_frames", true)) { + _framesCount = atoi(values.c_str()); + } else if (param.matchString("num_cycles", true)) { + _cyclesCount = atoi(values.c_str()); + } else if (param.matchString("animation", true)) { + char filename[64]; + sscanf(values.c_str(), "%s", filename); + values = Common::String(filename); + _animation = _engine->loadAnimation(values); + _animation->start(); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } +} + +HotMovControl::~HotMovControl() { + if (_animation) + delete _animation; + + _frames.clear(); +} + +bool HotMovControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_cycle < _cyclesCount) { + if (_animation && _animation->endOfVideo()) { + _cycle++; + + if (_cycle == _cyclesCount) { + _engine->getScriptManager()->setStateValue(_key, 2); + return false; + } + + _animation->rewind(); + } + + if (_animation && _animation->needsUpdate()) { + const Graphics::Surface *frameData = _animation->decodeNextFrame(); + if (frameData) + _engine->getRenderManager()->blitSurfaceToBkgScaled(*frameData, _rectangle); + } + } + + return false; +} + +bool HotMovControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (!_animation) + return false; + + if (_cycle < _cyclesCount) { + if (_frames[_animation->getCurFrame()].contains(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(CursorIndex_Active); + return true; + } + } + + return false; +} + +bool HotMovControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (!_animation) + return false; + + if (_cycle < _cyclesCount) { + if (_frames[_animation->getCurFrame()].contains(backgroundImageSpacePos)) { + setVenus(); + _engine->getScriptManager()->setStateValue(_key, 1); + return true; + } + } + + return false; +} + +void HotMovControl::readHsFile(const Common::String &fileName) { + if (_framesCount == 0) + return; + + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + warning("HS file %s could could be opened", fileName.c_str()); + return; + } + + Common::String line; + + _frames.resize(_framesCount); + + while (!file.eos()) { + line = file.readLine(); + + int frame; + int x; + int y; + int width; + int height; + + sscanf(line.c_str(), "%d:%d %d %d %d~", &frame, &x, &y, &width, &height); + + if (frame >= 0 && frame < _framesCount) + _frames[frame] = Common::Rect(x, y, width, height); + } + file.close(); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/hotmov_control.h b/engines/zvision/scripting/controls/hotmov_control.h new file mode 100644 index 0000000000..99d1fd0979 --- /dev/null +++ b/engines/zvision/scripting/controls/hotmov_control.h @@ -0,0 +1,61 @@ +/* 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_HOTMOV_CONTROL_H +#define ZVISION_HOTMOV_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/array.h" +#include "common/rect.h" + +namespace Video { + class VideoDecoder; +} + +namespace ZVision { + +// Only used in Zork Nemesis, handles movies where the player needs to click on something (mj7g, vw3g) +class HotMovControl : public Control { +public: + HotMovControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~HotMovControl(); + +private: + int32 _framesCount; + int32 _cycle; + int32 _cyclesCount; + Video::VideoDecoder *_animation; + Common::Rect _rectangle; + Common::Array<Common::Rect> _frames; +public: + 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 readHsFile(const Common::String &fileName); +}; + +} // 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..9525333ef0 --- /dev/null +++ b/engines/zvision/scripting/controls/input_control.cpp @@ -0,0 +1,270 @@ +/* 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/input_control.h" +#include "zvision/graphics/cursors/cursor_manager.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/text/string_manager.h" +#include "zvision/graphics/render_manager.h" + +#include "common/str.h" +#include "common/stream.h" +#include "common/rect.h" +#include "video/video_decoder.h" + +namespace ZVision { + +InputControl::InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_INPUT), + _background(0), + _nextTabstop(0), + _focused(false), + _textChanged(false), + _enterPressed(false), + _readOnly(false), + _txtWidth(0), + _animation(NULL) { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("rectangle", true)) { + int x1; + int y1; + int x2; + int y2; + + sscanf(values.c_str(), "%d %d %d %d", &x1, &y1, &x2, &y2); + + _textRectangle = Common::Rect(x1, y1, x2, y2); + } else if (param.matchString("aux_hotspot", true)) { + int x1; + int y1; + int x2; + int y2; + + sscanf(values.c_str(), "%d %d %d %d", &x1, &y1, &x2, &y2); + + _headerRectangle = Common::Rect(x1, y1, x2, y2); + } else if (param.matchString("string_init", true)) { + uint fontFormatNumber; + + sscanf(values.c_str(), "%u", &fontFormatNumber); + + _stringInit.readAllStyles(_engine->getStringManager()->getTextLine(fontFormatNumber)); + } else if (param.matchString("chooser_init_string", true)) { + uint fontFormatNumber; + + sscanf(values.c_str(), "%u", &fontFormatNumber); + + _stringChooserInit.readAllStyles(_engine->getStringManager()->getTextLine(fontFormatNumber)); + } else if (param.matchString("next_tabstop", true)) { + sscanf(values.c_str(), "%u", &_nextTabstop); + } else if (param.matchString("cursor_dimensions", true)) { + // Ignore, use the dimensions in the animation file + } else if (param.matchString("cursor_animation_frames", true)) { + // Ignore, use the frame count in the animation file + } else if (param.matchString("cursor_animation", true)) { + char fileName[25]; + + sscanf(values.c_str(), "%24s %*u", fileName); + + _animation = _engine->loadAnimation(fileName); + _animation->start(); + } else if (param.matchString("focus", true)) { + _focused = true; + _engine->getScriptManager()->setFocusControlKey(_key); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + _maxTxtWidth = _textRectangle.width(); + if (_animation) + _maxTxtWidth -= _animation->getWidth(); +} + +InputControl::~InputControl() { + _background->free(); + delete _background; +} + +bool InputControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_textRectangle.contains(backgroundImageSpacePos)) { + if (!_readOnly) { + // Save + _engine->getScriptManager()->focusControl(_key); + setVenus(); + } else { + // Restore + if (_currentInputText.size()) { + setVenus(); + _enterPressed = true; + } + } + } + return false; +} + +bool InputControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_textRectangle.contains(backgroundImageSpacePos)) { + if (!_readOnly) { + // Save + _engine->getCursorManager()->changeCursor(CursorIndex_Active); + return true; + } else { + // Restore + if (_currentInputText.size()) { + _engine->getCursorManager()->changeCursor(CursorIndex_Active); + _engine->getScriptManager()->focusControl(_key); + return true; + } + } + } + return false; +} + +bool InputControl::onKeyDown(Common::KeyState keyState) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (!_focused) { + return false; + } + + if (keyState.keycode == Common::KEYCODE_BACKSPACE) { + if (!_readOnly) { + _currentInputText.deleteLastChar(); + _textChanged = true; + } + } else if (keyState.keycode == Common::KEYCODE_RETURN) { + _enterPressed = true; + } else if (keyState.keycode == Common::KEYCODE_TAB) { + unfocus(); + // Focus the next input control + _engine->getScriptManager()->focusControl(_nextTabstop); + // Don't process this event for other controls + return true; + } else { + if (!_readOnly) { + // 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; + } + } + } + return false; +} + +bool InputControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (!_background) { + _background = _engine->getRenderManager()->getBkgRect(_textRectangle); + } + + // First see if we need to render the text + if (_textChanged) { + // Blit the text using the RenderManager + + Graphics::Surface txt; + txt.copyFrom(*_background); + + int32 oldTxtWidth = _txtWidth; + + if (!_readOnly || !_focused) + _txtWidth = _engine->getTextRenderer()->drawText(_currentInputText, _stringInit, txt); + else + _txtWidth = _engine->getTextRenderer()->drawText(_currentInputText, _stringChooserInit, txt); + + if (_readOnly || _txtWidth <= _maxTxtWidth) + _engine->getRenderManager()->blitSurfaceToBkg(txt, _textRectangle.left, _textRectangle.top); + else { + // Assume the last character caused the overflow. + _currentInputText.deleteLastChar(); + _txtWidth = oldTxtWidth; + } + + txt.free(); + } + + if (_animation && !_readOnly && _focused) { + if (_animation->endOfVideo()) + _animation->rewind(); + + if (_animation->needsUpdate()) { + const Graphics::Surface *srf = _animation->decodeNextFrame(); + int16 xx = _textRectangle.left + _txtWidth; + if (xx >= _textRectangle.left + (_textRectangle.width() - (int16)_animation->getWidth())) + xx = _textRectangle.left + _textRectangle.width() - (int16)_animation->getWidth(); + _engine->getRenderManager()->blitSurfaceToBkg(*srf, xx, _textRectangle.top); + } + } + + _textChanged = false; + return false; +} + +bool InputControl::enterPress() { + if (_enterPressed) { + _enterPressed = false; + return true; + } + return false; +} + +void InputControl::setText(const Common::String &_str) { + _currentInputText = _str; + _textChanged = true; +} + +const Common::String InputControl::getText() { + return _currentInputText; +} + +void InputControl::setReadOnly(bool readonly) { + _readOnly = readonly; +} + +} // 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..6abdb3c692 --- /dev/null +++ b/engines/zvision/scripting/controls/input_control.h @@ -0,0 +1,82 @@ +/* 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/text/text.h" +#include "zvision/text/string_manager.h" + +#include "common/rect.h" + +namespace Video { + class VideoDecoder; +} + +namespace ZVision { + +class InputControl : public Control { +public: + InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~InputControl(); + +private: + Graphics::Surface *_background; + Common::Rect _textRectangle; + Common::Rect _headerRectangle; + TextStyleState _stringInit; + TextStyleState _stringChooserInit; + uint32 _nextTabstop; + bool _focused; + + Common::String _currentInputText; + bool _textChanged; + bool _enterPressed; + bool _readOnly; + + int16 _txtWidth; + int16 _maxTxtWidth; + Video::VideoDecoder *_animation; + +public: + void focus() { + _focused = true; + _textChanged = true; + } + void unfocus() { + _focused = false; + _textChanged = true; + } + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onKeyDown(Common::KeyState keyState); + bool process(uint32 deltaTimeInMillis); + void setText(const Common::String &_str); + const Common::String getText(); + bool enterPress(); + void setReadOnly(bool); +}; + +} // 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..0f105b424c --- /dev/null +++ b/engines/zvision/scripting/controls/lever_control.cpp @@ -0,0 +1,411 @@ +/* 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/lever_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" + +#include "common/stream.h" +#include "common/file.h" +#include "common/tokenizer.h" +#include "common/system.h" +#include "graphics/surface.h" +#include "video/video_decoder.h" + +namespace ZVision { + +LeverControl::LeverControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_LEVER), + _frameInfo(0), + _frameCount(0), + _startFrame(0), + _currentFrame(0), + _lastRenderedFrame(0), + _mouseIsCaptured(false), + _isReturning(false), + _accumulatedTime(0), + _returnRoutesCurrentFrame(0), + _animation(NULL), + _cursor(CursorIndex_Active), + _mirrored(false) { + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("descfile", true)) { + char levFileName[25]; + sscanf(values.c_str(), "%24s", levFileName); + + parseLevFile(levFileName); + } else if (param.matchString("cursor", true)) { + char cursorName[25]; + sscanf(values.c_str(), "%24s", cursorName); + + _cursor = _engine->getCursorManager()->getCursorId(Common::String(cursorName)); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + 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; + Common::String param; + Common::String values; + + while (!file.eos()) { + line = file.readLine(); + getLevParams(line, param, values); + + if (param.matchString("animation_id", true)) { + // Not used + } else if (param.matchString("filename", true)) { + _animation = _engine->loadAnimation(values); + } else if (param.matchString("skipcolor", true)) { + // Not used + } else if (param.matchString("anim_coords", true)) { + int left, top, right, bottom; + sscanf(values.c_str(), "%d %d %d %d", &left, &top, &right, &bottom); + + _animationCoords.left = left; + _animationCoords.top = top; + _animationCoords.right = right; + _animationCoords.bottom = bottom; + } else if (param.matchString("mirrored", true)) { + uint mirrored; + sscanf(values.c_str(), "%u", &mirrored); + + _mirrored = mirrored == 0 ? false : true; + } else if (param.matchString("frames", true)) { + sscanf(values.c_str(), "%u", &_frameCount); + + _frameInfo = new FrameInfo[_frameCount]; + } else if (param.matchString("elsewhere", true)) { + // Not used + } else if (param.matchString("out_of_control", true)) { + // Not used + } else if (param.matchString("start_pos", true)) { + sscanf(values.c_str(), "%u", &_startFrame); + _currentFrame = _startFrame; + } else if (param.matchString("hotspot_deltas", true)) { + uint x; + uint y; + sscanf(values.c_str(), "%u %u", &x, &y); + + _hotspotDelta.x = x; + _hotspotDelta.y = y; + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } 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)) { + setVenus(); + _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 16 + // This is a heuristic. This determines how responsive the lever is to mouse movement. + if (_lastMousePos.sqrDist(backgroundImageSpacePos) >= 16) { + 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); + _engine->getScriptManager()->setStateValue(_key, _currentFrame); + break; + } + } + } + _engine->getCursorManager()->changeCursor(_cursor); + cursorWasChanged = true; + } else if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(_cursor); + 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; + + _animation->seekToFrame(frameNumber); + frameData = _animation->decodeNextFrame(); + if (frameData) + _engine->getRenderManager()->blitSurfaceToBkgScaled(*frameData, _animationCoords); +} + +void LeverControl::getLevParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values) { + const char *chrs = inputStr.c_str(); + uint lbr; + + for (lbr = 0; lbr < inputStr.size(); lbr++) + if (chrs[lbr] == ':') + break; + + if (lbr >= inputStr.size()) + return; + + uint rbr; + + for (rbr = lbr + 1; rbr < inputStr.size(); rbr++) + if (chrs[rbr] == '~') + break; + + if (rbr >= inputStr.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/controls/lever_control.h b/engines/zvision/scripting/controls/lever_control.h new file mode 100644 index 0000000000..8787234c51 --- /dev/null +++ b/engines/zvision/scripting/controls/lever_control.h @@ -0,0 +1,121 @@ +/* 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 Video { + class VideoDecoder; +} + +namespace ZVision { + +// Only used in Zork Nemesis, handles draggable levers (te2e, tm7e, tp2e, tt2e, tz2e) +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: + Video::VideoDecoder *_animation; + + int _cursor; + 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); + void getLevParams(const Common::String &inputStr, Common::String ¶meter, Common::String &values); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/paint_control.cpp b/engines/zvision/scripting/controls/paint_control.cpp new file mode 100644 index 0000000000..62dde3d170 --- /dev/null +++ b/engines/zvision/scripting/controls/paint_control.cpp @@ -0,0 +1,219 @@ +/* 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/paint_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" +#include "zvision/graphics/render_manager.h" + +namespace ZVision { + +PaintControl::PaintControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_PAINT) { + + _cursor = CursorIndex_Active; + _paint = NULL; + _bkg = NULL; + _brush = NULL; + _colorKey = 0; + _mouseDown = false; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("rectangle", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _rectangle = Common::Rect(x, y, width + x, height + y); + } else if (param.matchString("cursor", true)) { + _cursor = _engine->getCursorManager()->getCursorId(values); + } else if (param.matchString("brush_file", true)) { + _brush = _engine->getRenderManager()->loadImage(values, false); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } else if (param.matchString("paint_file", true)) { + _paint = _engine->getRenderManager()->loadImage(values, false); + } else if (param.matchString("eligible_objects", true)) { + char buf[256]; + memset(buf, 0, 256); + strncpy(buf, values.c_str(), 255); + + char *curpos = buf; + char *strend = buf + strlen(buf); + while (true) { + char *st = curpos; + + if (st >= strend) + break; + + while (*curpos != ' ' && curpos < strend) + curpos++; + + *curpos = 0; + curpos++; + + int obj = atoi(st); + + _eligibleObjects.push_back(obj); + } + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + if (_paint) { + _colorKey = _paint->format.RGBToColor(255, 0, 255); + _bkg = new Graphics::Surface; + _bkg->create(_rectangle.width(), _rectangle.height(), _paint->format); + _bkg->fillRect(Common::Rect(_rectangle.width(), _rectangle.height()), _colorKey); + + Graphics::Surface *tmp = new Graphics::Surface; + tmp->create(_rectangle.width(), _rectangle.height(), _paint->format); + _engine->getRenderManager()->blitSurfaceToSurface(*_paint, _rectangle, *tmp, 0, 0); + _paint->free(); + delete _paint; + _paint = tmp; + } +} + +PaintControl::~PaintControl() { + // Clear the state value back to 0 + //_engine->getScriptManager()->setStateValue(_key, 0); + if (_paint) { + _paint->free(); + delete _paint; + } + if (_brush) { + _brush->free(); + delete _brush; + } + if (_bkg) { + _bkg->free(); + delete _bkg; + } +} + +bool PaintControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + _mouseDown = false; + + return false; +} + +bool PaintControl::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_rectangle.contains(backgroundImageSpacePos)) { + int mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem); + + if (eligeblity(mouseItem)) { + setVenus(); + _mouseDown = true; + } + } + + return false; +} + +bool PaintControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_rectangle.contains(backgroundImageSpacePos)) { + int mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem); + + if (eligeblity(mouseItem)) { + _engine->getCursorManager()->changeCursor(_cursor); + + if (_mouseDown) { + Common::Rect bkgRect = paint(backgroundImageSpacePos); + if (!bkgRect.isEmpty()) { + Common::Rect imgRect = bkgRect; + imgRect.translate(-_rectangle.left, -_rectangle.top); + + Graphics::Surface imgUpdate = _bkg->getSubArea(imgRect); + + _engine->getRenderManager()->blitSurfaceToBkg(imgUpdate, bkgRect.left, bkgRect.top, _colorKey); + } + } + return true; + } + } + + return false; +} + +bool PaintControl::eligeblity(int itemId) { + for (Common::List<int>::iterator it = _eligibleObjects.begin(); it != _eligibleObjects.end(); it++) + if (*it == itemId) + return true; + return false; +} + +Common::Rect PaintControl::paint(const Common::Point &point) { + Common::Rect paintRect = Common::Rect(_brush->w, _brush->h); + paintRect.moveTo(point); + paintRect.clip(_rectangle); + + if (!paintRect.isEmpty()) { + Common::Rect brushRect = paintRect; + brushRect.translate(-point.x, -point.y); + + Common::Rect bkgRect = paintRect; + bkgRect.translate(-_rectangle.left, -_rectangle.top); + + for (int yy = 0; yy < brushRect.height(); yy++) { + uint16 *mask = (uint16 *)_brush->getBasePtr(brushRect.left, brushRect.top + yy); + uint16 *from = (uint16 *)_paint->getBasePtr(bkgRect.left, bkgRect.top + yy); + uint16 *to = (uint16 *)_bkg->getBasePtr(bkgRect.left, bkgRect.top + yy); + for (int xx = 0; xx < brushRect.width(); xx++) { + if (*mask != 0) + *(to + xx) = *(from + xx); + + mask++; + } + } + + } + return paintRect; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/paint_control.h b/engines/zvision/scripting/controls/paint_control.h new file mode 100644 index 0000000000..8c01f0e68a --- /dev/null +++ b/engines/zvision/scripting/controls/paint_control.h @@ -0,0 +1,92 @@ +/* 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_PAINT_CONTROL_H +#define ZVISION_PAINT_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "graphics/surface.h" + +#include "common/rect.h" +#include "common/list.h" + +namespace ZVision { + +// Only used in Zork Nemesis, handles the painting puzzle screen in Lucien's room in Irondune (ch4g) +class PaintControl : public Control { +public: + PaintControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~PaintControl(); + + /** + * @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); + + /** + * @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 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); + + bool process(uint32 deltaTimeInMillis) { + return false; + }; + +private: + /** + * The area that will trigger the event + * This is in image space coordinates, NOT screen space + */ + + uint32 _colorKey; + + Graphics::Surface *_paint; + Graphics::Surface *_bkg; + Graphics::Surface *_brush; + + Common::List<int> _eligibleObjects; + + int _cursor; + Common::Rect _rectangle; + + bool _mouseDown; + + bool eligeblity(int itemId); + Common::Rect paint(const Common::Point &point); + +}; + +} // 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..f51a28d644 --- /dev/null +++ b/engines/zvision/scripting/controls/push_toggle_control.cpp @@ -0,0 +1,147 @@ +/* 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/graphics/cursors/cursor_manager.h" + +#include "common/stream.h" + +namespace ZVision { + +PushToggleControl::PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_PUSHTGL), + _countTo(2), + _cursor(CursorIndex_Active), + _event(Common::EVENT_LBUTTONUP) { + + _hotspots.clear(); + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("*_hotspot", true)) { + uint x; + uint y; + uint width; + uint height; + + sscanf(values.c_str(), "%u,%u,%u,%u", &x, &y, &width, &height); + + _hotspots.push_back(Common::Rect(x, y, x + width + 1, y + height + 1)); + } else if (param.matchString("cursor", true)) { + _cursor = _engine->getCursorManager()->getCursorId(values); + } else if (param.matchString("animation", true)) { + // Not used + } else if (param.matchString("sound", true)) { + // Not used + } else if (param.matchString("count_to", true)) { + sscanf(values.c_str(), "%u", &_countTo); + } else if (param.matchString("mouse_event", true)) { + if (values.equalsIgnoreCase("up")) { + _event = Common::EVENT_LBUTTONUP; + } else if (values.equalsIgnoreCase("down")) { + _event = Common::EVENT_LBUTTONDOWN; + } else if (values.equalsIgnoreCase("double")) { + // Not used + } + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + if (_hotspots.size() == 0) { + warning("Push_toggle %u was parsed incorrectly", key); + } +} + +PushToggleControl::~PushToggleControl() { + _hotspots.clear(); +} + +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 (contain(backgroundImageSpacePos)) { + setVenus(); + 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 (contain(backgroundImageSpacePos)) { + setVenus(); + 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 (contain(backgroundImageSpacePos)) { + _engine->getCursorManager()->changeCursor(_cursor); + return true; + } + + return false; +} + +bool PushToggleControl::contain(const Common::Point &point) { + for (uint i = 0; i < _hotspots.size(); i++) + if (_hotspots[i].contains(point)) + 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..9444c77cb6 --- /dev/null +++ b/engines/zvision/scripting/controls/push_toggle_control.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_PUSH_TOGGLE_CONTROL_H +#define ZVISION_PUSH_TOGGLE_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/rect.h" +#include "common/events.h" +#include "common/array.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::Array<Common::Rect> _hotspots; + /** The cursor to use when hovering over _hotspot */ + int _cursor; + /** Button maximal values count */ + uint _countTo; + + Common::EventType _event; + + bool contain(const Common::Point &point); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/safe_control.cpp b/engines/zvision/scripting/controls/safe_control.cpp new file mode 100644 index 0000000000..4d2a91a1ad --- /dev/null +++ b/engines/zvision/scripting/controls/safe_control.cpp @@ -0,0 +1,180 @@ +/* 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/safe_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" + +#include "common/stream.h" +#include "common/file.h" +#include "common/tokenizer.h" +#include "common/system.h" +#include "graphics/surface.h" +#include "video/video_decoder.h" + +namespace ZVision { + +SafeControl::SafeControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_SAFE) { + _statesCount = 0; + _curState = 0; + _animation = NULL; + _innerRaduis = 0; + _innerRadiusSqr = 0; + _outerRadius = 0; + _outerRadiusSqr = 0; + _zeroPointer = 0; + _startPointer = 0; + _targetFrame = 0; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("animation", true)) { + _animation = _engine->loadAnimation(values); + _animation->start(); + } else if (param.matchString("rectangle", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _rectangle = Common::Rect(x, y, width, height); + } else if (param.matchString("num_states", true)) { + _statesCount = atoi(values.c_str()); + } else if (param.matchString("center", true)) { + int x; + int y; + + sscanf(values.c_str(), "%d %d", &x, &y); + _center = Common::Point(x, y); + } else if (param.matchString("dial_inner_radius", true)) { + _innerRaduis = atoi(values.c_str()); + _innerRadiusSqr = _innerRaduis * _innerRaduis; + } else if (param.matchString("radius", true)) { + _outerRadius = atoi(values.c_str()); + _outerRadiusSqr = _outerRadius * _outerRadius; + } else if (param.matchString("zero_radians_offset", true)) { + _zeroPointer = atoi(values.c_str()); + } else if (param.matchString("pointer_offset", true)) { + _startPointer = atoi(values.c_str()); + _curState = _startPointer; + } else if (param.matchString("cursor", true)) { + // Not used + } else if (param.matchString("mirrored", true)) { + // Not used + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + if (_animation) + _animation->seekToFrame(_curState); +} + +SafeControl::~SafeControl() { + if (_animation) + delete _animation; + +} + +bool SafeControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_animation && _animation->getCurFrame() != _targetFrame && _animation->needsUpdate()) { + // If we're past the target frame, move back one + if (_animation->getCurFrame() > _targetFrame) + _animation->seekToFrame(_animation->getCurFrame() - 1); + + const Graphics::Surface *frameData = _animation->decodeNextFrame(); + if (_animation->getCurFrame() == _targetFrame) + _engine->getScriptManager()->setStateValue(_key, _curState); + if (frameData) + _engine->getRenderManager()->blitSurfaceToBkg(*frameData, _rectangle.left, _rectangle.top); + } + + return false; +} + +bool SafeControl::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_rectangle.contains(backgroundImageSpacePos)) { + int32 mR = backgroundImageSpacePos.sqrDist(_center); + if (mR <= _outerRadiusSqr && mR >= _innerRadiusSqr) { + _engine->getCursorManager()->changeCursor(CursorIndex_Active); + return true; + } + } + return false; +} + +bool SafeControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_rectangle.contains(backgroundImageSpacePos)) { + int32 mR = backgroundImageSpacePos.sqrDist(_center); + if (mR <= _outerRadiusSqr && mR >= _innerRadiusSqr) { + setVenus(); + + Common::Point tmp = backgroundImageSpacePos - _center; + + float dd = atan2((float)tmp.x, (float)tmp.y) * 57.29578; + + int16 dp_state = 360 / _statesCount; + + int16 m_state = (_statesCount - ((((int16)dd + 540) % 360) / dp_state)) % _statesCount; + + int16 tmp2 = (m_state + _curState - _zeroPointer + _statesCount - 1) % _statesCount; + + if (_animation) + _animation->seekToFrame((_curState + _statesCount - _startPointer) % _statesCount); + + _curState = (_statesCount * 2 + tmp2) % _statesCount; + + _targetFrame = (_curState + _statesCount - _startPointer) % _statesCount; + return true; + } + } + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/safe_control.h b/engines/zvision/scripting/controls/safe_control.h new file mode 100644 index 0000000000..3e8c17635c --- /dev/null +++ b/engines/zvision/scripting/controls/safe_control.h @@ -0,0 +1,65 @@ +/* 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_SAFE_CONTROL_H +#define ZVISION_SAFE_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/list.h" +#include "common/rect.h" + +namespace Video { + class VideoDecoder; +} + +namespace ZVision { + +// Only used in Zork Nemesis, handles the safe in the Asylum (ac4g) +class SafeControl : public Control { +public: + SafeControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~SafeControl(); + +private: + int16 _statesCount; + int16 _curState; + Video::VideoDecoder *_animation; + Common::Point _center; + Common::Rect _rectangle; + int16 _innerRaduis; + int32 _innerRadiusSqr; + int16 _outerRadius; + int32 _outerRadiusSqr; + int16 _zeroPointer; + int16 _startPointer; + int16 _targetFrame; + +public: + bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos); + bool process(uint32 deltaTimeInMillis); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/save_control.cpp b/engines/zvision/scripting/controls/save_control.cpp new file mode 100644 index 0000000000..2ac77c4776 --- /dev/null +++ b/engines/zvision/scripting/controls/save_control.cpp @@ -0,0 +1,123 @@ +/* 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/input_control.h" +#include "zvision/scripting/controls/save_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/text/string_manager.h" + +#include "zvision/file/save_manager.h" +#include "zvision/graphics/render_manager.h" + +#include "common/str.h" +#include "common/stream.h" + +namespace ZVision { + +SaveControl::SaveControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_SAVE), + _saveControl(false) { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("savebox", true)) { + int saveId; + int inputId; + + sscanf(values.c_str(), "%d %d", &saveId, &inputId); + saveElement elmnt; + elmnt.inputKey = inputId; + elmnt.saveId = saveId; + elmnt.exist = false; + _inputs.push_back(elmnt); + } else if (param.matchString("control_type", true)) { + if (values.contains("save")) + _saveControl = true; + else + _saveControl = false; + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + for (saveElmntList::iterator iter = _inputs.begin(); iter != _inputs.end(); ++iter) { + Control *ctrl = _engine->getScriptManager()->getControl(iter->inputKey); + if (ctrl && ctrl->getType() == Control::CONTROL_INPUT) { + InputControl *inp = (InputControl *)ctrl; + inp->setReadOnly(!_saveControl); + Common::SeekableReadStream *save = _engine->getSaveManager()->getSlotFile(iter->saveId); + if (save) { + SaveGameHeader header; + if (_engine->getSaveManager()->readSaveGameHeader(save, header)) { + inp->setText(header.saveName); + iter->exist = true; + } + delete save; + } + } + } +} + +bool SaveControl::process(uint32 deltaTimeInMillis) { + for (saveElmntList::iterator iter = _inputs.begin(); iter != _inputs.end(); ++iter) { + Control *ctrl = _engine->getScriptManager()->getControl(iter->inputKey); + if (ctrl && ctrl->getType() == Control::CONTROL_INPUT) { + InputControl *inp = (InputControl *)ctrl; + if (inp->enterPress()) { + if (_saveControl) { + if (inp->getText().size() > 0) { + bool toSave = true; + if (iter->exist) + if (!_engine->getRenderManager()->askQuestion(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVEEXIST))) + toSave = false; + + if (toSave) { + _engine->getSaveManager()->saveGame(iter->saveId, inp->getText(), true); + _engine->getRenderManager()->delayedMessage(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVED), 2000); + _engine->getScriptManager()->changeLocation(_engine->getScriptManager()->getLastMenuLocation()); + } + } else { + _engine->getRenderManager()->timedMessage(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVEEMPTY), 2000); + } + } else { + _engine->getSaveManager()->loadGame(iter->saveId); + return true; + } + break; + } + } + } + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/save_control.h b/engines/zvision/scripting/controls/save_control.h new file mode 100644 index 0000000000..cdc50eb54d --- /dev/null +++ b/engines/zvision/scripting/controls/save_control.h @@ -0,0 +1,55 @@ +/* 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_SAVE_CONTROL_H +#define ZVISION_SAVE_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "common/list.h" + +namespace ZVision { + +class SaveControl : public Control { +public: + SaveControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + +private: + struct saveElement { + int saveId; + int inputKey; + bool exist; + }; + typedef Common::List<saveElement> saveElmntList; + saveElmntList _inputs; + + bool _saveControl; + +public: + + bool process(uint32 deltaTimeInMillis); + +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/slot_control.cpp b/engines/zvision/scripting/controls/slot_control.cpp new file mode 100644 index 0000000000..42b54a9ab5 --- /dev/null +++ b/engines/zvision/scripting/controls/slot_control.cpp @@ -0,0 +1,219 @@ +/* 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/slot_control.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" +#include "zvision/graphics/render_manager.h" + +#include "common/stream.h" + +namespace ZVision { + +SlotControl::SlotControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_SLOT), + _cursor(CursorIndex_Active), + _distanceId('0') { + + _renderedItem = 0; + _bkg = NULL; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("hotspot", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _hotspot = Common::Rect(x, y, width, height); + } else if (param.matchString("rectangle", true)) { + int x; + int y; + int width; + int height; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height); + + _rectangle = Common::Rect(x, y, width, height); + } else if (param.matchString("cursor", true)) { + _cursor = _engine->getCursorManager()->getCursorId(values); + } else if (param.matchString("distance_id", true)) { + sscanf(values.c_str(), "%c", &_distanceId); + } else if (param.matchString("venus_id", true)) { + _venusId = atoi(values.c_str()); + } else if (param.matchString("eligible_objects", true)) { + char buf[256]; + memset(buf, 0, 256); + strncpy(buf, values.c_str(), 255); + + char *curpos = buf; + char *strend = buf + strlen(buf); + while (true) { + char *st = curpos; + + if (st >= strend) + break; + + while (*curpos != ' ' && curpos < strend) + curpos++; + + *curpos = 0; + curpos++; + + int obj = atoi(st); + + _eligibleObjects.push_back(obj); + } + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + if (_hotspot.isEmpty() || _rectangle.isEmpty()) { + warning("Slot %u was parsed incorrectly", key); + } +} + +SlotControl::~SlotControl() { + // Clear the state value back to 0 + //_engine->getScriptManager()->setStateValue(_key, 0); + + if (_bkg) + delete _bkg; +} + +bool SlotControl::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_hotspot.contains(backgroundImageSpacePos)) { + setVenus(); + + int item = _engine->getScriptManager()->getStateValue(_key); + int mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem); + if (item != 0) { + if (mouseItem != 0) { + if (eligeblity(mouseItem)) { + _engine->getScriptManager()->inventoryDrop(mouseItem); + _engine->getScriptManager()->inventoryAdd(item); + _engine->getScriptManager()->setStateValue(_key, mouseItem); + } + } else { + _engine->getScriptManager()->inventoryAdd(item); + _engine->getScriptManager()->setStateValue(_key, 0); + } + } else if (mouseItem == 0) { + if (eligeblity(0)) { + _engine->getScriptManager()->inventoryDrop(0); + _engine->getScriptManager()->setStateValue(_key, 0); + } + } else if (eligeblity(mouseItem)) { + _engine->getScriptManager()->setStateValue(_key, mouseItem); + _engine->getScriptManager()->inventoryDrop(mouseItem); + } + } + return false; +} + +bool SlotControl::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(_cursor); + return true; + } + + return false; +} + +bool SlotControl::process(uint32 deltaTimeInMillis) { + if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED) + return false; + + if (_engine->canRender()) { + int curItem = _engine->getScriptManager()->getStateValue(_key); + if (curItem != _renderedItem) { + if (_renderedItem != 0 && curItem == 0) { + _engine->getRenderManager()->blitSurfaceToBkg(*_bkg, _rectangle.left, _rectangle.top); + _renderedItem = curItem; + } else { + if (_renderedItem == 0) { + if (_bkg) + delete _bkg; + + _bkg = _engine->getRenderManager()->getBkgRect(_rectangle); + } else { + _engine->getRenderManager()->blitSurfaceToBkg(*_bkg, _rectangle.left, _rectangle.top); + } + + char buf[16]; + if (_engine->getGameId() == GID_NEMESIS) + sprintf(buf, "%d%cobj.tga", curItem, _distanceId); + else + sprintf(buf, "g0z%cu%2.2x1.tga", _distanceId, curItem); + + Graphics::Surface *srf = _engine->getRenderManager()->loadImage(buf); + + int16 drawx = _rectangle.left; + int16 drawy = _rectangle.top; + + if (_rectangle.width() > srf->w) + drawx = _rectangle.left + (_rectangle.width() - srf->w) / 2; + + if (_rectangle.height() > srf->h) + drawy = _rectangle.top + (_rectangle.height() - srf->h) / 2; + + _engine->getRenderManager()->blitSurfaceToBkg(*srf, drawx, drawy, 0); + + delete srf; + + _renderedItem = curItem; + } + } + } + return false; +} + +bool SlotControl::eligeblity(int itemId) { + for (Common::List<int>::iterator it = _eligibleObjects.begin(); it != _eligibleObjects.end(); it++) + if (*it == itemId) + return true; + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/slot_control.h b/engines/zvision/scripting/controls/slot_control.h new file mode 100644 index 0000000000..e776d99311 --- /dev/null +++ b/engines/zvision/scripting/controls/slot_control.h @@ -0,0 +1,84 @@ +/* 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_SLOT_CONTROL_H +#define ZVISION_SLOT_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "graphics/surface.h" + +#include "common/rect.h" +#include "common/list.h" + +namespace ZVision { + +class SlotControl : public Control { +public: + SlotControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~SlotControl(); + + /** + * 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); + + bool process(uint32 deltaTimeInMillis); + +private: + /** + * The area that will trigger the event + * This is in image space coordinates, NOT screen space + */ + Common::Rect _rectangle; + Common::Rect _hotspot; + + int _cursor; + char _distanceId; + + int _renderedItem; + + Common::List<int> _eligibleObjects; + + bool eligeblity(int itemId); + + Graphics::Surface *_bkg; + + /** The cursor to use when hovering over _hotspot */ + Common::String _hoverCursor; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/controls/titler_control.cpp b/engines/zvision/scripting/controls/titler_control.cpp new file mode 100644 index 0000000000..683d6660af --- /dev/null +++ b/engines/zvision/scripting/controls/titler_control.cpp @@ -0,0 +1,108 @@ +/* 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/titler_control.h" + +#include "zvision/zvision.h" +#include "zvision/text/text.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" + +#include "common/stream.h" + +namespace ZVision { + +TitlerControl::TitlerControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream) + : Control(engine, key, CONTROL_TITLER) { + + _surface = NULL; + _curString = -1; + + // Loop until we find the closing brace + Common::String line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + Common::String param; + Common::String values; + getParams(line, param, values); + + while (!stream.eos() && !line.contains('}')) { + if (param.matchString("string_resource_file", true)) { + readStringsFile(values); + } else if (param.matchString("rectangle", true)) { + int x; + int y; + int x2; + int y2; + + sscanf(values.c_str(), "%d %d %d %d", &x, &y, &x2, &y2); + + _rectangle = Common::Rect(x, y, x2, y2); + } + + line = stream.readLine(); + _engine->getScriptManager()->trimCommentsAndWhiteSpace(&line); + getParams(line, param, values); + } + + if (!_rectangle.isEmpty()) { + _surface = new Graphics::Surface; + _surface->create(_rectangle.width(), _rectangle.height(), _engine->_resourcePixelFormat); + _surface->fillRect(Common::Rect(_surface->w, _surface->h), 0); + } +} + +TitlerControl::~TitlerControl() { + if (_surface) { + _surface->free(); + delete _surface; + } +} + +void TitlerControl::setString(int strLine) { + if (strLine != _curString && strLine >= 0 && strLine < (int)_strings.size()) { + _surface->fillRect(Common::Rect(_surface->w, _surface->h), 0); + _engine->getTextRenderer()->drawTextWithWordWrapping(_strings[strLine], *_surface); + _engine->getRenderManager()->blitSurfaceToBkg(*_surface, _rectangle.left, _rectangle.top); + _curString = strLine; + } +} + +void TitlerControl::readStringsFile(const Common::String &fileName) { + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + warning("String_resource_file %s could could be opened", fileName.c_str()); + return; + } + + _strings.clear(); + + while (!file.eos()) { + + Common::String line = readWideLine(file); + _strings.push_back(line); + } + file.close(); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/controls/titler_control.h b/engines/zvision/scripting/controls/titler_control.h new file mode 100644 index 0000000000..dd96e4a846 --- /dev/null +++ b/engines/zvision/scripting/controls/titler_control.h @@ -0,0 +1,55 @@ +/* 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_TITLER_CONTROL_H +#define ZVISION_TITLER_CONTROL_H + +#include "zvision/scripting/control.h" + +#include "graphics/surface.h" + +#include "common/rect.h" +#include "common/array.h" + +namespace ZVision { + +// Only used in Zork Nemesis, handles the death screen with the Restore/Exit buttons +class TitlerControl : public Control { +public: + TitlerControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream); + ~TitlerControl(); + + void setString(int strLine); + +private: + + Common::Array< Common::String > _strings; + Common::Rect _rectangle; + int16 _curString; + Graphics::Surface *_surface; + + void readStringsFile(const Common::String &fileName); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/animation_effect.cpp b/engines/zvision/scripting/effects/animation_effect.cpp new file mode 100644 index 0000000000..511a0db353 --- /dev/null +++ b/engines/zvision/scripting/effects/animation_effect.cpp @@ -0,0 +1,217 @@ +/* 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/effects/animation_effect.h" + +#include "zvision/zvision.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/scripting/script_manager.h" + +#include "graphics/surface.h" +#include "video/video_decoder.h" + +namespace ZVision { + +AnimationEffect::AnimationEffect(ZVision *engine, uint32 controlKey, const Common::String &fileName, int32 mask, int32 frate, bool disposeAfterUse) + : ScriptingEffect(engine, controlKey, SCRIPTING_EFFECT_ANIM), + _disposeAfterUse(disposeAfterUse), + _mask(mask), + _animation(NULL) { + + _animation = engine->loadAnimation(fileName); + + if (frate > 0) { + _frmDelayOverride = (int32)(1000.0 / frate); + + // WORKAROUND: We do not allow the engine to delay more than 66 msec + // per frame (15fps max) + if (_frmDelayOverride > 66) + _frmDelayOverride = 66; + } else { + _frmDelayOverride = 0; + } +} + +AnimationEffect::~AnimationEffect() { + if (_animation) + delete _animation; + + _engine->getScriptManager()->setStateValue(_key, 2); + + PlayNodes::iterator it = _playList.begin(); + if (it != _playList.end()) { + _engine->getScriptManager()->setStateValue((*it).slot, 2); + + if ((*it)._scaled) { + (*it)._scaled->free(); + delete(*it)._scaled; + } + } + + _playList.clear(); +} + +bool AnimationEffect::process(uint32 deltaTimeInMillis) { + ScriptManager *scriptManager = _engine->getScriptManager(); + RenderManager *renderManager = _engine->getRenderManager(); + RenderTable::RenderState renderState = renderManager->getRenderTable()->getRenderState(); + bool isPanorama = (renderState == RenderTable::PANORAMA); + int16 velocity = _engine->getMouseVelocity() + _engine->getKeyboardVelocity(); + + // Do not update animation nodes in panoramic mode while turning, if the user + // has set this option + if (scriptManager->getStateValue(StateKey_NoTurnAnim) == 1 && isPanorama && velocity) + return false; + + PlayNodes::iterator it = _playList.begin(); + if (it != _playList.end()) { + playnode *nod = &(*it); + + if (nod->_curFrame == -1) { + // The node is just beginning playback + nod->_curFrame = nod->start; + + _animation->start(); + _animation->seekToFrame(nod->start); + _animation->setEndFrame(nod->stop); + + nod->_delay = deltaTimeInMillis; // Force the frame to draw + if (nod->slot) + scriptManager->setStateValue(nod->slot, 1); + } else if (_animation->endOfVideo()) { + // The node has reached the end; check if we need to loop + nod->loop--; + + if (nod->loop == 0) { + if (nod->slot >= 0) + scriptManager->setStateValue(nod->slot, 2); + if (nod->_scaled) { + nod->_scaled->free(); + delete nod->_scaled; + } + _playList.erase(it); + return _disposeAfterUse; + } + + nod->_curFrame = nod->start; + _animation->seekToFrame(nod->start); + } + + // Check if we need to draw a frame + bool needsUpdate = false; + if (_frmDelayOverride == 0) { + // If not overridden, use the VideoDecoder's check + needsUpdate = _animation->needsUpdate(); + } else { + // Otherwise, implement our own timing + nod->_delay -= deltaTimeInMillis; + + if (nod->_delay <= 0) { + nod->_delay += _frmDelayOverride; + needsUpdate = true; + } + } + + if (needsUpdate) { + const Graphics::Surface *frame = _animation->decodeNextFrame(); + + if (frame) { + uint32 dstw; + uint32 dsth; + if (isPanorama) { + dstw = nod->pos.height(); + dsth = nod->pos.width(); + } else { + dstw = nod->pos.width(); + dsth = nod->pos.height(); + } + + // We only scale down the animation to fit its frame, not up, otherwise we + // end up with distorted animations - e.g. the armor visor in location cz1e + // in Nemesis (one of the armors inside Irondune), or the planet in location + // aa10 in Nemesis (Juperon, outside the asylum). We do allow scaling up only + // when a simple 2x filter is requested (e.g. the alchemists and cup sequence + // in Nemesis) + if (frame->w > dstw || frame->h > dsth || (frame->w == dstw / 2 && frame->h == dsth / 2)) { + if (nod->_scaled) + if (nod->_scaled->w != dstw || nod->_scaled->h != dsth) { + nod->_scaled->free(); + delete nod->_scaled; + nod->_scaled = NULL; + } + + if (!nod->_scaled) { + nod->_scaled = new Graphics::Surface; + nod->_scaled->create(dstw, dsth, frame->format); + } + + renderManager->scaleBuffer(frame->getPixels(), nod->_scaled->getPixels(), frame->w, frame->h, frame->format.bytesPerPixel, dstw, dsth); + frame = nod->_scaled; + } + + if (isPanorama) { + Graphics::Surface *transposed = RenderManager::tranposeSurface(frame); + renderManager->blitSurfaceToBkg(*transposed, nod->pos.left, nod->pos.top, _mask); + transposed->free(); + delete transposed; + } else { + renderManager->blitSurfaceToBkg(*frame, nod->pos.left, nod->pos.top, _mask); + } + } + } + } + + return false; +} + +void AnimationEffect::addPlayNode(int32 slot, int x, int y, int x2, int y2, int startFrame, int endFrame, int loops) { + playnode nod; + nod.loop = loops; + nod.pos = Common::Rect(x, y, x2 + 1, y2 + 1); + nod.start = startFrame; + nod.stop = CLIP<int>(endFrame, 0, _animation->getFrameCount() - 1); + nod.slot = slot; + nod._curFrame = -1; + nod._delay = 0; + nod._scaled = NULL; + _playList.push_back(nod); +} + +bool AnimationEffect::stop() { + PlayNodes::iterator it = _playList.begin(); + if (it != _playList.end()) { + _engine->getScriptManager()->setStateValue((*it).slot, 2); + if ((*it)._scaled) { + (*it)._scaled->free(); + delete(*it)._scaled; + } + } + + _playList.clear(); + + // We don't need to delete, it's may be reused + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/animation_effect.h b/engines/zvision/scripting/effects/animation_effect.h new file mode 100644 index 0000000000..fd6e24ab8b --- /dev/null +++ b/engines/zvision/scripting/effects/animation_effect.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_ANIMATION_NODE_H +#define ZVISION_ANIMATION_NODE_H + +#include "zvision/scripting/scripting_effect.h" +#include "common/rect.h" +#include "common/list.h" + +namespace Graphics { +struct Surface; +} + +namespace Video { + class VideoDecoder; +} + +namespace ZVision { + +class ZVision; + +class AnimationEffect : public ScriptingEffect { +public: + AnimationEffect(ZVision *engine, uint32 controlKey, const Common::String &fileName, int32 mask, int32 frate, bool disposeAfterUse = true); + ~AnimationEffect(); + + struct playnode { + Common::Rect pos; + int32 slot; + int32 start; + int32 stop; + int32 loop; + int32 _curFrame; + int32 _delay; + Graphics::Surface *_scaled; + }; + +private: + typedef Common::List<playnode> PlayNodes; + + PlayNodes _playList; + + int32 _mask; + bool _disposeAfterUse; + + Video::VideoDecoder *_animation; + int32 _frmDelayOverride; + +public: + bool process(uint32 deltaTimeInMillis); + + void addPlayNode(int32 slot, int x, int y, int x2, int y2, int startFrame, int endFrame, int loops = 1); + + bool stop(); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/distort_effect.cpp b/engines/zvision/scripting/effects/distort_effect.cpp new file mode 100644 index 0000000000..113b5d048d --- /dev/null +++ b/engines/zvision/scripting/effects/distort_effect.cpp @@ -0,0 +1,104 @@ +/* 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/effects/distort_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/render_table.h" + +#include "common/stream.h" + +namespace ZVision { + +DistortNode::DistortNode(ZVision *engine, uint32 key, int16 speed, float startAngle, float endAngle, float startLineScale, float endLineScale) + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_DISTORT) { + + _angle = _engine->getRenderManager()->getRenderTable()->getAngle(); + _linScale = _engine->getRenderManager()->getRenderTable()->getLinscale(); + + _speed = speed; + _incr = true; + _startAngle = startAngle; + _endAngle = endAngle; + _startLineScale = startLineScale; + _endLineScale = endLineScale; + + _curFrame = 1.0; + + _diffAngle = endAngle - startAngle; + _diffLinScale = endLineScale - startLineScale; + + _frmSpeed = (float)speed / 15.0; + _frames = (int)ceil((5.0 - _frmSpeed * 2.0) / _frmSpeed); + if (_frames <= 0) + _frames = 1; + + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 1); +} + +DistortNode::~DistortNode() { + setParams(_angle, _linScale); +} + +bool DistortNode::process(uint32 deltaTimeInMillis) { + float updTime = deltaTimeInMillis / (1000.0 / 60.0); + + if (_incr) + _curFrame += updTime; + else + _curFrame -= updTime; + + if (_curFrame < 1.0) { + _curFrame = 1.0; + _incr = true; + } else if (_curFrame > _frames) { + _curFrame = _frames; + _incr = false; + } + + float diff = (1.0 / (5.0 - (_curFrame * _frmSpeed))) / (5.0 - _frmSpeed); + setParams(_startAngle + diff * _diffAngle, _startLineScale + diff * _diffLinScale); + + return false; +} + +void DistortNode::setParams(float angl, float linScale) { + RenderTable *table = _engine->getRenderManager()->getRenderTable(); + if (table->getRenderState() == RenderTable::PANORAMA) { + table->setPanoramaFoV(angl); + table->setPanoramaScale(linScale); + table->generateRenderTable(); + _engine->getRenderManager()->markDirty(); + } else if (table->getRenderState() == RenderTable::TILT) { + table->setTiltFoV(angl); + table->setTiltScale(linScale); + table->generateRenderTable(); + _engine->getRenderManager()->markDirty(); + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/distort_effect.h b/engines/zvision/scripting/effects/distort_effect.h new file mode 100644 index 0000000000..c64f10e6ff --- /dev/null +++ b/engines/zvision/scripting/effects/distort_effect.h @@ -0,0 +1,63 @@ +/* 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_DISTORT_NODE_H +#define ZVISION_DISTORT_NODE_H + +#include "zvision/scripting/scripting_effect.h" + +namespace ZVision { + +class ZVision; + +class DistortNode : public ScriptingEffect { +public: + DistortNode(ZVision *engine, uint32 key, int16 speed, float startAngle, float endAngle, float startLineScale, float endLineScale); + ~DistortNode(); + + bool process(uint32 deltaTimeInMillis); + +private: + int16 _speed; + float _startAngle; + float _endAngle; + float _startLineScale; + float _endLineScale; + + float _frmSpeed; + float _diffAngle; + float _diffLinScale; + bool _incr; + int16 _frames; + + float _curFrame; + + float _angle; + float _linScale; + +private: + void setParams(float angl, float linScale); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/music_effect.cpp b/engines/zvision/scripting/effects/music_effect.cpp new file mode 100644 index 0000000000..e3fdc96dba --- /dev/null +++ b/engines/zvision/scripting/effects/music_effect.cpp @@ -0,0 +1,296 @@ +/* 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/effects/music_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/sound/midi.h" +#include "zvision/sound/zork_raw.h" + +#include "common/stream.h" +#include "common/file.h" +#include "audio/decoders/wave.h" + +namespace ZVision { + +static const uint8 dbMapLinear[256] = +{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, +2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, +4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, +8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 13, 13, 14, +14, 15, 15, 16, 16, 17, 18, 18, 19, 20, 21, 21, 22, 23, 24, 25, +26, 27, 28, 29, 30, 31, 32, 33, 34, 36, 37, 38, 40, 41, 43, 45, +46, 48, 50, 52, 53, 55, 57, 60, 62, 64, 67, 69, 72, 74, 77, 80, +83, 86, 89, 92, 96, 99, 103, 107, 111, 115, 119, 123, 128, 133, 137, 143, +148, 153, 159, 165, 171, 177, 184, 191, 198, 205, 212, 220, 228, 237, 245, 255}; + +MusicNode::MusicNode(ZVision *engine, uint32 key, Common::String &filename, bool loop, uint8 volume) + : MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) { + _loop = loop; + _volume = volume; + _deltaVolume = 0; + _balance = 0; + _crossfade = false; + _crossfadeTarget = 0; + _crossfadeTime = 0; + _sub = NULL; + _stereo = false; + _loaded = false; + + Audio::RewindableAudioStream *audioStream = NULL; + + if (filename.contains(".wav")) { + Common::File *file = new Common::File(); + if (_engine->getSearchManager()->openFile(*file, filename)) { + audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES); + } + } else { + audioStream = makeRawZorkStream(filename, _engine); + } + + if (audioStream) { + _stereo = audioStream->isStereo(); + + if (_loop) { + Audio::LoopingAudioStream *loopingAudioStream = new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::YES); + _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, loopingAudioStream, -1, dbMapLinear[_volume]); + } else { + _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream, -1, dbMapLinear[_volume]); + } + + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 1); + + // Change filename.raw into filename.sub + Common::String subname = filename; + subname.setChar('s', subname.size() - 3); + subname.setChar('u', subname.size() - 2); + subname.setChar('b', subname.size() - 1); + + if (_engine->getSearchManager()->hasFile(subname)) + _sub = new Subtitle(_engine, subname); + + _loaded = true; + } +} + +MusicNode::~MusicNode() { + if (_loaded) + _engine->_mixer->stopHandle(_handle); + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 2); + if (_sub) + delete _sub; + debug(1, "MusicNode: %d destroyed", _key); +} + +void MusicNode::setDeltaVolume(uint8 volume) { + _deltaVolume = volume; + setVolume(_volume); +} + +void MusicNode::setBalance(int8 balance) { + _balance = balance; + _engine->_mixer->setChannelBalance(_handle, _balance); +} + +void MusicNode::setFade(int32 time, uint8 target) { + _crossfadeTarget = target; + _crossfadeTime = time; + _crossfade = true; +} + +bool MusicNode::process(uint32 deltaTimeInMillis) { + if (!_loaded || ! _engine->_mixer->isSoundHandleActive(_handle)) + return stop(); + else { + uint8 _newvol = _volume; + + if (_crossfade) { + if (_crossfadeTime > 0) { + if ((int32)deltaTimeInMillis > _crossfadeTime) + deltaTimeInMillis = _crossfadeTime; + _newvol += (int)(floor(((float)(_crossfadeTarget - _newvol) / (float)_crossfadeTime)) * (float)deltaTimeInMillis); + _crossfadeTime -= deltaTimeInMillis; + } else { + _crossfade = false; + _newvol = _crossfadeTarget; + } + } + + if (_volume != _newvol) + setVolume(_newvol); + + if (_sub && _engine->getScriptManager()->getStateValue(StateKey_Subtitles) == 1) + _sub->process(_engine->_mixer->getSoundElapsedTime(_handle) / 100); + } + return false; +} + +void MusicNode::setVolume(uint8 newVolume) { + if (!_loaded) + return; + + _volume = newVolume; + + if (_deltaVolume >= _volume) + _engine->_mixer->setChannelVolume(_handle, 0); + else + _engine->_mixer->setChannelVolume(_handle, dbMapLinear[_volume - _deltaVolume]); +} + +uint8 MusicNode::getVolume() { + return _volume; +} + +PanTrackNode::PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos) + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_PANTRACK) { + _slot = slot; + _position = pos; + + // Try to set pan value for music node immediately + process(0); +} + +PanTrackNode::~PanTrackNode() { +} + +bool PanTrackNode::process(uint32 deltaTimeInMillis) { + ScriptManager * scriptManager = _engine->getScriptManager(); + ScriptingEffect *fx = scriptManager->getSideFX(_slot); + if (fx && fx->getType() == SCRIPTING_EFFECT_AUDIO) { + MusicNodeBASE *mus = (MusicNodeBASE *)fx; + + int curPos = scriptManager->getStateValue(StateKey_ViewPos); + int16 _width = _engine->getRenderManager()->getBkgSize().x; + int16 _halfWidth = _width / 2; + int16 _quarterWidth = _width / 4; + + int tmp = 0; + if (curPos <= _position) + tmp = _position - curPos; + else + tmp = _position - curPos + _width; + + int balance = 0; + + if (tmp > _halfWidth) + tmp -= _width; + + if (tmp > _quarterWidth) { + balance = 1; + tmp = _halfWidth - tmp; + } else if (tmp < -_quarterWidth) { + balance = -1; + tmp = -_halfWidth - tmp; + } + + // Originally it's value -90...90 but we use -127...127 and therefore 360 replaced by 508 + mus->setBalance( (508 * tmp) / _width ); + + tmp = (360 * tmp) / _width; + + int deltaVol = balance; + + // This value sets how fast volume goes off than sound source back of you + // By this value we can hack some "bugs" have place in originall game engine like beat sound in ZGI-dc10 + int volumeCorrection = 2; + + if (_engine->getGameId() == GID_GRANDINQUISITOR) { + if (scriptManager->getCurrentLocation() == "dc10") + volumeCorrection = 5; + } + + if (deltaVol != 0) + deltaVol = (mus->getVolume() * volumeCorrection) * (90 - tmp * balance) / 90; + if (deltaVol > 255) + deltaVol = 255; + + mus->setDeltaVolume(deltaVol); + } + return false; +} + +MusicMidiNode::MusicMidiNode(ZVision *engine, uint32 key, int8 program, int8 note, int8 volume) + : MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) { + _volume = volume; + _prog = program; + _noteNumber = note; + _pan = 0; + + _chan = _engine->getMidiManager()->getFreeChannel(); + + if (_chan >= 0) { + _engine->getMidiManager()->setVolume(_chan, _volume); + _engine->getMidiManager()->setPan(_chan, _pan); + _engine->getMidiManager()->setProgram(_chan, _prog); + _engine->getMidiManager()->noteOn(_chan, _noteNumber, _volume); + } + + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 1); +} + +MusicMidiNode::~MusicMidiNode() { + if (_chan >= 0) { + _engine->getMidiManager()->noteOff(_chan); + } + if (_key != StateKey_NotSet) + _engine->getScriptManager()->setStateValue(_key, 2); +} + +void MusicMidiNode::setDeltaVolume(uint8 volume) { +} + +void MusicMidiNode::setBalance(int8 balance) { +} + +void MusicMidiNode::setFade(int32 time, uint8 target) { +} + +bool MusicMidiNode::process(uint32 deltaTimeInMillis) { + return false; +} + +void MusicMidiNode::setVolume(uint8 newVolume) { + if (_chan >= 0) { + _engine->getMidiManager()->setVolume(_chan, newVolume); + } + _volume = newVolume; +} + +uint8 MusicMidiNode::getVolume() { + return _volume; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/music_effect.h b/engines/zvision/scripting/effects/music_effect.h new file mode 100644 index 0000000000..7657be8e09 --- /dev/null +++ b/engines/zvision/scripting/effects/music_effect.h @@ -0,0 +1,137 @@ +/* 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_MUSIC_NODE_H +#define ZVISION_MUSIC_NODE_H + +#include "audio/mixer.h" +#include "zvision/scripting/scripting_effect.h" +#include "zvision/text/subtitles.h" + +namespace Common { +class String; +} + +namespace ZVision { + +class MusicNodeBASE : public ScriptingEffect { +public: + MusicNodeBASE(ZVision *engine, uint32 key, ScriptingEffectType type) : ScriptingEffect(engine, key, type) {} + ~MusicNodeBASE() {} + + /** + * 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 + */ + virtual bool process(uint32 deltaTimeInMillis) = 0; + + virtual void setVolume(uint8 volume) = 0; + virtual uint8 getVolume() = 0; + virtual void setDeltaVolume(uint8 volume) = 0; + virtual void setBalance(int8 balance) = 0; + + virtual void setFade(int32 time, uint8 target) = 0; +}; + +class MusicNode : public MusicNodeBASE { +public: + MusicNode(ZVision *engine, uint32 key, Common::String &file, bool loop, uint8 volume); + ~MusicNode(); + + /** + * 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 setVolume(uint8 volume); + uint8 getVolume(); + void setDeltaVolume(uint8 volume); + void setBalance(int8 balance); + + void setFade(int32 time, uint8 target); + +private: + uint8 _volume; + uint8 _deltaVolume; + int8 _balance; + bool _loop; + bool _crossfade; + uint8 _crossfadeTarget; + int32 _crossfadeTime; + bool _stereo; + Audio::SoundHandle _handle; + Subtitle *_sub; + bool _loaded; +}; + +// Only used by Zork: Nemesis, for the flute and piano puzzles (tj4e and ve6f, as well as vr) +class MusicMidiNode : public MusicNodeBASE { +public: + MusicMidiNode(ZVision *engine, uint32 key, int8 program, int8 note, int8 volume); + ~MusicMidiNode(); + + /** + * 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 setVolume(uint8 volume); + uint8 getVolume(); + void setDeltaVolume(uint8 volume); + void setBalance(int8 balance); + + void setFade(int32 time, uint8 target); + +private: + int8 _chan; + int8 _noteNumber; + int8 _pan; + int8 _volume; + int8 _prog; +}; + +class PanTrackNode : public ScriptingEffect { +public: + PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos); + ~PanTrackNode(); + + bool process(uint32 deltaTimeInMillis); + +private: + uint32 _slot; + int16 _position; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/region_effect.cpp b/engines/zvision/scripting/effects/region_effect.cpp new file mode 100644 index 0000000000..78061cf4de --- /dev/null +++ b/engines/zvision/scripting/effects/region_effect.cpp @@ -0,0 +1,56 @@ +/* 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/effects/region_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" + +namespace ZVision { + +RegionNode::RegionNode(ZVision *engine, uint32 key, GraphicsEffect *effect, uint32 delay) + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_REGION) { + _effect = effect; + _delay = delay; + _timeLeft = 0; +} + +RegionNode::~RegionNode() { + _engine->getRenderManager()->deleteEffect(_key); +} + +bool RegionNode::process(uint32 deltaTimeInMillis) { + _timeLeft -= deltaTimeInMillis; + + if (_timeLeft <= 0) { + _timeLeft = _delay; + if (_effect) + _effect->update(); + } + + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/region_effect.h b/engines/zvision/scripting/effects/region_effect.h new file mode 100644 index 0000000000..4fc16224ff --- /dev/null +++ b/engines/zvision/scripting/effects/region_effect.h @@ -0,0 +1,57 @@ +/* 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_REGION_NODE_H +#define ZVISION_REGION_NODE_H + +#include "graphics/surface.h" + +#include "zvision/scripting/scripting_effect.h" +#include "zvision/graphics/graphics_effect.h" + +namespace ZVision { + +class ZVision; + +class RegionNode : public ScriptingEffect { +public: + RegionNode(ZVision *engine, uint32 key, GraphicsEffect *effect, uint32 delay); + ~RegionNode(); + + /** + * 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); + +private: + int32 _timeLeft; + uint32 _delay; + GraphicsEffect *_effect; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/syncsound_effect.cpp b/engines/zvision/scripting/effects/syncsound_effect.cpp new file mode 100644 index 0000000000..70ba97deb8 --- /dev/null +++ b/engines/zvision/scripting/effects/syncsound_effect.cpp @@ -0,0 +1,85 @@ +/* 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/effects/syncsound_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/sound/zork_raw.h" + +#include "common/stream.h" +#include "common/file.h" +#include "audio/decoders/wave.h" + +namespace ZVision { + +SyncSoundNode::SyncSoundNode(ZVision *engine, uint32 key, Common::String &filename, int32 syncto) + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_AUDIO) { + _syncto = syncto; + _sub = NULL; + + Audio::RewindableAudioStream *audioStream = NULL; + + if (filename.contains(".wav")) { + Common::File *file = new Common::File(); + if (_engine->getSearchManager()->openFile(*file, filename)) { + audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES); + } + } else { + audioStream = makeRawZorkStream(filename, _engine); + } + + _engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream); + + Common::String subname = filename; + subname.setChar('s', subname.size() - 3); + subname.setChar('u', subname.size() - 2); + subname.setChar('b', subname.size() - 1); + + if (_engine->getSearchManager()->hasFile(subname)) + _sub = new Subtitle(_engine, subname); +} + +SyncSoundNode::~SyncSoundNode() { + _engine->_mixer->stopHandle(_handle); + if (_sub) + delete _sub; +} + +bool SyncSoundNode::process(uint32 deltaTimeInMillis) { + if (! _engine->_mixer->isSoundHandleActive(_handle)) + return stop(); + else { + + if (_engine->getScriptManager()->getSideFX(_syncto) == NULL) + return stop(); + + if (_sub && _engine->getScriptManager()->getStateValue(StateKey_Subtitles) == 1) + _sub->process(_engine->_mixer->getSoundElapsedTime(_handle) / 100); + } + return false; +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/syncsound_effect.h b/engines/zvision/scripting/effects/syncsound_effect.h new file mode 100644 index 0000000000..0eabff77a3 --- /dev/null +++ b/engines/zvision/scripting/effects/syncsound_effect.h @@ -0,0 +1,56 @@ +/* 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_SYNCSOUND_NODE_H +#define ZVISION_SYNCSOUND_NODE_H + +#include "audio/mixer.h" +#include "zvision/scripting/scripting_effect.h" +#include "zvision/text/subtitles.h" + +namespace Common { +class String; +} + +namespace ZVision { +class SyncSoundNode : public ScriptingEffect { +public: + SyncSoundNode(ZVision *engine, uint32 key, Common::String &file, int32 syncto); + ~SyncSoundNode(); + + /** + * 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); +private: + int32 _syncto; + Audio::SoundHandle _handle; + Subtitle *_sub; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/effects/timer_effect.cpp b/engines/zvision/scripting/effects/timer_effect.cpp new file mode 100644 index 0000000000..778f9dec6c --- /dev/null +++ b/engines/zvision/scripting/effects/timer_effect.cpp @@ -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. + * + */ + +#include "common/scummsys.h" + +#include "zvision/scripting/effects/timer_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" + +#include "common/stream.h" + +namespace ZVision { + +TimerNode::TimerNode(ZVision *engine, uint32 key, uint timeInSeconds) + : ScriptingEffect(engine, key, SCRIPTING_EFFECT_TIMER) { + _timeLeft = 0; + + 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/effects/timer_effect.h b/engines/zvision/scripting/effects/timer_effect.h new file mode 100644 index 0000000000..5e45d54d7d --- /dev/null +++ b/engines/zvision/scripting/effects/timer_effect.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/scripting/scripting_effect.h" + +namespace ZVision { + +class ZVision; + +class TimerNode : public ScriptingEffect { +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/effects/ttytext_effect.cpp b/engines/zvision/scripting/effects/ttytext_effect.cpp new file mode 100644 index 0000000000..8d340dae39 --- /dev/null +++ b/engines/zvision/scripting/effects/ttytext_effect.cpp @@ -0,0 +1,172 @@ +/* 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/effects/ttytext_effect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/text/text.h" + +#include "common/stream.h" +#include "common/file.h" + +namespace ZVision { + +ttyTextNode::ttyTextNode(ZVision *engine, uint32 key, const Common::String &file, const Common::Rect &r, int32 delay) : + ScriptingEffect(engine, key, SCRIPTING_EFFECT_TTYTXT), + _fnt(engine) { + _delay = delay; + _r = r; + _txtpos = 0; + _nexttime = 0; + _dx = 0; + _dy = 0; + + Common::File *infile = _engine->getSearchManager()->openFile(file); + if (infile) { + while (!infile->eos()) { + Common::String asciiLine = readWideLine(*infile); + if (asciiLine.empty()) { + continue; + } + _txtbuf += asciiLine; + } + + delete infile; + } + _img.create(_r.width(), _r.height(), _engine->_resourcePixelFormat); + _state._sharp = true; + _state.readAllStyles(_txtbuf); + _state.updateFontWithTextState(_fnt); + _engine->getScriptManager()->setStateValue(_key, 1); +} + +ttyTextNode::~ttyTextNode() { + _engine->getScriptManager()->setStateValue(_key, 2); + _img.free(); +} + +bool ttyTextNode::process(uint32 deltaTimeInMillis) { + _nexttime -= deltaTimeInMillis; + + if (_nexttime < 0) { + if (_txtpos < _txtbuf.size()) { + if (_txtbuf[_txtpos] == '<') { + int32 start = _txtpos; + int32 end = 0; + int16 ret = 0; + while (_txtbuf[_txtpos] != '>' && _txtpos < _txtbuf.size()) + _txtpos++; + end = _txtpos; + if (start != -1) { + if ((end - start - 1) > 0) { + ret = _state.parseStyle(_txtbuf.c_str() + start + 1, end - start - 1); + } + } + + if (ret & (TEXT_CHANGE_FONT_TYPE | TEXT_CHANGE_FONT_STYLE)) { + _state.updateFontWithTextState(_fnt); + } else if (ret & TEXT_CHANGE_NEWLINE) { + newline(); + } + + if (ret & TEXT_CHANGE_HAS_STATE_BOX) { + Common::String buf; + buf = Common::String::format("%d", _engine->getScriptManager()->getStateValue(_state._statebox)); + + for (uint8 j = 0; j < buf.size(); j++) + outchar(buf[j]); + } + + _txtpos++; + } else { + int8 charsz = getUtf8CharSize(_txtbuf[_txtpos]); + + uint16 chr = readUtf8Char(_txtbuf.c_str() + _txtpos); + + if (chr == ' ') { + uint32 i = _txtpos + charsz; + uint16 width = _fnt.getCharWidth(chr); + + while (i < _txtbuf.size() && _txtbuf[i] != ' ' && _txtbuf[i] != '<') { + + int8 chsz = getUtf8CharSize(_txtbuf[i]); + uint16 uchr = readUtf8Char(_txtbuf.c_str() + _txtpos); + + width += _fnt.getCharWidth(uchr); + + i += chsz; + } + + if (_dx + width > _r.width()) + newline(); + else + outchar(chr); + } else + outchar(chr); + + _txtpos += charsz; + } + _nexttime = _delay; + _engine->getRenderManager()->blitSurfaceToBkg(_img, _r.left, _r.top); + } else + return stop(); + } + + return false; +} + +void ttyTextNode::scroll() { + int32 scrl = 0; + while (_dy - scrl > _r.height() - _fnt.getFontHeight()) + scrl += _fnt.getFontHeight(); + int8 *pixels = (int8 *)_img.getPixels(); + for (uint16 h = scrl; h < _img.h; h++) + memcpy(pixels + _img.pitch * (h - scrl), pixels + _img.pitch * h, _img.pitch); + + _img.fillRect(Common::Rect(0, _img.h - scrl, _img.w, _img.h), 0); + _dy -= scrl; +} + +void ttyTextNode::newline() { + _dy += _fnt.getFontHeight(); + _dx = 0; +} + +void ttyTextNode::outchar(uint16 chr) { + uint32 clr = _engine->_resourcePixelFormat.RGBToColor(_state._red, _state._green, _state._blue); + + if (_dx + _fnt.getCharWidth(chr) > _r.width()) + newline(); + + if (_dy + _fnt.getFontHeight() >= _r.height()) + scroll(); + + _fnt.drawChar(&_img, chr, _dx, _dy, clr); + + _dx += _fnt.getCharWidth(chr); +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/effects/ttytext_effect.h b/engines/zvision/scripting/effects/ttytext_effect.h new file mode 100644 index 0000000000..18cbbbaee3 --- /dev/null +++ b/engines/zvision/scripting/effects/ttytext_effect.h @@ -0,0 +1,73 @@ +/* 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_TTYTEXT_NODE_H +#define ZVISION_TTYTEXT_NODE_H + +#include "common/rect.h" +#include "graphics/surface.h" + +#include "zvision/scripting/scripting_effect.h" +#include "zvision/text/text.h" +#include "zvision/text/truetype_font.h" + +namespace Common { +class String; +} + +namespace ZVision { +class ttyTextNode : public ScriptingEffect { +public: + ttyTextNode(ZVision *engine, uint32 key, const Common::String &file, const Common::Rect &r, int32 delay); + ~ttyTextNode(); + + /** + * 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); +private: + Common::Rect _r; + + TextStyleState _state; + StyledTTFont _fnt; + Common::String _txtbuf; + uint32 _txtpos; + + int32 _delay; + int32 _nexttime; + Graphics::Surface _img; + int16 _dx; + int16 _dy; +private: + + void newline(); + void scroll(); + void outchar(uint16 chr); +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/inventory.cpp b/engines/zvision/scripting/inventory.cpp new file mode 100644 index 0000000000..76d43b200b --- /dev/null +++ b/engines/zvision/scripting/inventory.cpp @@ -0,0 +1,122 @@ +/* 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/script_manager.h" + +namespace ZVision { + +int8 ScriptManager::inventoryGetCount() { + return getStateValue(StateKey_Inv_Cnt_Slot); +} + +void ScriptManager::inventorySetCount(int8 cnt) { + setStateValue(StateKey_Inv_Cnt_Slot, cnt); +} + +int16 ScriptManager::inventoryGetItem(int8 id) { + if (id < 49 && id >= 0) + return getStateValue(StateKey_Inv_1_Slot + id); + return -1; +} + +void ScriptManager::inventorySetItem(int8 id, int16 item) { + if (id < 49 && id >= 0) + setStateValue(StateKey_Inv_1_Slot + id, item); +} + +void ScriptManager::inventoryAdd(int16 item) { + int8 cnt = inventoryGetCount(); + + if (cnt < 49) { + bool notExist = true; + + if (cnt == 0) { + inventorySetItem(0, 0); + inventorySetCount(1); // we needed empty item for cycle code + cnt = 1; + } + + for (int8 cur = 0; cur < cnt; cur++) + if (inventoryGetItem(cur) == item) { + notExist = false; + break; + } + + if (notExist) { + for (int8 i = cnt; i > 0; i--) + inventorySetItem(i, inventoryGetItem(i - 1)); + + inventorySetItem(0, item); + + setStateValue(StateKey_InventoryItem, item); + + inventorySetCount(cnt + 1); + } + } +} + +void ScriptManager::inventoryDrop(int16 item) { + int8 itemCount = inventoryGetCount(); + + // if items in inventory > 0 + if (itemCount != 0) { + int8 index = 0; + + // finding needed item + while (index < itemCount) { + if (inventoryGetItem(index) == item) + break; + + index++; + } + + // if item in the inventory + if (itemCount != index) { + // shift all items left with rewrite founded item + for (int8 v = index; v < itemCount - 1 ; v++) + inventorySetItem(v, inventoryGetItem(v + 1)); + + // del last item + inventorySetItem(itemCount - 1, 0); + inventorySetCount(inventoryGetCount() - 1); + + setStateValue(StateKey_InventoryItem, inventoryGetItem(0)); + } + } +} +void ScriptManager::inventoryCycle() { + int8 itemCount = inventoryGetCount(); + int8 curItem = inventoryGetItem(0); + if (itemCount > 1) { + for (int8 i = 0; i < itemCount - 1; i++) + inventorySetItem(i, inventoryGetItem(i + 1)); + + inventorySetItem(itemCount - 1, curItem); + + setStateValue(StateKey_InventoryItem, inventoryGetItem(0)); + + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/menu.cpp b/engines/zvision/scripting/menu.cpp new file mode 100644 index 0000000000..064bd1b57d --- /dev/null +++ b/engines/zvision/scripting/menu.cpp @@ -0,0 +1,761 @@ +/* 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 "zvision/graphics/render_manager.h" +#include "zvision/scripting/menu.h" + +namespace ZVision { + +enum { + kMainMenuSave = 0, + kMainMenuLoad = 1, + kMainMenuPrefs = 2, + kMainMenuExit = 3 +}; + +enum { + kMenuItem = 0, + kMenuMagic = 1, + kMenuMain = 2 +}; + +MenuHandler::MenuHandler(ZVision *engine) { + _engine = engine; + menuBarFlag = 0xFFFF; +} + +MenuZGI::MenuZGI(ZVision *engine) : + MenuHandler(engine) { + menuMouseFocus = -1; + inMenu = false; + scrolled[0] = false; + scrolled[1] = false; + scrolled[2] = false; + scrollPos[0] = 0; + scrollPos[1] = 0; + scrollPos[2] = 0; + mouseOnItem = -1; + redraw = false; + clean = false; + + char buf[24]; + for (int i = 1; i < 4; i++) { + sprintf(buf, "gmzau%2.2x1.tga", i); + _engine->getRenderManager()->readImageToSurface(buf, menuBack[i - 1][0], false); + sprintf(buf, "gmzau%2.2x1.tga", i + 0x10); + _engine->getRenderManager()->readImageToSurface(buf, menuBack[i - 1][1], false); + } + for (int i = 0; i < 4; i++) { + sprintf(buf, "gmzmu%2.2x1.tga", i); + _engine->getRenderManager()->readImageToSurface(buf, menuBar[i][0], false); + sprintf(buf, "gmznu%2.2x1.tga", i); + _engine->getRenderManager()->readImageToSurface(buf, menuBar[i][1], false); + } + + for (int i = 0; i < 50; i++) { + items[i][0] = NULL; + items[i][1] = NULL; + itemId[i] = 0; + } + + for (int i = 0; i < 12; i++) { + magic[i][0] = NULL; + magic[i][1] = NULL; + magicId[i] = 0; + } +} + +MenuZGI::~MenuZGI() { + for (int i = 0; i < 3; i++) { + menuBack[i][0].free(); + menuBack[i][1].free(); + } + for (int i = 0; i < 4; i++) { + menuBar[i][0].free(); + menuBar[i][1].free(); + } + for (int i = 0; i < 50; i++) { + if (items[i][0]) { + items[i][0]->free(); + delete items[i][0]; + } + if (items[i][1]) { + items[i][1]->free(); + delete items[i][1]; + } + } + for (int i = 0; i < 12; i++) { + if (magic[i][0]) { + magic[i][0]->free(); + delete magic[i][0]; + } + if (magic[i][1]) { + magic[i][1]->free(); + delete magic[i][1]; + } + } +} + +void MenuZGI::onMouseUp(const Common::Point &Pos) { + if (Pos.y < 40) { + switch (menuMouseFocus) { + case kMenuItem: + if (menuBarFlag & kMenubarItems) { + int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots); + if (itemCount == 0) + itemCount = 20; + + for (int i = 0; i < itemCount; i++) { + int itemspace = (600 - 28) / itemCount; + + if (Common::Rect(scrollPos[kMenuItem] + itemspace * i, 0, + scrollPos[kMenuItem] + itemspace * i + 28, 32).contains(Pos)) { + int32 mouseItem = _engine->getScriptManager()->getStateValue(StateKey_InventoryItem); + if (mouseItem >= 0 && mouseItem < 0xE0) { + _engine->getScriptManager()->inventoryDrop(mouseItem); + _engine->getScriptManager()->inventoryAdd(_engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + i)); + _engine->getScriptManager()->setStateValue(StateKey_Inv_StartSlot + i, mouseItem); + + redraw = true; + } + } + } + } + break; + + case kMenuMagic: + if (menuBarFlag & kMenubarMagic) { + for (int i = 0; i < 12; i++) { + + uint itemnum = _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + i); + if (itemnum != 0) { + if (_engine->getScriptManager()->getStateValue(StateKey_Reversed_Spellbooc) == 1) + itemnum = 0xEE + i; + else + itemnum = 0xE0 + i; + } + if (itemnum) + if (_engine->getScriptManager()->getStateValue(StateKey_InventoryItem) == 0 || _engine->getScriptManager()->getStateValue(StateKey_InventoryItem) >= 0xE0) + if (Common::Rect(668 + 47 * i - scrollPos[kMenuMagic], 0, + 668 + 47 * i - scrollPos[kMenuMagic] + 28, 32).contains(Pos)) + _engine->getScriptManager()->setStateValue(StateKey_Active_Spell, itemnum); + } + + } + break; + + case kMenuMain: + + // Exit + if (menuBarFlag & kMenubarExit) + if (Common::Rect(320 + 135, + scrollPos[kMenuMain], + 320 + 135 + 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + _engine->ifQuit(); + } + + // Settings + if (menuBarFlag & kMenubarSettings) + if (Common::Rect(320 , + scrollPos[kMenuMain], + 320 + 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 'p', 'e', 0); + } + + // Load + if (menuBarFlag & kMenubarRestore) + if (Common::Rect(320 - 135, + scrollPos[kMenuMain], + 320, + scrollPos[kMenuMain] + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 'r', 'e', 0); + } + + // Save + if (menuBarFlag & kMenubarSave) + if (Common::Rect(320 - 135 * 2, + scrollPos[kMenuMain], + 320 - 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 's', 'e', 0); + } + break; + } + } +} + +void MenuZGI::onMouseMove(const Common::Point &Pos) { + if (Pos.y < 40) { + + if (!inMenu) + redraw = true; + inMenu = true; + switch (menuMouseFocus) { + case kMenuItem: + if (menuBarFlag & kMenubarItems) { + int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots); + if (itemCount == 0) + itemCount = 20; + else if (itemCount > 50) + itemCount = 50; + + int lastItem = mouseOnItem; + + mouseOnItem = -1; + + for (int i = 0; i < itemCount; i++) { + int itemspace = (600 - 28) / itemCount; + + if (Common::Rect(scrollPos[kMenuItem] + itemspace * i, 0, + scrollPos[kMenuItem] + itemspace * i + 28, 32).contains(Pos)) { + mouseOnItem = i; + break; + } + } + + if (lastItem != mouseOnItem) + if (_engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + mouseOnItem) || + _engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + lastItem)) + redraw = true; + } + break; + + case kMenuMagic: + if (menuBarFlag & kMenubarMagic) { + int lastItem = mouseOnItem; + mouseOnItem = -1; + for (int i = 0; i < 12; i++) { + if (Common::Rect(668 + 47 * i - scrollPos[kMenuMagic], 0, + 668 + 47 * i - scrollPos[kMenuMagic] + 28, 32).contains(Pos)) { + mouseOnItem = i; + break; + } + } + + if (lastItem != mouseOnItem) + if (_engine->getScriptManager()->getStateValue(StateKey_Spell_1 + mouseOnItem) || + _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + lastItem)) + redraw = true; + + } + break; + + case kMenuMain: { + int lastItem = mouseOnItem; + mouseOnItem = -1; + + // Exit + if (menuBarFlag & kMenubarExit) + if (Common::Rect(320 + 135, + scrollPos[kMenuMain], + 320 + 135 + 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + mouseOnItem = kMainMenuExit; + } + + // Settings + if (menuBarFlag & kMenubarSettings) + if (Common::Rect(320 , + scrollPos[kMenuMain], + 320 + 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + mouseOnItem = kMainMenuPrefs; + } + + // Load + if (menuBarFlag & kMenubarRestore) + if (Common::Rect(320 - 135, + scrollPos[kMenuMain], + 320, + scrollPos[kMenuMain] + 32).contains(Pos)) { + mouseOnItem = kMainMenuLoad; + } + + // Save + if (menuBarFlag & kMenubarSave) + if (Common::Rect(320 - 135 * 2, + scrollPos[kMenuMain], + 320 - 135, + scrollPos[kMenuMain] + 32).contains(Pos)) { + mouseOnItem = kMainMenuSave; + } + + if (lastItem != mouseOnItem) + redraw = true; + } + break; + + default: + int cur_menu = menuMouseFocus; + if (Common::Rect(64, 0, 64 + 512, 8).contains(Pos)) { // Main + menuMouseFocus = kMenuMain; + scrolled[kMenuMain] = false; + scrollPos[kMenuMain] = menuBack[kMenuMain][1].h - menuBack[kMenuMain][0].h; + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 2); + } + + if (menuBarFlag & kMenubarMagic) + if (Common::Rect(640 - 28, 0, 640, 32).contains(Pos)) { // Magic + menuMouseFocus = kMenuMagic; + scrolled[kMenuMagic] = false; + scrollPos[kMenuMagic] = 28; + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 3); + } + + if (menuBarFlag & kMenubarItems) + if (Common::Rect(0, 0, 28, 32).contains(Pos)) { // Items + menuMouseFocus = kMenuItem; + scrolled[kMenuItem] = false; + scrollPos[kMenuItem] = 28 - 600; + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 1); + } + + if (cur_menu != menuMouseFocus) + clean = true; + + break; + } + } else { + if (inMenu) + clean = true; + inMenu = false; + if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0) + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 0); + menuMouseFocus = -1; + } +} + +void MenuZGI::process(uint32 deltatime) { + if (clean) { + _engine->getRenderManager()->clearMenuSurface(); + clean = false; + } + switch (menuMouseFocus) { + case kMenuItem: + if (menuBarFlag & kMenubarItems) + if (!scrolled[kMenuItem]) { + redraw = true; + float scrl = 600.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + scrollPos[kMenuItem] += (int)scrl; + + if (scrollPos[kMenuItem] >= 0) { + scrolled[kMenuItem] = true; + scrollPos[kMenuItem] = 0; + } + } + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuItem][0], scrollPos[kMenuItem], 0); + + int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots); + if (itemCount == 0) + itemCount = 20; + else if (itemCount > 50) + itemCount = 50; + + for (int i = 0; i < itemCount; i++) { + int itemspace = (600 - 28) / itemCount; + + bool inrect = false; + + if (mouseOnItem == i) + inrect = true; + + uint curItemId = _engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + i); + + if (curItemId != 0) { + if (itemId[i] != curItemId) { + char buf[16]; + sprintf(buf, "gmzwu%2.2x1.tga", curItemId); + items[i][0] = _engine->getRenderManager()->loadImage(buf, false); + sprintf(buf, "gmzxu%2.2x1.tga", curItemId); + items[i][1] = _engine->getRenderManager()->loadImage(buf, false); + itemId[i] = curItemId; + } + + if (inrect) + _engine->getRenderManager()->blitSurfaceToMenu(*items[i][1], scrollPos[kMenuItem] + itemspace * i, 0, 0); + else + _engine->getRenderManager()->blitSurfaceToMenu(*items[i][0], scrollPos[kMenuItem] + itemspace * i, 0, 0); + + } else { + if (items[i][0]) { + items[i][0]->free(); + delete items[i][0]; + items[i][0] = NULL; + } + if (items[i][1]) { + items[i][1]->free(); + delete items[i][1]; + items[i][1] = NULL; + } + itemId[i] = 0; + } + } + + redraw = false; + } + break; + + case kMenuMagic: + if (menuBarFlag & kMenubarMagic) + if (!scrolled[kMenuMagic]) { + redraw = true; + float scrl = 600.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + scrollPos[kMenuMagic] += (int)scrl; + + if (scrollPos[kMenuMagic] >= 600) { + scrolled[kMenuMagic] = true; + scrollPos[kMenuMagic] = 600; + } + } + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMagic][0], 640 - scrollPos[kMenuMagic], 0); + + for (int i = 0; i < 12; i++) { + bool inrect = false; + + if (mouseOnItem == i) + inrect = true; + + uint curItemId = _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + i); + if (curItemId) { + if (_engine->getScriptManager()->getStateValue(StateKey_Reversed_Spellbooc) == 1) + curItemId = 0xEE + i; + else + curItemId = 0xE0 + i; + } + + if (curItemId != 0) { + if (itemId[i] != curItemId) { + char buf[16]; + sprintf(buf, "gmzwu%2.2x1.tga", curItemId); + magic[i][0] = _engine->getRenderManager()->loadImage(buf, false); + sprintf(buf, "gmzxu%2.2x1.tga", curItemId); + magic[i][1] = _engine->getRenderManager()->loadImage(buf, false); + magicId[i] = curItemId; + } + + if (inrect) + _engine->getRenderManager()->blitSurfaceToMenu(*magic[i][1], 668 + 47 * i - scrollPos[kMenuMagic], 0, 0); + else + _engine->getRenderManager()->blitSurfaceToMenu(*magic[i][0], 668 + 47 * i - scrollPos[kMenuMagic], 0, 0); + + } else { + if (magic[i][0]) { + magic[i][0]->free(); + delete magic[i][0]; + magic[i][0] = NULL; + } + if (magic[i][1]) { + magic[i][1]->free(); + delete magic[i][1]; + magic[i][1] = NULL; + } + magicId[i] = 0; + } + } + redraw = false; + } + break; + + case kMenuMain: + if (!scrolled[kMenuMain]) { + redraw = true; + float scrl = 32.0 * 2.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + scrollPos[kMenuMain] += (int)scrl; + + if (scrollPos[kMenuMain] >= 0) { + scrolled[kMenuMain] = true; + scrollPos[kMenuMain] = 0; + } + } + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMain][0], 30, scrollPos[kMenuMain]); + + if (menuBarFlag & kMenubarExit) { + if (mouseOnItem == kMainMenuExit) + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuExit][1], 320 + 135, scrollPos[kMenuMain]); + else + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuExit][0], 320 + 135, scrollPos[kMenuMain]); + } + if (menuBarFlag & kMenubarSettings) { + if (mouseOnItem == kMainMenuPrefs) + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuPrefs][1], 320, scrollPos[kMenuMain]); + else + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuPrefs][0], 320, scrollPos[kMenuMain]); + } + if (menuBarFlag & kMenubarRestore) { + if (mouseOnItem == kMainMenuLoad) + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuLoad][1], 320 - 135, scrollPos[kMenuMain]); + else + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuLoad][0], 320 - 135, scrollPos[kMenuMain]); + } + if (menuBarFlag & kMenubarSave) { + if (mouseOnItem == kMainMenuSave) + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuSave][1], 320 - 135 * 2, scrollPos[kMenuMain]); + else + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuSave][0], 320 - 135 * 2, scrollPos[kMenuMain]); + } + redraw = false; + } + break; + default: + if (redraw) { + if (inMenu) { + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMain][1], 30, 0); + + if (menuBarFlag & kMenubarItems) + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuItem][1], 0, 0); + + if (menuBarFlag & kMenubarMagic) + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMagic][1], 640 - 28, 0); + } + redraw = false; + } + break; + } +} + +MenuNemesis::MenuNemesis(ZVision *engine) : + MenuHandler(engine) { + inMenu = false; + scrolled = false; + scrollPos = 0; + mouseOnItem = -1; + redraw = false; + delay = 0; + + char buf[24]; + for (int i = 0; i < 4; i++) + for (int j = 0; j < 6; j++) { + sprintf(buf, "butfrm%d%d.tga", i + 1, j); + _engine->getRenderManager()->readImageToSurface(buf, but[i][j], false); + } + + _engine->getRenderManager()->readImageToSurface("bar.tga", menuBar, false); + + frm = 0; +} + +MenuNemesis::~MenuNemesis() { + for (int i = 0; i < 4; i++) + for (int j = 0; j < 6; j++) + but[i][j].free(); + + menuBar.free(); +} + +static const int16 buts[4][2] = { {120 , 64}, {144, 184}, {128, 328}, {120, 456} }; + +void MenuNemesis::onMouseUp(const Common::Point &Pos) { + if (Pos.y < 40) { + // Exit + if (menuBarFlag & kMenubarExit) + if (Common::Rect(buts[3][1], + scrollPos, + buts[3][0] + buts[3][1], + scrollPos + 32).contains(Pos)) { + _engine->ifQuit(); + frm = 5; + redraw = true; + } + + // Settings + if (menuBarFlag & kMenubarSettings) + if (Common::Rect(buts[2][1], + scrollPos, + buts[2][0] + buts[2][1], + scrollPos + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 'p', 'e', 0); + frm = 5; + redraw = true; + } + + // Load + if (menuBarFlag & kMenubarRestore) + if (Common::Rect(buts[1][1], + scrollPos, + buts[1][0] + buts[1][1], + scrollPos + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 'r', 'e', 0); + frm = 5; + redraw = true; + } + + // Save + if (menuBarFlag & kMenubarSave) + if (Common::Rect(buts[0][1], + scrollPos, + buts[0][0] + buts[0][1], + scrollPos + 32).contains(Pos)) { + _engine->getScriptManager()->changeLocation('g', 'j', 's', 'e', 0); + frm = 5; + redraw = true; + } + } +} + +void MenuNemesis::onMouseMove(const Common::Point &Pos) { + if (Pos.y < 40) { + + inMenu = true; + + if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 2) + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 2); + + int lastItem = mouseOnItem; + mouseOnItem = -1; + + // Exit + if (menuBarFlag & kMenubarExit) + if (Common::Rect(buts[3][1], + scrollPos, + buts[3][0] + buts[3][1], + scrollPos + 32).contains(Pos)) { + mouseOnItem = kMainMenuExit; + } + + // Settings + if (menuBarFlag & kMenubarSettings) + if (Common::Rect(buts[2][1], + scrollPos, + buts[2][0] + buts[2][1], + scrollPos + 32).contains(Pos)) { + mouseOnItem = kMainMenuPrefs; + } + + // Load + if (menuBarFlag & kMenubarRestore) + if (Common::Rect(buts[1][1], + scrollPos, + buts[1][0] + buts[1][1], + scrollPos + 32).contains(Pos)) { + mouseOnItem = kMainMenuLoad; + } + + // Save + if (menuBarFlag & kMenubarSave) + if (Common::Rect(buts[0][1], + scrollPos, + buts[0][0] + buts[0][1], + scrollPos + 32).contains(Pos)) { + mouseOnItem = kMainMenuSave; + } + + if (lastItem != mouseOnItem) { + redraw = true; + frm = 0; + delay = 200; + } + } else { + inMenu = false; + if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0) + _engine->getScriptManager()->setStateValue(StateKey_MenuState, 0); + mouseOnItem = -1; + } +} + +void MenuNemesis::process(uint32 deltatime) { + if (inMenu) { + if (!scrolled) { + float scrl = 32.0 * 2.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + scrollPos += (int)scrl; + redraw = true; + } + + if (scrollPos >= 0) { + scrolled = true; + scrollPos = 0; + } + + if (mouseOnItem != -1) { + delay -= deltatime; + if (delay <= 0 && frm < 4) { + delay = 200; + frm++; + redraw = true; + } + } + + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menuBar, 64, scrollPos); + + if (menuBarFlag & kMenubarExit) + if (mouseOnItem == kMainMenuExit) + _engine->getRenderManager()->blitSurfaceToMenu(but[3][frm], buts[3][1], scrollPos); + + if (menuBarFlag & kMenubarSettings) + if (mouseOnItem == kMainMenuPrefs) + _engine->getRenderManager()->blitSurfaceToMenu(but[2][frm], buts[2][1], scrollPos); + + if (menuBarFlag & kMenubarRestore) + if (mouseOnItem == kMainMenuLoad) + _engine->getRenderManager()->blitSurfaceToMenu(but[1][frm], buts[1][1], scrollPos); + + if (menuBarFlag & kMenubarSave) + if (mouseOnItem == kMainMenuSave) + _engine->getRenderManager()->blitSurfaceToMenu(but[0][frm], buts[0][1], scrollPos); + + redraw = false; + } + } else { + scrolled = false; + if (scrollPos > -32) { + float scrl = 32.0 * 2.0 * (deltatime / 1000.0); + + if (scrl == 0) + scrl = 1.0; + + Common::Rect cl(64, (int16)(32 + scrollPos - scrl), 64 + 512, 32 + scrollPos + 1); + _engine->getRenderManager()->clearMenuSurface(cl); + + scrollPos -= (int)scrl; + redraw = true; + } else + scrollPos = -32; + + if (redraw) { + _engine->getRenderManager()->blitSurfaceToMenu(menuBar, 64, scrollPos); + redraw = false; + } + } +} + +} // End of namespace ZVision diff --git a/engines/zvision/scripting/menu.h b/engines/zvision/scripting/menu.h new file mode 100644 index 0000000000..f6b21b9c97 --- /dev/null +++ b/engines/zvision/scripting/menu.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_MENU_H +#define ZVISION_MENU_H + +#include "graphics/surface.h" +#include "common/rect.h" + +#include "zvision/zvision.h" +#include "zvision/scripting/script_manager.h" + +namespace ZVision { + +enum menuBar { + kMenubarExit = 0x1, + kMenubarSettings = 0x2, + kMenubarRestore = 0x4, + kMenubarSave = 0x8, + kMenubarItems = 0x100, + kMenubarMagic = 0x200 +}; + +class MenuHandler { +public: + MenuHandler(ZVision *engine); + virtual ~MenuHandler() {}; + virtual void onMouseMove(const Common::Point &Pos) {}; + virtual void onMouseDown(const Common::Point &Pos) {}; + virtual void onMouseUp(const Common::Point &Pos) {}; + virtual void process(uint32 deltaTimeInMillis) {}; + + void setEnable(uint16 flags) { + menuBarFlag = flags; + } + uint16 getEnable() { + return menuBarFlag; + } +protected: + uint16 menuBarFlag; + ZVision *_engine; +}; + +class MenuZGI: public MenuHandler { +public: + MenuZGI(ZVision *engine); + ~MenuZGI(); + void onMouseMove(const Common::Point &Pos); + void onMouseUp(const Common::Point &Pos); + void process(uint32 deltaTimeInMillis); +private: + Graphics::Surface menuBack[3][2]; + Graphics::Surface menuBar[4][2]; + Graphics::Surface *items[50][2]; + uint itemId[50]; + + Graphics::Surface *magic[12][2]; + uint magicId[12]; + + int menuMouseFocus; + bool inMenu; + + int mouseOnItem; + + bool scrolled[3]; + int16 scrollPos[3]; + + bool clean; + bool redraw; + +}; + +class MenuNemesis: public MenuHandler { +public: + MenuNemesis(ZVision *engine); + ~MenuNemesis(); + void onMouseMove(const Common::Point &Pos); + void onMouseUp(const Common::Point &Pos); + void process(uint32 deltaTimeInMillis); +private: + Graphics::Surface but[4][6]; + Graphics::Surface menuBar; + + bool inMenu; + + int mouseOnItem; + + bool scrolled; + int16 scrollPos; + + bool redraw; + + int frm; + int16 delay; + +}; + +} // 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..7d64357b0a --- /dev/null +++ b/engines/zvision/scripting/puzzle.h @@ -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. + * + */ + +#ifndef ZVISION_PUZZLE_H +#define ZVISION_PUZZLE_H + +#include "zvision/scripting/actions.h" + +#include "common/list.h" +#include "common/ptr.h" + +namespace ZVision { + +struct Puzzle { + Puzzle() : key(0), addedBySetState(false) {} + + ~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..edc1b8622c --- /dev/null +++ b/engines/zvision/scripting/scr_file_handling.cpp @@ -0,0 +1,439 @@ +/* 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/scripting/script_manager.h" + +#include "zvision/scripting/puzzle.h" +#include "zvision/scripting/actions.h" +#include "zvision/scripting/controls/push_toggle_control.h" +#include "zvision/scripting/controls/lever_control.h" +#include "zvision/scripting/controls/slot_control.h" +#include "zvision/scripting/controls/save_control.h" +#include "zvision/scripting/controls/input_control.h" +#include "zvision/scripting/controls/safe_control.h" +#include "zvision/scripting/controls/hotmov_control.h" +#include "zvision/scripting/controls/fist_control.h" +#include "zvision/scripting/controls/paint_control.h" +#include "zvision/scripting/controls/titler_control.h" + +#include "common/textconsole.h" +#include "common/file.h" +#include "common/tokenizer.h" + +namespace ZVision { + +void ScriptManager::parseScrFile(const Common::String &fileName, ScriptScope &scope) { + Common::File file; + if (!_engine->getSearchManager()->openFile(file, fileName)) { + error("Script file not found: %s", fileName.c_str()); + } + + while (!file.eos()) { + Common::String line = file.readLine(); + if (file.err()) { + error("Error parsing scr file: %s", fileName.c_str()); + } + + 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.procCount = 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, puzzle->key); + } else if (line.matchString("results {", true)) { + parseResults(stream, puzzle->resultActions); + + // WORKAROUND for a script bug in Zork Nemesis, room ve5e (tuning + // fork box closeup). If the player leaves the screen while the + // box is open, puzzle 19398 shows the animation where the box + // closes, but the box state (state variable 19397) is not updated. + // We insert the missing assignment for the box state here. + // Fixes bug #6803. + if (_engine->getGameId() == GID_NEMESIS && puzzle->key == 19398) + puzzle->resultActions.push_back(new ActionAssign(_engine, 11, "19397, 0")); + } else if (line.matchString("flags {", true)) { + setStateFlag(puzzle->key, parseFlags(stream)); + } + + line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + } + + puzzle->addedBySetState = false; +} + +bool ScriptManager::parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList, uint32 key) const { + // Loop until we find the closing brace + Common::String line = stream.readLine(); + trimCommentsAndWhiteSpace(&line); + + // Skip any commented out criteria. If all the criteria are commented out, + // we might end up with an invalid criteria list (bug #6776). + while (line.empty()) { + 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>()); + + // WORKAROUND for a script bug in Zork: Nemesis, room td9e (fist puzzle) + // Since we patch the script that triggers when manipulating the left fist + // (below), we add an additional check for the left fist sound, so that it + // doesn't get killed immediately when the left fist animation starts. + // Together with the workaround below, it fixes bug #6783. + if (_engine->getGameId() == GID_NEMESIS && key == 3594) { + Puzzle::CriteriaEntry entry; + entry.key = 567; + entry.criteriaOperator = Puzzle::NOT_EQUAL_TO; + entry.argumentIsAKey = false; + entry.argument = 1; + + criteriaList.back().push_back(entry); + } + + 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)); + + // WORKAROUND for a script bug in Zork: Nemesis, room td9e (fist puzzle) + // Check for the state of animation 567 (left fist) when manipulating + // the fingers of the left fist (puzzle slots 3582, 3583). + // Together with the workaround above, it fixes bug #6783. + if (_engine->getGameId() == GID_NEMESIS && (key == 3582 || key == 3583) && entry.key == 568) + entry.key = 567; + + // 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; + + // There are supposed to be three tokens, but there is no + // guarantee that there will be a space between the second and + // the third one (bug #6774) + if (token.size() == 1) { + token = tokenizer.nextToken(); + } else { + token.deleteChar(0); + } + + // First determine if the last token is an id or a value + // Then parse it into 'argument' + 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 strSlot(chrs + startpos, chrs + pos); + slot = atoi(strSlot.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)) { + // Only used by ZGI (locations cd6e, cd6k, dg2f, dg4e, dv1j) + 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("cursor", true)) { + actionList.push_back(new ActionCursor(_engine, slot, args)); + } else if (act.matchString("debug", true)) { + // Not used. Purposely left empty + } else if (act.matchString("delay_render", true)) { + actionList.push_back(new ActionDelayRender(_engine, slot, args)); + } else if (act.matchString("disable_control", true)) { + actionList.push_back(new ActionDisableControl(_engine, slot, args)); + } else if (act.matchString("disable_venus", true)) { + // Not used. Purposely left empty + } else if (act.matchString("display_message", true)) { + actionList.push_back(new ActionDisplayMessage(_engine, slot, args)); + } else if (act.matchString("dissolve", true)) { + actionList.push_back(new ActionDissolve(_engine)); + } else if (act.matchString("distort", true)) { + // Only used by Zork: Nemesis for the "treatment" puzzle in the Sanitarium (aj30) + actionList.push_back(new ActionDistort(_engine, slot, args)); + } else if (act.matchString("enable_control", true)) { + actionList.push_back(new ActionEnableControl(_engine, slot, args)); + } else if (act.matchString("flush_mouse_events", true)) { + actionList.push_back(new ActionFlushMouseEvents(_engine, slot)); + } else if (act.matchString("inventory", true)) { + actionList.push_back(new ActionInventory(_engine, slot, args)); + } else if (act.matchString("kill", true)) { + // Only used by ZGI + actionList.push_back(new ActionKill(_engine, slot, args)); + } else if (act.matchString("menu_bar_enable", true)) { + actionList.push_back(new ActionMenuBarEnable(_engine, slot, args)); + } 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)) { + actionList.push_back(new ActionPreferences(_engine, slot, args)); + } 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)) { + // Only used by Zork: Nemesis + actionList.push_back(new ActionRegion(_engine, slot, args)); + } else if (act.matchString("restore_game", true)) { + // Only used by ZGI to load the restart game slot, r.svr. + // Used by the credits screen. + actionList.push_back(new ActionRestoreGame(_engine, slot, args)); + } else if (act.matchString("rotate_to", true)) { + actionList.push_back(new ActionRotateTo(_engine, slot, args)); + } else if (act.matchString("save_game", true)) { + // Not used. Purposely left empty + } 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)) { + // Not used. Purposely left empty + } 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")) { + // WORKAROUND for a script bug in ZGI: There is an invalid hotspot + // at scene em1h (bottom of tower), which points to a missing + // script em1n. This is a hotspot at the right of the screen. + // In the original, this hotspot doesn't lead anywhere anyway, + // so instead of moving to a missing scene, we just remove the + // hotspot altogether. The alternative would be to just process + // and ignore invalid scenes, but I don't think it's worth the + // effort. Fixes bug #6780. + if (_engine->getGameId() == GID_GRANDINQUISITOR && key == 5653) + return NULL; + 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")) { + // Only used in Zork Nemesis, handles tilt controls (ZGI doesn't have a tilt view) + Control::parseTiltControl(_engine, stream); + return NULL; + } else if (controlType.equalsIgnoreCase("slot")) { + return new SlotControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("input")) { + return new InputControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("save")) { + return new SaveControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("lever")) { + // Only used in Zork Nemesis, handles draggable levers (te2e, tm7e, tp2e, tt2e, tz2e) + return new LeverControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("safe")) { + // Only used in Zork Nemesis, handles the safe in the Asylum (ac4g) + return new SafeControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("hotmovie")) { + // Only used in Zork Nemesis, handles movies where the player needs to click on something (mj7g, vw3g) + return new HotMovControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("fist")) { + // Only used in Zork Nemesis, handles the door lock puzzle with the skeletal fingers (td9e) + return new FistControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("paint")) { + // Only used in Zork Nemesis, handles the painting puzzle screen in Lucien's room in Irondune (ch4g) + return new PaintControl(_engine, key, stream); + } else if (controlType.equalsIgnoreCase("titler")) { + // Only used in Zork Nemesis, handles the death screen with the Restore/Exit buttons (cjde) + return new TitlerControl(_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..70eaab2a0a --- /dev/null +++ b/engines/zvision/scripting/script_manager.cpp @@ -0,0 +1,904 @@ +/* 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/script_manager.h" + +#include "zvision/zvision.h" +#include "zvision/graphics/render_manager.h" +#include "zvision/graphics/cursors/cursor_manager.h" +#include "zvision/file/save_manager.h" +#include "zvision/scripting/actions.h" +#include "zvision/scripting/menu.h" +#include "zvision/scripting/effects/timer_effect.h" + +#include "common/algorithm.h" +#include "common/hashmap.h" +#include "common/debug.h" +#include "common/stream.h" +#include "common/config-manager.h" + +namespace ZVision { + +ScriptManager::ScriptManager(ZVision *engine) + : _engine(engine), + _currentlyFocusedControl(0), + _activeControls(NULL) { +} + +ScriptManager::~ScriptManager() { + cleanScriptScope(universe); + cleanScriptScope(world); + cleanScriptScope(room); + cleanScriptScope(nodeview); + _controlEvents.clear(); +} + +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); + + _controlEvents.clear(); +} + +void ScriptManager::update(uint deltaTimeMillis) { + if (_currentLocation != _nextLocation) { + ChangeLocationReal(false); + } + + updateNodes(deltaTimeMillis); + if (!execScope(nodeview)) { + return; + } + if (!execScope(room)) { + return; + } + if (!execScope(world)) { + return; + } + if (!execScope(universe)) { + return; + } + updateControls(deltaTimeMillis); +} + +bool ScriptManager::execScope(ScriptScope &scope) { + // Swap queues + PuzzleList *tmp = scope.execQueue; + scope.execQueue = scope.scopeQueue; + scope.scopeQueue = tmp; + scope.scopeQueue->clear(); + + for (PuzzleList::iterator PuzzleIter = scope.puzzles.begin(); PuzzleIter != scope.puzzles.end(); ++PuzzleIter) { + (*PuzzleIter)->addedBySetState = false; + } + + if (scope.procCount < 2 || getStateValue(StateKey_ExecScopeStyle)) { + for (PuzzleList::iterator PuzzleIter = scope.puzzles.begin(); PuzzleIter != scope.puzzles.end(); ++PuzzleIter) { + if (!checkPuzzleCriteria(*PuzzleIter, scope.procCount)) { + return false; + } + } + } else { + for (PuzzleList::iterator PuzzleIter = scope.execQueue->begin(); PuzzleIter != scope.execQueue->end(); ++PuzzleIter) { + if (!checkPuzzleCriteria(*PuzzleIter, scope.procCount)) { + return false; + } + } + } + + if (scope.procCount < 2) { + scope.procCount++; + } + return true; +} + +void ScriptManager::referenceTableAddPuzzle(uint32 key, PuzzleRef ref) { + if (_referenceTable.contains(key)) { + Common::Array<PuzzleRef> *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(ScriptScope &scope) { + // Iterate through each local Puzzle + for (PuzzleList::iterator PuzzleIter = scope.puzzles.begin(); PuzzleIter != scope.puzzles.end(); ++PuzzleIter) { + Puzzle *puzzlePtr = (*PuzzleIter); + + PuzzleRef 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; + } + + // Process only one event + if (!_controlEvents.empty()) { + Common::Event _event = _controlEvents.front(); + Common::Point imageCoord; + switch (_event.type) { + case Common::EVENT_LBUTTONDOWN: + imageCoord = _engine->getRenderManager()->screenSpaceToImageSpace(_event.mouse); + onMouseDown(_event.mouse, imageCoord); + break; + case Common::EVENT_LBUTTONUP: + imageCoord = _engine->getRenderManager()->screenSpaceToImageSpace(_event.mouse); + onMouseUp(_event.mouse, imageCoord); + break; + case Common::EVENT_KEYDOWN: + onKeyDown(_event.kbd); + break; + case Common::EVENT_KEYUP: + onKeyUp(_event.kbd); + break; + default: + break; + } + _controlEvents.pop_front(); + } + + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); iter++) { + if ((*iter)->process(deltaTimeMillis)) { + break; + } + } +} + +bool 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)) { + return true; + } + + // Check each Criteria + if (counter == 0 && (getStateFlag(puzzle->key) & Puzzle::DO_ME_NOW) == 0) { + return true; + } + + 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); + + for (Common::List<ResultAction *>::iterator resultIter = puzzle->resultActions.begin(); resultIter != puzzle->resultActions.end(); ++resultIter) { + if (!(*resultIter)->execute()) { + return false; + } + } + } + + return true; +} + +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(ScriptScope &scope) { + scope.privQueueOne.clear(); + scope.privQueueTwo.clear(); + scope.scopeQueue = &scope.privQueueOne; + scope.execQueue = &scope.privQueueTwo; + 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.procCount = 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<PuzzleRef> *arr = &_referenceTable[key]; + for (int32 i = arr->size() - 1; i >= 0; i--) { + if (!(*arr)[i].puz->addedBySetState) { + (*arr)[i].scope->scopeQueue->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) { + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); ++iter) { + if ((*iter)->getKey() == key) { + return *iter; + } + } + + return nullptr; +} + +void ScriptManager::focusControl(uint32 key) { + if (!_activeControls) { + return; + } + if (_currentlyFocusedControl == key) { + 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::setFocusControlKey(uint32 key) { + _currentlyFocusedControl = key; +} + +void ScriptManager::addSideFX(ScriptingEffect *fx) { + _activeSideFx.push_back(fx); +} + +ScriptingEffect *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(ScriptingEffect::ScriptingEffectType 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) { + if ((*iter)->onKeyDown(keyState)) { + return; + } + } +} + +void ScriptManager::onKeyUp(Common::KeyState keyState) { + if (!_activeControls) { + return; + } + for (ControlList::iterator iter = _activeControls->begin(); iter != _activeControls->end(); ++iter) { + if ((*iter)->onKeyUp(keyState)) { + return; + } + } +} + +void ScriptManager::changeLocation(const Location &_newLocation) { + changeLocation(_newLocation.world, _newLocation.room, _newLocation.node, _newLocation.view, _newLocation.offset); +} + +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 is 0000, return to the previous location. + if (_nextLocation == "0000") { + 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::ChangeLocationReal(bool isLoading) { + assert(_nextLocation.world != 0); + debug(1, "Changing location to: %c %c %c %c %u", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view, _nextLocation.offset); + + const bool enteringMenu = (_nextLocation.world == 'g' && _nextLocation.room == 'j'); + const bool leavingMenu = (_currentLocation.world == 'g' && _currentLocation.room == 'j'); + const bool isSaveScreen = (enteringMenu && _nextLocation.node == 's' && _nextLocation.view == 'e'); + const bool isRestoreScreen = (enteringMenu && _nextLocation.node == 'r' && _nextLocation.view == 'e'); + + if (enteringMenu && !ConfMan.getBool("originalsaveload")) { + if (isSaveScreen || isRestoreScreen) { + // Hook up the ScummVM save/restore dialog + bool gameSavedOrLoaded = _engine->getSaveManager()->scummVMSaveLoadDialog(isSaveScreen); + if (!gameSavedOrLoaded || isSaveScreen) { + // Reload the current room + _nextLocation.world = _currentLocation.world; + _nextLocation.room = _currentLocation.room; + _nextLocation.node = _currentLocation.node; + _nextLocation.view = _currentLocation.view; + _nextLocation.offset = _currentLocation.offset; + + return; + } else { + _currentLocation.world = 'g'; + _currentLocation.room = '0'; + _currentLocation.node = '0'; + _currentLocation.view = '0'; + _currentLocation.offset = 0; + } + } + } + + _engine->setRenderDelay(2); + + if (!leavingMenu) { + if (!isLoading && !enteringMenu) { + 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)); + } + } + + if (enteringMenu) { + if (isSaveScreen && !leavingMenu) { + _engine->getSaveManager()->prepareSaveBuffer(); + } + } else { + if (leavingMenu) { + _engine->getSaveManager()->flushSaveBuffer(); + } + } + + 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); + + _engine->getMenuHandler()->setEnable(0xFFFF); + + 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 == "0000") { + _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); + } + + _engine->getRenderManager()->checkBorders(); +} + +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 nextLocation; + + nextLocation.world = stream->readByte(); + nextLocation.room = stream->readByte(); + nextLocation.node = stream->readByte(); + nextLocation.view = stream->readByte(); + nextLocation.offset = stream->readUint32LE() & 0x0000FFFF; + + while (stream->pos() < stream->size()) { + uint32 tag = stream->readUint32BE(); + uint32 tagSize = 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 < tagSize / 2; i++) { + setStateFlagSilent(i, stream->readUint16LE()); + } + break; + case MKTAG('P', 'U', 'Z', 'Z'): + for (uint32 i = 0; i < tagSize / 2; i++) { + setStateValueSilent(i, stream->readUint16LE()); + } + break; + default: + stream->seek(tagSize, SEEK_CUR); + } + } + + _nextLocation = nextLocation; + + ChangeLocationReal(true); + + _engine->setRenderDelay(10); + setStateValue(StateKey_RestoreFlag, 1); + + _engine->loadSettings(); +} + +Location ScriptManager::getCurrentLocation() const { + Location location = _currentLocation; + location.offset = _engine->getRenderManager()->getCurrentBackgroundOffset(); + + return location; +} + +Location ScriptManager::getLastLocation() { + Location location; + location.world = getStateValue(StateKey_LastWorld); + location.room = getStateValue(StateKey_LastRoom); + location.node = getStateValue(StateKey_LastNode); + location.view = getStateValue(StateKey_LastView); + location.offset = getStateValue(StateKey_LastViewPos); + + return location; +} + +Location ScriptManager::getLastMenuLocation() { + Location location; + location.world = getStateValue(StateKey_Menu_LastWorld); + location.room = getStateValue(StateKey_Menu_LastRoom); + location.node = getStateValue(StateKey_Menu_LastNode); + location.view = getStateValue(StateKey_Menu_LastView); + location.offset = getStateValue(StateKey_Menu_LastViewPos); + + return location; +} + +void ScriptManager::addEvent(Common::Event event) { + _controlEvents.push_back(event); +} + +void ScriptManager::flushEvent(Common::EventType type) { + EventList::iterator it = _controlEvents.begin(); + while (it != _controlEvents.end()) { + + if ((*it).type == type) { + it = _controlEvents.erase(it); + } else { + it++; + } + } +} + +void ScriptManager::trimCommentsAndWhiteSpace(Common::String *string) const { + for (int i = string->size() - 1; i >= 0; i--) { + if ((*string)[i] == '#') { + string->erase(i); + } + } + + string->trim(); +} + +ValueSlot::ValueSlot(ScriptManager *scriptManager, const char *slotValue): + _scriptManager(scriptManager) { + value = 0; + slot = false; + const char *isSlot = strstr(slotValue, "["); + if (isSlot) { + slot = true; + value = atoi(isSlot + 1); + } else { + slot = false; + value = atoi(slotValue); + } +} +int16 ValueSlot::getValue() { + if (slot) { + if (value >= 0) { + return _scriptManager->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..7c276bf917 --- /dev/null +++ b/engines/zvision/scripting/script_manager.h @@ -0,0 +1,381 @@ +/* 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/scripting/puzzle.h" +#include "zvision/scripting/control.h" +#include "zvision/scripting/scripting_effect.h" + +#include "common/hashmap.h" +#include "common/queue.h" +#include "common/events.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_ExecScopeStyle = 76, + StateKey_Brightness = 77, + StateKey_MPEGMovies = 78, + StateKey_EF9_R = 91, + StateKey_EF9_G = 92, + StateKey_EF9_B = 93, + StateKey_EF9_Speed = 94, + StateKey_Inv_Cnt_Slot = 100, + StateKey_Inv_1_Slot = 101, + StateKey_Inv_49_Slot = 149, + // ZGI only + StateKey_Inv_TotalSlots = 150, + StateKey_Inv_StartSlot = 151, + StateKey_Spell_1 = 191, + StateKey_Active_Spell = 205, + StateKey_Reversed_Spellbooc = 206 +}; + +struct Location { + Location() : world('g'), room('a'), node('r'), view('y'), offset(0) {} + + char world; + char room; + char node; + char view; + uint32 offset; +}; + +inline bool operator==(const Location& lhs, const Location& rhs) { + return ( + lhs.world == rhs.world && + lhs.room == rhs.room && + lhs.node == rhs.node && + lhs.view == rhs.view + ); +} + +inline bool operator==(const Location& lhs, const char* rhs) { + Common::String lhsStr = Common::String::format("%c%c%c%c", lhs.world, lhs.room, lhs.node, lhs.view); + return lhsStr == rhs; +} + +inline bool operator!=(const Location& lhs, const Location& rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const Location& lhs, const char* rhs) { + return !(lhs == rhs); +} + +typedef Common::List<Puzzle *> PuzzleList; +typedef Common::Queue<Puzzle *> PuzzleQueue; +typedef Common::List<Control *> ControlList; +typedef Common::HashMap<uint32, int32> StateMap; +typedef Common::List<ScriptingEffect *> SideFXList; +typedef Common::List<Common::Event> EventList; + +class ScriptManager { +public: + ScriptManager(ZVision *engine); + ~ScriptManager(); + +private: + ZVision *_engine; + + struct ScriptScope { + uint32 procCount; + + PuzzleList *scopeQueue; // For adding puzzles to queue + PuzzleList *execQueue; // Switch to it when execute + PuzzleList privQueueOne; + PuzzleList privQueueTwo; + + PuzzleList puzzles; + ControlList controls; + }; + + struct PuzzleRef { + Puzzle *puz; + ScriptScope *scope; + }; + + typedef Common::HashMap<uint32, Common::Array<PuzzleRef> > 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; + + EventList _controlEvents; + + ScriptScope universe; + ScriptScope world; + ScriptScope room; + ScriptScope 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); + // Only change focus control without call focus/unfocus. + void setFocusControlKey(uint32 key); + + void addSideFX(ScriptingEffect *fx); + ScriptingEffect *getSideFX(uint32 key); + void deleteSideFx(uint32 key); + void stopSideFx(uint32 key); + void killSideFx(uint32 key); + void killSideFxType(ScriptingEffect::ScriptingEffectType type); + + void addEvent(Common::Event); + void flushEvent(Common::EventType 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 changeLocation(const Location &_newLocation); + + void serialize(Common::WriteStream *stream); + void deserialize(Common::SeekableReadStream *stream); + + Location getCurrentLocation() const; + Location getLastLocation(); + Location getLastMenuLocation(); + + /** + * Removes any line comments using '#' as a sequence start. + * Then removes any trailing and leading 'whitespace' using String::trim() + * Note: String::trim uses isspace() to determine what is whitespace and what is not. + * + * @param string The string to modify. It is modified in place + */ + void trimCommentsAndWhiteSpace(Common::String *string) const; + +private: + void referenceTableAddPuzzle(uint32 key, PuzzleRef ref); + void addPuzzlesToReferenceTable(ScriptScope &scope); + void updateNodes(uint deltaTimeMillis); + void updateControls(uint deltaTimeMillis); + bool checkPuzzleCriteria(Puzzle *puzzle, uint counter); + void cleanStateTable(); + void cleanScriptScope(ScriptScope &scope); + bool execScope(ScriptScope &scope); + + /** Perform change location */ + void ChangeLocationReal(bool isLoading); + + int8 inventoryGetCount(); + void inventorySetCount(int8 cnt); + int16 inventoryGetItem(int8 id); + void inventorySetItem(int8 id, int16 item); + + void setStateFlagSilent(uint32 key, uint value); + void setStateValueSilent(uint32 key, int value); + +public: + void inventoryAdd(int16 item); + void inventoryDrop(int16 item); + void inventoryCycle(); + +private: + /** + * 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, ScriptScope &scope); + + /** + * 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 + * @param key Puzzle key (for workarounds) + * @return Whether any criteria were read + */ + bool parseCriteria(Common::SeekableReadStream &stream, Common::List<Common::List<Puzzle::CriteriaEntry> > &criteriaList, uint32 key) 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 *scriptManager, const char *slotValue); + int16 getValue(); +private: + int16 value; + bool slot; + ScriptManager *_scriptManager; +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/scripting/scripting_effect.h b/engines/zvision/scripting/scripting_effect.h new file mode 100644 index 0000000000..2a2153204f --- /dev/null +++ b/engines/zvision/scripting/scripting_effect.h @@ -0,0 +1,124 @@ +/* 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 SCRIPTING_EFFECT_H_INCLUDED +#define SCRIPTING_EFFECT_H_INCLUDED + +namespace Common { +class SeekableReadStream; +struct Point; +class WriteStream; +} + +namespace ZVision { + +class ZVision; + +/** + * The base class that represents effects created from Actions. + * This class is virtual. + * + * Detailed Description: + * A scene has Controls. By interacting with the controls, the user + * causes Actions to execute. Certain Actions create 'effects', for + * example, a sound or an animation. This is the base class for + * those effects. + */ +class ScriptingEffect { +public: + + enum ScriptingEffectType { + SCRIPTING_EFFECT_ANIM = 1, + SCRIPTING_EFFECT_AUDIO = 2, + SCRIPTING_EFFECT_DISTORT = 4, + SCRIPTING_EFFECT_PANTRACK = 8, + SCRIPTING_EFFECT_REGION = 16, + SCRIPTING_EFFECT_TIMER = 32, + SCRIPTING_EFFECT_TTYTXT = 64, + SCRIPTING_EFFECT_UNKNOWN = 128, + SCRIPTING_EFFECT_ALL = 255 + }; + + ScriptingEffect() : _engine(0), _key(0), _type(SCRIPTING_EFFECT_UNKNOWN) {} + ScriptingEffect(ZVision *engine, uint32 key, ScriptingEffectType type) : _engine(engine), _key(key), _type(type) {} + virtual ~ScriptingEffect() {} + + uint32 getKey() { + return _key; + } + ScriptingEffectType getType() { + return _type; + } + + virtual bool process(uint32 deltaTimeInMillis) { + return false; + } + /** + * Serialize a SideFX for save game use. This should only be used if a SideFX needs + * to save values that would be different from initialization. AKA a TimerNode needs to + * store the amount of time left on the timer. Any Controls overriding this *MUST* write + * their key as the first data outputted. The default implementation is NOP. + * + * NOTE: If this method is overridden, you MUST also override deserialize() + * and needsSerialization() + * + * @param stream Stream to write any needed data to + */ + virtual void serialize(Common::WriteStream *stream) {} + /** + * De-serialize data from a save game stream. This should only be implemented if the + * SideFX also implements serialize(). The calling method assumes the size of the + * data read from the stream exactly equals that written in serialize(). The default + * implementation is NOP. + * + * NOTE: If this method is overridden, you MUST also override serialize() + * and needsSerialization() + * + * @param stream Save game file stream + */ + virtual void deserialize(Common::SeekableReadStream *stream) {} + /** + * If a SideFX overrides serialize() and deserialize(), this should return true + * + * @return Does the SideFX need save game serialization? + */ + virtual inline bool needsSerialization() { + return false; + } + + virtual bool stop() { + return true; + } + virtual void kill() {} + +protected: + ZVision *_engine; + uint32 _key; + ScriptingEffectType _type; + +// Static member functions +public: + +}; +} // End of namespace ZVision + +#endif // SCRIPTING_EFFECT_H_INCLUDED |