Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,446 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_ACTIONS_H
#define ZVISION_ACTIONS_H
#include "common/path.h"
#include "common/rect.h"
#include "common/str.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);
~ActionAdd();
bool execute() override;
private:
uint32 _key;
ValueSlot *_value = NULL;
};
class ActionAssign : public ResultAction {
public:
ActionAssign(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionAssign() override;
bool execute() override;
private:
uint32 _key;
ValueSlot *_value = NULL;
};
class ActionAttenuate : public ResultAction {
public:
ActionAttenuate(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
int32 _attenuation;
};
class ActionChangeLocation : public ResultAction {
public:
ActionChangeLocation(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
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() override;
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() override;
private:
uint8 _action;
};
class ActionDelayRender : public ResultAction {
public:
ActionDelayRender(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _framesToDelay;
};
class ActionDisableControl : public ResultAction {
public:
ActionDisableControl(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
};
class ActionDisplayMessage : public ResultAction {
public:
ActionDisplayMessage(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
int16 _control;
int16 _msgid;
};
class ActionDissolve : public ResultAction {
public:
ActionDissolve(ZVision *engine);
bool execute() override;
};
class ActionDistort : public ResultAction {
public:
ActionDistort(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionDistort() override;
bool execute() override;
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() override;
private:
uint32 _key;
};
class ActionFlushMouseEvents : public ResultAction {
public:
ActionFlushMouseEvents(ZVision *engine, int32 slotkey);
bool execute() override;
};
class ActionInventory : public ResultAction {
public:
ActionInventory(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
int8 _type;
int32 _key;
};
class ActionKill : public ResultAction {
public:
ActionKill(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
uint32 _type;
};
class ActionMenuBarEnable : public ResultAction {
public:
ActionMenuBarEnable(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint16 _menus;
};
class ActionMusic : public ResultAction {
public:
ActionMusic(ZVision *engine, int32 slotkey, const Common::String &line, bool global);
~ActionMusic() override;
bool execute() override;
private:
Common::Path _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() override;
bool execute() override;
private:
int32 _pos; // Sound source position; NB in panoramas (all original game scripts), this is specified as the X background coordinate; otherwise it is specified in azimuth degrees.
uint8 _mag; // Magnitude of effect (not used by original game scripts); 255 for fully directional sound, 0 for fully ambient
bool _resetMusicNode; // If true (default, original game scripts have no concept of this), associated music slot value is reset to a value of 2 upon creation of this object.
// This seems necessary to ensure all original game pan-track effects load correctly, though it is still unclear exactly what the original intent of these values was.
// So far, best guess for music slotkey values is: 0 = has never been loaded, 1 = loaded and actively playing now, 2 = has loaded & played & then subsequently been killed.
// Since there is literally nothing in the game scripts that sets some of these values to 2, and certain pan_tracks require it to be 2 for the puzzle that creates them to trigger, the original game engine code must have set these values to 2 manually somehow upon conditions being met to allow a pan_track to be created?
bool _staticScreen; // Used by auxiliary scripts to apply directionality to audio in static screens; not used in original game scripts.
bool _resetMixerOnDelete; // Unnecessary and should be set false for original scripts; useful in some cases in extra scripts to avoid brief volume spikes on location changes
uint32 _musicSlot;
};
class ActionPlayAnimation : public ResultAction {
public:
ActionPlayAnimation(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionPlayAnimation() override;
bool execute() override;
private:
Common::Path _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() override;
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() override;
bool execute() override;
private:
Common::Path _fileName;
int32 _mask;
int32 _framerate;
};
class ActionPreferences : public ResultAction {
public:
ActionPreferences(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
bool _save;
};
class ActionQuit : public ResultAction {
public:
ActionQuit(ZVision *engine, int32 slotkey) : ResultAction(engine, slotkey) {}
bool execute() override;
};
class ActionRegion : public ResultAction {
public:
ActionRegion(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionRegion() override;
bool execute() override;
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() override;
private:
uint32 _key;
};
class ActionRandom : public ResultAction {
public:
ActionRandom(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionRandom() override;
bool execute() override;
private:
ValueSlot *_max;
};
class ActionRestoreGame : public ResultAction {
public:
ActionRestoreGame(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
Common::Path _fileName;
};
class ActionRotateTo : public ResultAction {
public:
ActionRotateTo(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
int32 _toPos;
int32 _time;
};
class ActionSetPartialScreen : public ResultAction {
public:
ActionSetPartialScreen(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint _x;
uint _y;
Common::Path _fileName;
int32 _backgroundColor;
};
class ActionSetScreen : public ResultAction {
public:
ActionSetScreen(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
Common::Path _fileName;
};
class ActionStop : public ResultAction {
public:
ActionStop(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
uint32 _key;
};
class ActionStreamVideo : public ResultAction {
public:
ActionStreamVideo(ZVision *engine, int32 slotkey, const Common::String &line);
bool execute() override;
private:
Common::Path _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() override;
private:
int _syncto;
Common::Path _fileName;
};
class ActionTimer : public ResultAction {
public:
ActionTimer(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionTimer() override;
bool execute() override;
private:
ValueSlot *_time;
};
class ActionTtyText : public ResultAction {
public:
ActionTtyText(ZVision *engine, int32 slotkey, const Common::String &line);
~ActionTtyText() override;
bool execute() override;
private:
Common::Path _filename;
uint32 _delay;
Common::Rect _r;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,140 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/detection.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/control.h"
#include "zvision/scripting/script_manager.h"
namespace ZVision {
void Control::parseFlatControl(ZVision *engine) {
debugC(1, kDebugGraphics, "Setting render state to FLAT");
engine->getRenderManager()->getRenderTable()->setRenderState(RenderTable::FLAT);
}
void Control::parsePanoramaControl(ZVision *engine, Common::SeekableReadStream &stream) {
debugC(1, kDebugGraphics, "Setting render state to PANORAMA");
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;
if (sscanf(line.c_str(), "angle(%f)", &fov) == 1)
renderTable->setPanoramaFoV(fov);
} else if (line.matchString("linscale*", true)) {
float scale;
if (sscanf(line.c_str(), "linscale(%f)", &scale) == 1)
renderTable->setPanoramaScale(scale);
} else if (line.matchString("reversepana*", true)) {
uint reverse = 0;
sscanf(line.c_str(), "reversepana(%u)", &reverse);
if (reverse == 1) {
renderTable->setPanoramaReverse(true);
}
} else if (line.matchString("zeropoint*", true)) {
uint point;
if (sscanf(line.c_str(), "zeropoint(%u)", &point) == 1)
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) {
debugC(1, kDebugGraphics, "Setting render state to TILT");
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;
if (sscanf(line.c_str(), "angle(%f)", &fov) == 1)
renderTable->setTiltFoV(fov);
} else if (line.matchString("linscale*", true)) {
float scale;
if (sscanf(line.c_str(), "linscale(%f)", &scale) == 1)
renderTable->setTiltScale(scale);
} else if (line.matchString("reversepana*", true)) {
uint reverse = 0;
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 &parameter, 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

View File

@@ -0,0 +1,146 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#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 &parameter, 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

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/system.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/fist_control.h"
#include "zvision/video/rlf_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(Common::Path(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::Path &fileName) {
Common::File file;
if (!file.open(fileName)) {
warning("Desc file %s could could be opened", fileName.toString().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(Common::Path(values));
} else if (param.matchString("anim_rect", true)) {
int left, top, right, bottom;
if (sscanf(values.c_str(), "%d %d %d %d", &left, &top, &right, &bottom) == 4)
_anmRect = Common::Rect(left, top, right, bottom);
} else if (param.matchString("num_fingers", true)) {
if (sscanf(values.c_str(), "%d", &_fistnum) == 1) {
_fistsUp.resize(_fistnum);
_fistsDwn.resize(_fistnum);
}
} else if (param.matchString("entries", true)) {
if (sscanf(values.c_str(), "%d", &_numEntries) == 1)
_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());
if (sscanf(param.c_str(), "up_hs_num_%d", &fist) == 1)
_fistsUp[fist].resize(num);
} else if (param.matchString("up_hs_*", true)) {
int16 fist, box, x1, y1, x2, y2;
if (sscanf(param.c_str(), "up_hs_%hd_%hd", &fist, &box) == 2) {
if (sscanf(values.c_str(), "%hd %hd %hd %hd", &x1, &y1, &x2, &y2) == 4)
(_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());
if (sscanf(param.c_str(), "down_hs_num_%d", &fist) == 1)
_fistsDwn[fist].resize(num);
} else if (param.matchString("down_hs_*", true)) {
int16 fist, box, x1, y1, x2, y2;
if (sscanf(param.c_str(), "down_hs_%hd_%hd", &fist, &box) == 2) {
if (sscanf(values.c_str(), "%hd %hd %hd %hd", &x1, &y1, &x2, &y2) == 4)
(_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 &parameter, 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

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_FIST_CONTROL_H
#define ZVISION_FIST_CONTROL_H
#include "common/array.h"
#include "common/rect.h"
#include "zvision/scripting/control.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() override;
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) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override;
private:
void readDescFile(const Common::Path &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 &parameter, Common::String &values);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,184 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/system.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/hotmov_control.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(Common::Path(values));
} else if (param.matchString("rectangle", true)) {
int x;
int y;
int width;
int height;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_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];
if (sscanf(values.c_str(), "%s", filename) == 1) {
values = Common::String(filename);
_animation = _engine->loadAnimation(Common::Path(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::Path &fileName) {
if (_framesCount == 0)
return;
Common::File file;
if (!file.open(fileName)) {
warning("HS file %s could could be opened", fileName.toString().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;
if (sscanf(line.c_str(), "%d:%d %d %d %d~", &frame, &x, &y, &width, &height) == 5) {
if (frame >= 0 && frame < _framesCount)
_frames[frame] = Common::Rect(x, y, width, height);
}
}
file.close();
}
} // End of namespace ZVision

View File

@@ -0,0 +1,60 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_HOTMOV_CONTROL_H
#define ZVISION_HOTMOV_CONTROL_H
#include "common/array.h"
#include "common/path.h"
#include "common/rect.h"
#include "zvision/scripting/control.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() override;
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) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override;
private:
void readHsFile(const Common::Path &fileName);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,271 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "backends/keymapper/keymap.h"
#include "common/rect.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/stream.h"
#include "common/system.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/input_control.h"
#include "zvision/text/string_manager.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, y1, x2, y2;
if (sscanf(values.c_str(), "%d %d %d %d", &x1, &y1, &x2, &y2) == 4)
_textRectangle = Common::Rect(x1, y1, x2, y2);
} else if (param.matchString("aux_hotspot", true)) {
int x1, y1, x2, y2;
if (sscanf(values.c_str(), "%d %d %d %d", &x1, &y1, &x2, &y2) == 4)
_headerRectangle = Common::Rect(x1, y1, x2, y2);
} else if (param.matchString("string_init", true)) {
uint fontFormatNumber;
if (sscanf(values.c_str(), "%u", &fontFormatNumber) == 1)
_stringInit.readAllStyles(_engine->getStringManager()->getTextLine(fontFormatNumber));
} else if (param.matchString("chooser_init_string", true)) {
uint fontFormatNumber;
if (sscanf(values.c_str(), "%u", &fontFormatNumber) == 1)
_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];
if (sscanf(values.c_str(), "%24s %*u", fileName) == 1) {
_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;
unfocus();
}
void InputControl::focus() {
if (!_readOnly) {
_engine->getGameKeymap()->setEnabled(false);
}
_focused = true;
_textChanged = true;
}
void InputControl::unfocus() {
if (!_readOnly) {
_engine->getGameKeymap()->setEnabled(true);
}
_focused = false;
_textChanged = true;
}
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

View File

@@ -0,0 +1,74 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_INPUT_CONTROL_H
#define ZVISION_INPUT_CONTROL_H
#include "common/rect.h"
#include "zvision/scripting/control.h"
#include "zvision/text/string_manager.h"
#include "zvision/text/text.h"
namespace Video {
class VideoDecoder;
}
namespace ZVision {
class InputControl : public Control {
public:
InputControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~InputControl() override;
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() override;
void unfocus() override;
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onKeyDown(Common::KeyState keyState) override;
bool process(uint32 deltaTimeInMillis) override;
void setText(const Common::String &_str);
const Common::String getText();
bool enterPress();
void setReadOnly(bool);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,352 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/tokenizer.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/lever_control.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];
if (sscanf(values.c_str(), "%24s", levFileName) == 1)
parseLevFile(levFileName);
} else if (param.matchString("cursor", true)) {
char cursorName[25];
if (sscanf(values.c_str(), "%24s", cursorName) == 1)
_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::Path &fileName) {
debugC(2, kDebugControl, "LeverControl::parseLevFile(%s)", fileName.toString().c_str());
Common::File file;
if (!file.open(fileName)) {
warning("LEV file %s could could be opened", fileName.toString().c_str());
return;
}
Common::String line;
Common::String param;
Common::String values;
int id = 0;
while (!file.eos()) {
line = file.readLine();
getLevParams(line, param, values);
if (param.matchString("animation_id", true)) {
sscanf(values.c_str(), "%d", &id);
debugC(2, kDebugControl, "Lever animation ID: %d", id);
} else if (param.matchString("filename", true)) {
_animation = _engine->loadAnimation(Common::Path(values));
} else if (param.matchString("skipcolor", true)) {
// Not used
} else if (param.matchString("anim_coords", true)) {
int left, top, right, bottom;
if (sscanf(values.c_str(), "%d %d %d %d", &left, &top, &right, &bottom) == 4) {
_animationCoords.left = left;
_animationCoords.top = top;
_animationCoords.right = right;
_animationCoords.bottom = bottom;
}
} else if (param.matchString("mirrored", true)) {
uint mirrored;
if (sscanf(values.c_str(), "%u", &mirrored) == 1)
_mirrored = mirrored == 0 ? false : true;
} else if (param.matchString("frames", true)) {
if (sscanf(values.c_str(), "%u", &_frameCount) == 1)
_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)) {
if (sscanf(values.c_str(), "%u", &_startFrame) == 1)
_currentFrame = _startFrame;
} else if (param.matchString("hotspot_deltas", true)) {
uint x;
uint y;
if (sscanf(values.c_str(), "%u %u", &x, &y) == 2) {
_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;
if (sscanf(token.c_str(), "%u,%u", &toFrame, &angle) == 2)
_frameInfo[frameNumber].paths.push_back(PathSegment(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.
}
// WORKAROUND for a script bug in Zork: Nemesis, room tz2e (orrery)
// Animation coordinates for left hand lever do not properly align with background image
switch (id) {
case 2926:
_animationCoords.bottom -= 4;
_animationCoords.right += 1;
_animationCoords.left -= 1;
_animationCoords.top += 1;
break;
default:
break;
}
// Cycle through all unit direction vectors in path segments & determine step distance
debugC(3, kDebugControl, "Setting step distances");
for (uint frame=0; frame < _frameCount; frame++) {
debugC(3, kDebugControl, "Frame %d", frame);
for (auto &iter : _frameInfo[frame].paths) {
uint destFrame = iter.toFrame;
Common::Point deltaPos = _frameInfo[destFrame].hotspot.origin() - _frameInfo[frame].hotspot.origin();
Math::Vector2d deltaPosVector((float)deltaPos.x, (float)deltaPos.y);
iter.distance *= deltaPosVector.getMagnitude();
debugC(3, kDebugControl, "\tdeltaPos = %d,%d, Distance %f", deltaPos.x, deltaPos.y, iter.distance);
}
}
debugC(2, kDebugControl, "LeverControl::~parseLevFile()");
}
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;
_gripOffset = backgroundImageSpacePos - _frameInfo[_currentFrame].hotspot.origin();
}
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;
}
_gripOffset = Common::Point(0,0);
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) {
uint nextFrame = _currentFrame;
do {
Common::Point gripOrigin = _frameInfo[_currentFrame].hotspot.origin() + _gripOffset;
debugC(1, kDebugControl, "LeverControl::onMouseMove() screenPos = %d,%d, imagePos = %d,%d, gripOrigin = %d,%d", screenSpacePos.x, screenSpacePos.y, backgroundImageSpacePos.x, backgroundImageSpacePos.y, gripOrigin.x, gripOrigin.y);
Common::Point deltaPos = backgroundImageSpacePos - gripOrigin;
nextFrame = getNextFrame(deltaPos);
if (nextFrame != _currentFrame) {
_currentFrame = nextFrame;
_engine->getScriptManager()->setStateValue(_key, _currentFrame);
}
} while (nextFrame != _currentFrame);
if (_lastRenderedFrame != _currentFrame)
renderFrame(_currentFrame);
_engine->getCursorManager()->changeCursor(_cursor);
cursorWasChanged = true;
} else if (_frameInfo[_currentFrame].hotspot.contains(backgroundImageSpacePos)) {
_engine->getCursorManager()->changeCursor(_cursor);
cursorWasChanged = true;
}
return cursorWasChanged;
}
uint LeverControl::getNextFrame(Common::Point &deltaPos) {
Math::Vector2d movement((float)deltaPos.x, (float)deltaPos.y);
for (auto &iter : _frameInfo[_currentFrame].paths) {
debugC(1, kDebugControl, "\tPossible step = %f,%f, angle = %d, distance %f", iter.direction.getX(), iter.direction.getY(), (uint)Math::rad2deg(iter.angle), iter.distance);
if (movement.dotProduct(iter.direction) >= iter.distance/2) {
return iter.toFrame;
}
}
return _currentFrame;
}
bool LeverControl::process(uint32 deltaTimeInMillis) {
if (_engine->getScriptManager()->getStateFlag(_key) & Puzzle::DISABLED)
return false;
if (_isReturning) {
_accumulatedTime += deltaTimeInMillis;
while (_accumulatedTime >= _returnFramePeriod) {
_accumulatedTime -= _returnFramePeriod;
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;
}
void LeverControl::renderFrame(uint frameNumber) {
_lastRenderedFrame = frameNumber;
if (frameNumber != 0 && frameNumber < _lastRenderedFrame && _mirrored)
frameNumber = (_frameCount * 2) - frameNumber - 1;
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 &parameter, 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

View File

@@ -0,0 +1,96 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_LEVER_CONTROL_H
#define ZVISION_LEVER_CONTROL_H
#include "common/list.h"
#include "common/path.h"
#include "common/rect.h"
#include "math/vector2d.h"
#include "zvision/scripting/control.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() override;
private:
struct PathSegment {
PathSegment(uint a, uint t) : angle(Math::deg2rad<float>(a)), toFrame(t), direction(cos(angle), -sin(angle)) {}
float angle; // Radians
uint toFrame;
Math::Vector2d direction; // NB unit vector upon initialisation
float distance = 1.0f;
};
struct FrameInfo {
Common::Rect hotspot;
Common::List<PathSegment> paths;
Common::List<uint> returnRoute;
};
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 _gripOffset;
Common::List<uint>::iterator _returnRoutesCurrentProgress;
uint _returnRoutesCurrentFrame;
const uint8 _returnFramePeriod = 60; // milliseconds
uint32 _accumulatedTime;
public:
bool onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override;
private:
void parseLevFile(const Common::Path &fileName);
void renderFrame(uint frameNumber);
void getLevParams(const Common::String &inputStr, Common::String &parameter, Common::String &values);
uint getNextFrame(Common::Point &deltaPos);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,216 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/paint_control.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;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_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(Common::Path(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(Common::Path(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

View File

@@ -0,0 +1,89 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_PAINT_CONTROL_H
#define ZVISION_PAINT_CONTROL_H
#include "common/list.h"
#include "common/rect.h"
#include "graphics/surface.h"
#include "zvision/scripting/control.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() override;
/**
* @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) override;
/**
* @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) override;
/**
* 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) override;
bool process(uint32 deltaTimeInMillis) override {
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

View File

@@ -0,0 +1,142 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/push_toggle_control.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;
if (sscanf(values.c_str(), "%u,%u,%u,%u", &x, &y, &width, &height) == 4)
_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

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_PUSH_TOGGLE_CONTROL_H
#define ZVISION_PUSH_TOGGLE_CONTROL_H
#include "common/array.h"
#include "common/events.h"
#include "common/rect.h"
#include "zvision/scripting/control.h"
namespace ZVision {
class PushToggleControl : public Control {
public:
PushToggleControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~PushToggleControl() override;
/**
* 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) override;
/**
* 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) override;
/**
* 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) override;
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

View File

@@ -0,0 +1,177 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/system.h"
#include "common/tokenizer.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/safe_control.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(Common::Path(values));
_animation->start();
} else if (param.matchString("rectangle", true)) {
int x;
int y;
int width;
int height;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_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;
if (sscanf(values.c_str(), "%d %d", &x, &y) == 2)
_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;
// Coverity complains about the order of arguments here,
// but changing that breaks the Zork Nemesis safe puzzle.
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

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_SAFE_CONTROL_H
#define ZVISION_SAFE_CONTROL_H
#include "common/list.h"
#include "common/rect.h"
#include "zvision/scripting/control.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() override;
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) override;
bool onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) override;
bool process(uint32 deltaTimeInMillis) override;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,118 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "common/str.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/file/save_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/input_control.h"
#include "zvision/scripting/controls/save_control.h"
#include "zvision/text/string_manager.h"
#include "zvision/text/subtitle_manager.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;
if (sscanf(values.c_str(), "%d %d", &saveId, &inputId) == 2) {
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->getSubtitleManager()->askQuestion(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVEEXIST)))
toSave = false;
if (toSave) {
_engine->getSaveManager()->saveGame(iter->saveId, inp->getText(), true);
_engine->getSubtitleManager()->delayedMessage(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVED), 2000);
_engine->getScriptManager()->changeLocation(_engine->getScriptManager()->getLastMenuLocation());
}
} else {
_engine->getSubtitleManager()->timedMessage(_engine->getStringManager()->getTextLine(StringManager::ZVISION_STR_SAVEEMPTY), 2000);
}
} else {
_engine->getSaveManager()->loadGame(iter->saveId);
return true;
}
break;
}
}
}
return false;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,53 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_SAVE_CONTROL_H
#define ZVISION_SAVE_CONTROL_H
#include "common/list.h"
#include "zvision/scripting/control.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) override;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,213 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/slot_control.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;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_hotspot = Common::Rect(x, y, width, height);
} else if (param.matchString("rectangle", true)) {
int x;
int y;
int width;
int height;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &width, &height) == 4)
_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)
Common::sprintf_s(buf, "%d%cobj.tga", curItem, _distanceId);
else
Common::sprintf_s(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

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_SLOT_CONTROL_H
#define ZVISION_SLOT_CONTROL_H
#include "common/list.h"
#include "common/rect.h"
#include "graphics/surface.h"
#include "zvision/scripting/control.h"
namespace ZVision {
class SlotControl : public Control {
public:
SlotControl(ZVision *engine, uint32 key, Common::SeekableReadStream &stream);
~SlotControl() override;
/**
* 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) override;
/**
* 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) override;
bool process(uint32 deltaTimeInMillis) override;
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

View File

@@ -0,0 +1,103 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/titler_control.h"
#include "zvision/text/text.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(Common::Path(values));
} else if (param.matchString("rectangle", true)) {
int x;
int y;
int x2;
int y2;
if (sscanf(values.c_str(), "%d %d %d %d", &x, &y, &x2, &y2) == 4)
_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::Path &fileName) {
Common::File file;
if (!file.open(fileName)) {
warning("String_resource_file %s could could be opened", fileName.toString().c_str());
return;
}
_strings.clear();
while (!file.eos()) {
Common::String line = readWideLine(file).encode();
_strings.push_back(line);
}
file.close();
}
} // End of namespace ZVision

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_TITLER_CONTROL_H
#define ZVISION_TITLER_CONTROL_H
#include "common/array.h"
#include "common/file.h"
#include "common/path.h"
#include "common/rect.h"
#include "common/stream.h"
#include "graphics/surface.h"
#include "zvision/scripting/control.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() override;
void setString(int strLine);
private:
Common::Array< Common::String > _strings;
Common::Rect _rectangle;
int16 _curString;
Graphics::Surface *_surface;
void readStringsFile(const Common::Path &fileName);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,213 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "graphics/surface.h"
#include "video/video_decoder.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/animation_effect.h"
namespace ZVision {
AnimationEffect::AnimationEffect(ZVision *engine, uint32 controlKey, const Common::Path &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) {
int dstw;
int 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

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_ANIMATION_NODE_H
#define ZVISION_ANIMATION_NODE_H
#include "common/list.h"
#include "common/path.h"
#include "common/rect.h"
#include "zvision/scripting/scripting_effect.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::Path &fileName, int32 mask, int32 frate, bool disposeAfterUse = true);
~AnimationEffect() override;
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) override;
void addPlayNode(int32 slot, int x, int y, int x2, int y2, int startFrame, int endFrame, int loops = 1);
bool stop() override;
};
} // End of namespace ZVision
#endif

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/render_table.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/distort_effect.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();
switch (table->getRenderState()) {
case RenderTable::PANORAMA: {
table->setPanoramaFoV(angl);
table->setPanoramaScale(linScale);
table->generateRenderTable();
_engine->getRenderManager()->markDirty();
break;
}
case RenderTable::TILT: {
table->setTiltFoV(angl);
table->setTiltScale(linScale);
table->generateRenderTable();
_engine->getRenderManager()->markDirty();
break;
}
default:
break;
}
}
} // End of namespace ZVision

View File

@@ -0,0 +1,62 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#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() override;
bool process(uint32 deltaTimeInMillis) override;
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

View File

@@ -0,0 +1,290 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "audio/decoders/wave.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "math/utils.h"
#include "math/angle.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/music_effect.h"
#include "zvision/sound/midi.h"
#include "zvision/sound/volume_manager.h"
#include "zvision/sound/zork_raw.h"
namespace ZVision {
void MusicNodeBASE::setDirection(Math::Angle azimuth, uint8 magnitude) {
if (_engine->getScriptManager()->getStateValue(StateKey_Qsound) >= 1) {
_azimuth = azimuth;
_directionality = magnitude;
_balance = ((int)(127 * _azimuth.getSine()) * _directionality) / 255;
} else
setBalance(0);
updateMixer();
}
void MusicNodeBASE::setBalance(int8 balance) {
_balance = balance;
_azimuth.setDegrees(0);
_directionality = 255;
updateMixer();
}
void MusicNodeBASE::updateMixer() {
if (_engine->getScriptManager()->getStateValue(StateKey_Qsound) >= 1)
_volumeOut = _engine->getVolumeManager()->convert(_volume, _azimuth, _directionality); // Apply game-specific volume profile and then attenuate according to azimuth
else
_volumeOut = _engine->getVolumeManager()->convert(_volume); // Apply game-specific volume profile and ignore azimuth
outputMixer();
}
MusicNode::MusicNode(ZVision *engine, uint32 key, Common::Path &filename, bool loop, uint8 volume)
: MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) {
_loop = loop;
_volume = volume;
_balance = 0;
_fade = false;
_fadeStartVol = volume;
_fadeEndVol = 0;
_fadeTime = 0;
_fadeElapsed = 0;
_sub = 0;
_stereo = false;
_loaded = false;
Audio::RewindableAudioStream *audioStream = NULL;
if (filename.baseName().contains(".wav")) {
Common::File *file = new Common::File();
if (file->open(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, _volume);
} else {
_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream, -1, _volume);
}
if (_key != StateKey_NotSet) {
debugC(3, kDebugSound, "setting musicnode state value to 1");
_engine->getScriptManager()->setStateValue(_key, 1);
}
// Change filename.raw into filename.sub
Common::String subname = filename.baseName();
subname.setChar('s', subname.size() - 3);
subname.setChar('u', subname.size() - 2);
subname.setChar('b', subname.size() - 1);
Common::Path subpath(filename.getParent().appendComponent(subname));
if (SearchMan.hasFile(subpath))
_sub = _engine->getSubtitleManager()->create(subpath, _handle); // NB automatic subtitle!
_loaded = true;
updateMixer();
}
debugC(3, kDebugSound, "MusicNode: %d created", _key);
}
MusicNode::~MusicNode() {
if (_loaded)
_engine->_mixer->stopHandle(_handle);
if (_key != StateKey_NotSet)
_engine->getScriptManager()->setStateValue(_key, 2);
if (_sub)
_engine->getSubtitleManager()->destroy(_sub);
debugC(3, kDebugSound, "MusicNode: %d destroyed", _key);
}
void MusicNode::outputMixer() {
_engine->_mixer->setChannelBalance(_handle, _balance);
_engine->_mixer->setChannelVolume(_handle, _volumeOut);
}
void MusicNode::setFade(int32 time, uint8 target) {
_fadeStartVol = _volume;
_fadeEndVol = target;
_fadeElapsed = 0;
_fadeTime = time <= 0 ? 0 : (uint32)time;
_fade = true;
}
bool MusicNode::process(uint32 deltaTimeInMillis) {
if (!_loaded || ! _engine->_mixer->isSoundHandleActive(_handle))
return stop();
else {
if (_fade) {
debugC(3, kDebugSound, "Fading music, endVol %d, startVol %d, current %d, fade time %d, elapsed time %dms", _fadeEndVol, _fadeStartVol, _volume, _fadeTime, _fadeElapsed);
uint8 _newvol = 0;
_fadeElapsed += deltaTimeInMillis;
if ((_fadeTime <= 0) | (_fadeElapsed >= _fadeTime)) {
_newvol = _fadeEndVol;
_fade = false;
} else {
if (_fadeEndVol > _fadeStartVol)
_newvol = _fadeStartVol + (_fadeElapsed * (_fadeEndVol - _fadeStartVol)) / _fadeTime;
else
_newvol = _fadeStartVol - (_fadeElapsed * (_fadeStartVol - _fadeEndVol)) / _fadeTime;
}
if (_volume != _newvol)
setVolume(_newvol);
}
}
return false;
}
void MusicNode::setVolume(uint8 newVolume) {
if (_loaded) {
debugC(4, kDebugSound, "Changing volume of music node %d from %d to %d", _key, _volume, newVolume);
_volume = newVolume;
updateMixer();
}
}
PanTrackNode::PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos, uint8 mag, bool resetMixerOnDelete, bool staticScreen)
: ScriptingEffect(engine, key, SCRIPTING_EFFECT_PANTRACK),
_slot(slot),
_sourcePos(0),
_viewPos(0),
_mag(mag),
_width(0),
_pos(pos),
_staticScreen(staticScreen),
_resetMixerOnDelete(resetMixerOnDelete) {
debugC(3, kDebugSound, "Created PanTrackNode, key %d, slot %d", _key, _slot);
process(0); // Try to set pan value for music node immediately
}
PanTrackNode::~PanTrackNode() {
debugC(1, kDebugSound, "Deleting PanTrackNode, key %d, slot %d", _key, _slot);
ScriptManager *scriptManager = _engine->getScriptManager();
ScriptingEffect *fx = scriptManager->getSideFX(_slot);
if (fx && fx->getType() == SCRIPTING_EFFECT_AUDIO && _resetMixerOnDelete) {
debugC(1, kDebugSound, "Resetting mixer, slot %d", _slot);
MusicNodeBASE *mus = (MusicNodeBASE *)fx;
mus->setBalance(0);
} else
debugC(1, kDebugSound, "NOT resetting mixer, slot %d", _slot);
}
bool PanTrackNode::process(uint32 deltaTimeInMillis) {
debugC(3, kDebugSound, "Processing PanTrackNode, key %d", _key);
ScriptManager *scriptManager = _engine->getScriptManager();
ScriptingEffect *fx = scriptManager->getSideFX(_slot);
if (fx && fx->getType() == SCRIPTING_EFFECT_AUDIO) {
MusicNodeBASE *mus = (MusicNodeBASE *)fx;
if (!_staticScreen)
// Original game scripted behaviour
switch (_engine->getRenderManager()->getRenderTable()->getRenderState()) {
case RenderTable::PANORAMA:
debugC(3, kDebugSound, "PanTrackNode in panorama mode");
_width = _engine->getRenderManager()->getBkgSize().x;
if (_width) {
_sourcePos.setDegrees(360 * _pos / _width);
_viewPos.setDegrees(360 * scriptManager->getStateValue(StateKey_ViewPos) / _width);
} else {
warning("Encountered zero background width whilst processing PanTrackNode in panoramic mode!");
}
break;
case RenderTable::FLAT:
case RenderTable::TILT:
default:
debugC(3, kDebugSound, "PanTrackNode in FLAT/TILT mode");
_sourcePos.setDegrees(0);
_viewPos.setDegrees(0);
break;
} else {
// Used for auxiliary scripts only
_sourcePos.setDegrees(_pos);
_viewPos.setDegrees(0);
}
Math::Angle azimuth;
azimuth = _sourcePos - _viewPos;
debugC(3, kDebugSound, "soundPos: %f, _viewPos: %f, azimuth: %f, width %d", _sourcePos.getDegrees(), _viewPos.getDegrees(), azimuth.getDegrees(), _width);
// azimuth is sound source position relative to player, clockwise from centre of camera axis to front when viewed top-down
mus->setDirection(azimuth, _mag);
}
return false;
}
MusicMidiNode::MusicMidiNode(ZVision *engine, uint32 key, uint8 program, uint8 note, uint8 volume)
: MusicNodeBASE(engine, key, SCRIPTING_EFFECT_AUDIO) {
_volume = volume;
_prog = program;
_noteNumber = note;
_pan = 0;
_chan = _engine->getMidiManager()->getFreeChannel();
if (_chan >= 0) {
updateMixer();
_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::setFade(int32 time, uint8 target) {
}
bool MusicMidiNode::process(uint32 deltaTimeInMillis) {
return false;
}
void MusicMidiNode::setVolume(uint8 newVolume) {
if (_chan >= 0) {
_volume = newVolume;
updateMixer();
}
}
void MusicMidiNode::outputMixer() {
_engine->getMidiManager()->setBalance(_chan, _balance);
_engine->getMidiManager()->setPan(_chan, _pan);
_engine->getMidiManager()->setVolume(_chan, _volumeOut);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,146 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_MUSIC_NODE_H
#define ZVISION_MUSIC_NODE_H
#include "audio/mixer.h"
#include "math/angle.h"
#include "zvision/scripting/scripting_effect.h"
#include "zvision/sound/volume_manager.h"
#include "zvision/text/subtitle_manager.h"
namespace Common {
class String;
}
namespace ZVision {
class MusicNodeBASE : public ScriptingEffect {
public:
MusicNodeBASE(ZVision *engine, uint32 key, ScriptingEffectType type) : ScriptingEffect(engine, key, type) {}
~MusicNodeBASE() override {}
/**
* 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) override = 0;
virtual void setVolume(uint8 volume) = 0;
uint8 getVolume() {
return _volume;
}
virtual void setFade(int32 time, uint8 target) = 0;
virtual void setBalance(int8 balance); // NB Overrides effects of setDirection()
void setDirection(Math::Angle azimuth, uint8 magnitude = 255); // NB Overrides effects of setBalance()
protected:
void updateMixer();
virtual void outputMixer() = 0;
uint8 _volume = 0;
int8 _balance = 0;
Math::Angle _azimuth;
uint8 _directionality; // 0 = fully ambient, 255 = fully directional
uint8 _volumeOut = 0;
};
class MusicNode : public MusicNodeBASE {
public:
MusicNode(ZVision *engine, uint32 key, Common::Path &file, bool loop, uint8 volume);
~MusicNode() override;
/**
* 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) override;
void setVolume(uint8 volume) override;
void setFade(int32 time, uint8 target) override;
private:
void outputMixer() override;
bool _loop;
bool _fade;
uint8 _fadeStartVol;
uint8 _fadeEndVol;
uint32 _fadeTime;
uint32 _fadeElapsed; // Cumulative time since fade start
bool _stereo;
Audio::SoundHandle _handle;
uint16 _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, uint8 program, uint8 note, uint8 volume);
~MusicMidiNode() override;
/**
* 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) override;
void setVolume(uint8 volume) override;
void setFade(int32 time, uint8 target) override;
private:
void outputMixer() override;
int8 _chan;
uint8 _noteNumber;
int8 _pan;
uint8 _prog;
};
class PanTrackNode : public ScriptingEffect {
public:
PanTrackNode(ZVision *engine, uint32 key, uint32 slot, int16 pos, uint8 mag = 255, bool resetMixerOnDelete = false, bool staticScreen = false);
~PanTrackNode() override;
bool process(uint32 deltaTimeInMillis) override;
private:
uint32 _slot;
int16 _width, _pos;
Math::Angle _sourcePos, _viewPos;
uint8 _mag;
bool _resetMixerOnDelete;
bool _staticScreen;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,53 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/region_effect.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

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_REGION_NODE_H
#define ZVISION_REGION_NODE_H
#include "graphics/surface.h"
#include "zvision/graphics/graphics_effect.h"
#include "zvision/scripting/scripting_effect.h"
namespace ZVision {
class ZVision;
class RegionNode : public ScriptingEffect {
public:
RegionNode(ZVision *engine, uint32 key, GraphicsEffect *effect, uint32 delay);
~RegionNode() override;
/**
* 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) override;
private:
int32 _timeLeft;
uint32 _delay;
GraphicsEffect *_effect;
};
} // End of namespace ZVision
#endif

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "audio/decoders/wave.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/syncsound_effect.h"
#include "zvision/sound/zork_raw.h"
namespace ZVision {
SyncSoundNode::SyncSoundNode(ZVision *engine, uint32 key, Common::Path &filename, int32 syncto)
: ScriptingEffect(engine, key, SCRIPTING_EFFECT_AUDIO) {
_syncto = syncto;
_sub = 0;
Audio::RewindableAudioStream *audioStream = NULL;
if (filename.baseName().contains(".wav")) {
Common::File *file = new Common::File();
if (file->open(filename)) {
audioStream = Audio::makeWAVStream(file, DisposeAfterUse::YES);
}
} else {
audioStream = makeRawZorkStream(filename, _engine);
}
_engine->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, audioStream);
Common::String subname = filename.baseName();
subname.setChar('s', subname.size() - 3);
subname.setChar('u', subname.size() - 2);
subname.setChar('b', subname.size() - 1);
Common::Path subpath(filename.getParent().appendComponent(subname));
if (SearchMan.hasFile(subpath))
_sub = _engine->getSubtitleManager()->create(subpath, _handle); // NB automatic subtitle!
}
SyncSoundNode::~SyncSoundNode() {
_engine->_mixer->stopHandle(_handle);
if (_sub)
_engine->getSubtitleManager()->destroy(_sub);
}
bool SyncSoundNode::process(uint32 deltaTimeInMillis) {
if (! _engine->_mixer->isSoundHandleActive(_handle))
return stop();
else {
if (_engine->getScriptManager()->getSideFX(_syncto) == NULL)
return stop();
}
return false;
}
} // End of namespace ZVision

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_SYNCSOUND_NODE_H
#define ZVISION_SYNCSOUND_NODE_H
#include "audio/mixer.h"
#include "zvision/scripting/scripting_effect.h"
#include "zvision/text/subtitle_manager.h"
namespace Common {
class String;
}
namespace ZVision {
class SyncSoundNode : public ScriptingEffect {
public:
SyncSoundNode(ZVision *engine, uint32 key, Common::Path &file, int32 syncto);
~SyncSoundNode() override;
/**
* 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) override;
private:
int32 _syncto;
Audio::SoundHandle _handle;
uint16 _sub;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,77 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/timer_effect.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

View File

@@ -0,0 +1,58 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#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() override;
/**
* 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) override;
void serialize(Common::WriteStream *stream) override;
void deserialize(Common::SeekableReadStream *stream) override;
inline bool needsSerialization() override {
return true;
}
bool stop() override;
private:
int32 _timeLeft;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,199 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "common/unicode-bidi.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/ttytext_effect.h"
#include "zvision/text/text.h"
namespace ZVision {
ttyTextNode::ttyTextNode(ZVision *engine, uint32 key, const Common::Path &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;
_lineStartPos = 0;
_startX = 0;
Common::File *infile = new Common::File;
infile->open(file);
if (infile) {
while (!infile->eos()) {
Common::U32String asciiLine = readWideLine(*infile);
if (asciiLine.empty()) {
continue;
}
_txtbuf += asciiLine;
}
delete infile;
}
_isRTL = Common::convertBiDiU32String(_txtbuf).visual != _txtbuf;
_img.create(_r.width(), _r.height(), _engine->_resourcePixelFormat);
_state._sharp = true;
_state.readAllStyles(_txtbuf.encode());
_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) {
Common::String buf = _txtbuf.substr(start + 1, end - start - 1);
ret = _state.parseStyle(buf, buf.size());
}
}
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));
if (_isRTL) {
int16 currDx = _dx + _fnt.getStringWidth(buf);
_dx = _r.width() - currDx;
_isRTL = false;
for (uint8 j = 0; j < buf.size(); j++)
outchar(buf[j]);
_isRTL = true;
_dx = currDx;
} else {
for (uint8 j = 0; j < buf.size(); j++)
outchar(buf[j]);
}
}
_txtpos++;
_lineStartPos = _txtpos;
_startX = _dx;
} else {
uint32 pos = _lineStartPos;
int16 dx = _startX;
while (pos < _txtbuf.size() && _txtbuf[pos] != '<') {
uint16 chr = _txtbuf[pos];
if (chr == ' ') {
uint32 i = pos + 1;
uint16 width = _fnt.getCharWidth(chr);
while (i < _txtbuf.size() && _txtbuf[i] != ' ' && _txtbuf[i] != '<') {
uint16 uchr = _txtbuf[i];
width += _fnt.getCharWidth(uchr);
i++;
}
if (dx + width > _r.width())
break;
}
dx += _fnt.getCharWidth(chr);
pos++;
}
Common::U32String lineBuffer = Common::convertBiDiU32String(_txtbuf.substr(_lineStartPos, pos - _lineStartPos)).visual;
if (pos == _txtpos)
newline();
else if (_isRTL)
outchar(lineBuffer[pos - _txtpos - 1]);
else
outchar(lineBuffer[_txtpos - _lineStartPos]);
_txtpos++;
}
_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;
_lineStartPos = _txtpos + 1;
_startX = _dx;
}
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();
if (_isRTL)
_fnt.drawChar(&_img, chr, _r.width() - _dx - _fnt.getCharWidth(chr), _dy, clr);
else
_fnt.drawChar(&_img, chr, _dx, _dy, clr);
_dx += _fnt.getCharWidth(chr);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,74 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#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::Path &file, const Common::Rect &r, int32 delay);
~ttyTextNode() override;
/**
* 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) override;
private:
Common::Rect _r;
TextStyleState _state;
StyledTTFont _fnt;
Common::U32String _txtbuf;
uint32 _txtpos;
uint32 _lineStartPos;
bool _isRTL;
int32 _delay;
int32 _nexttime;
Graphics::Surface _img;
int16 _dx;
int16 _startX;
int16 _dy;
private:
void newline();
void scroll();
void outchar(uint16 chr);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,120 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "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

View File

@@ -0,0 +1,592 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "zvision/detection.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/menu.h"
namespace ZVision {
enum {
kFocusNone = -1,
kFocusItems = 0,
kFocusMagic = 1,
kFocusMain = 2
};
MenuManager::MenuManager(ZVision *engine, const Common::Rect menuArea, const MenuParams params) :
_engine(engine),
_params(params),
_menuBarFlag(0xFFFF),
_menuArea(menuArea),
_menuOrigin(menuArea.origin()),
_menuTriggerArea(_menuOrigin, _menuArea.width(), _params.triggerHeight),
_mainScroller(params.activePos, params.idlePos, params.period) {
_enableFlags.set_size(6);
for (int8 i = 0; i < 4; i++) {
// Generate button hotspot areas
_menuHotspots[i] = Common::Rect(_params.wxButs[i][1], _menuArea.top, _params.wxButs[i][1] + _params.wxButs[i][0], _menuArea.bottom);
// Initialise button animation frames
_mainFrames[i] = _params.idleFrame;
}
for (int i = 0; i < 4; i++)
_buttonAnim[i] = new LinearScroller(_params.activeFrame, _params.idleFrame, _params.buttonPeriod);
setFocus(kFocusNone); // Ensure focus list is initialised
_mainArea = Common::Rect(_params.wMain, _hMainMenu);
_mainArea.moveTo(_menuOrigin + _mainScroller._pos);
}
MenuManager::~MenuManager() {
for (int i = 0; i < 4; i++)
delete _buttonAnim[i];
_mainBack.free();
}
void MenuManager::setEnable(uint16 flags) {
static const uint16 flagMasks[6] = {0x8, 0x4, 0x2, 0x1, 0x100, 0x200}; // Enum order: save,restore,prefs,quit,items,magic
_menuBarFlag = flags;
for (uint i = 0; i <= 5; i++) {
if (_menuBarFlag & flagMasks[i])
_enableFlags.set(i);
else
_enableFlags.unset(i);
}
}
void MenuManager::onMouseUp(const Common::Point &pos) {
if (_menuFocus.front() == kFocusMain) {
_mouseOnItem = mouseOverMain(pos);
if (_mouseOnItem == _mainClicked)
// Activate clicked action from main menu
switch (_mouseOnItem) {
case kMainMenuSave:
_engine->getScriptManager()->changeLocation('g', 'j', 's', 'e', 0);
setFocus(kFocusNone);
_mainScroller.reset();
_redraw = true;
break;
case kMainMenuLoad:
_engine->getScriptManager()->changeLocation('g', 'j', 'r', 'e', 0);
setFocus(kFocusNone);
_mainScroller.reset();
_redraw = true;
break;
case kMainMenuPrefs:
_engine->getScriptManager()->changeLocation('g', 'j', 'p', 'e', 0);
setFocus(kFocusNone);
_mainScroller.reset();
_redraw = true;
break;
case kMainMenuExit:
_engine->quit(true);
break;
default:
break;
}
}
_mainClicked = -1;
}
void MenuManager::onMouseDown(const Common::Point &pos) {
if (_menuFocus.front() == kFocusMain) {
_mouseOnItem = mouseOverMain(pos);
// Show clicked graphic
if ((_mouseOnItem >= 0) && (_mouseOnItem < 4))
if (_enableFlags.get(_mouseOnItem)) {
_mainClicked = _mouseOnItem;
_redraw = true;
}
}
}
void MenuManager::onMouseMove(const Common::Point &pos) {
bool nowInMenu = inMenu(pos);
if (nowInMenu != _prevInMenu)
_redraw = true;
_prevInMenu = nowInMenu;
int lastItem = _mouseOnItem;
switch (_menuFocus.front()) {
case kFocusMain:
// Inform game scripting engine that mouse is in main menu
if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 2)
_engine->getScriptManager()->setStateValue(StateKey_MenuState, 2);
_mouseOnItem = mouseOverMain(pos);
break;
case kFocusNone:
// Inform game scripting engine that mouse is not in any menu
if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0)
_engine->getScriptManager()->setStateValue(StateKey_MenuState, 0);
_mouseOnItem = -1;
break;
}
_mainScroller.setActive(_menuFocus.front() == kFocusMain);
// Update button animation status
for (int i = 0; i < 4; i++)
if (_menuFocus[0] == kFocusMain && _mouseOnItem == i)
_buttonAnim[i]->setActive(true);
else
_buttonAnim[i]->setActive(false);
if (lastItem != _mouseOnItem)
_redraw = true;
}
int MenuManager::mouseOverMain(const Common::Point &pos) {
// Common::Rect mainHotspot(28,_hSideMenu);
// mainHotspot.moveTo(mainOrigin + _mainScroller._pos);
for (int8 i = 0; i < 4; i++) {
if (_enableFlags.get(i) && _menuHotspots[i].contains(pos))
return i;
}
return -1;
}
void MenuManager::process(uint32 deltatime) {
if (_mainScroller.update(deltatime)) {
_mainArea.moveTo(_menuOrigin + _mainScroller._pos);
for (int i = 0; i < 4; i++)
_menuHotspots[i].moveTo(_menuOrigin + Common::Point(_params.wxButs[i][1], _mainScroller._pos.y));
_redraw = true;
}
// Update button highlight animation frame
for (int i = 0; i < 4; i++)
if (_buttonAnim[i]->update(deltatime)) {
_mainFrames[i] = _buttonAnim[i]->_pos;
_redraw = true;
}
if (_redraw) {
_engine->getRenderManager()->clearMenuSurface();
redrawAll();
_redraw = false;
}
}
void MenuNemesis::redrawAll() {
redrawMain();
}
void MenuManager::redrawMain() {
// Draw menu background
_engine->getRenderManager()->blitSurfaceToMenu(_mainBack, _mainScroller._pos.x, _mainScroller._pos.y, 0);
// Draw buttons
if (_menuFocus.front() == kFocusMain)
for (int8 i = 0; i < 4; i++) {
if (_enableFlags.get(i) && (_mainFrames[i] >= 0)) {
if (_mainClicked == i)
_engine->getRenderManager()->blitSurfaceToMenu(_mainButtons[i][_params.clickedFrame], _params.wxButs[i][1], _mainScroller._pos.y, 0);
else
_engine->getRenderManager()->blitSurfaceToMenu(_mainButtons[i][_mainFrames[i]], _params.wxButs[i][1], _mainScroller._pos.y, 0);
}
}
_clean = false;
}
void MenuManager::setFocus(int8 currentFocus) {
_menuFocus.set(currentFocus);
assert(_menuFocus.size() <= 4);
}
MenuZGI::MenuZGI(ZVision *engine, const Common::Rect menuArea) :
MenuManager(engine, menuArea, zgiParams),
_itemsScroller(Common::Point(0, 0), Common::Point(_wSideMenuTab - _wSideMenu, 0), _sideMenuPeriod),
_magicScroller(Common::Point(-_wSideMenu, 0), Common::Point(-_wSideMenuTab, 0), _sideMenuPeriod),
_itemsOrigin(menuArea.left, menuArea.top),
_magicOrigin(menuArea.right, menuArea.top) {
_magicArea = Common::Rect(_magicOrigin + _magicScroller._pos, _wSideMenu, _hSideMenu);
_itemsArea = Common::Rect(_itemsOrigin + _itemsScroller._pos, _wSideMenu, _hSideMenu);
// Buffer main menu background
_engine->getRenderManager()->readImageToSurface("gmzau031.tga", _mainBack, false);
char buf[24];
for (int i = 0; i < 4; i++) {
// Buffer menu buttons
Common::sprintf_s(buf, "gmzmu%2.2x1.tga", i);
_engine->getRenderManager()->readImageToSurface(buf, _mainButtons[i][0], false);
Common::sprintf_s(buf, "gmznu%2.2x1.tga", i);
_engine->getRenderManager()->readImageToSurface(buf, _mainButtons[i][1], false);
}
for (int i = 1; i < 4; i++) {
// Buffer full menu backgrounds
Common::sprintf_s(buf, "gmzau%2.2x1.tga", i);
_engine->getRenderManager()->readImageToSurface(buf, _menuBack[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;
}
// Initialise focus sequence
setFocus(kFocusMain);
setFocus(kFocusMagic);
setFocus(kFocusItems);
setFocus(kFocusNone);
}
MenuZGI::~MenuZGI() {
for (int i = 0; i < 3; i++) {
_menuBack[i].free();
}
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 2; j++)
_mainButtons[i][j].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];
}
}
}
bool MenuZGI::inMenu(const Common::Point &pos) const {
return _menuTriggerArea.contains(pos) || (_menuFocus.front() != kFocusNone);
}
void MenuZGI::onMouseUp(const Common::Point &pos) {
if (inMenu(pos))
// _redraw = true;
switch (_menuFocus.front()) {
case kFocusItems:
if (_enableFlags.get(kItemsMenu)) {
int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots);
if (itemCount == 0)
itemCount = 20;
int i = mouseOverItem(pos, itemCount);
if (i != -1) {
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 kFocusMagic:
if (_enableFlags.get(kMagicMenu)) {
int i = mouseOverMagic(pos);
if (i != -1) {
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)
_engine->getScriptManager()->setStateValue(StateKey_Active_Spell, itemnum);
}
}
break;
case kFocusMain:
MenuManager::onMouseUp(pos);
break;
default:
break;
}
}
void MenuZGI::onMouseMove(const Common::Point &pos) {
if (!inMenu(pos)) {
_mainScroller.reset();
_magicScroller.reset();
_itemsScroller.reset();
}
// Set focus to topmost layer of menus that mouse is currently over
for (uint8 i = 0; i < _menuFocus.size(); i++) {
switch (_menuFocus[i]) {
case kFocusItems:
if (_itemsArea.contains(pos)) {
setFocus(kFocusItems);
i = _menuFocus.size() + 1;
}
break;
case kFocusMagic:
if (_magicArea.contains(pos)) {
setFocus(kFocusMagic);
i = _menuFocus.size() + 1;
}
break;
case kFocusMain:
if (_mainArea.contains(pos)) {
setFocus(kFocusMain);
i = _menuFocus.size() + 1;
}
break;
default:
setFocus(kFocusNone);
break;
}
}
_itemsScroller.setActive(_menuFocus.front() == kFocusItems);
_magicScroller.setActive(_menuFocus.front() == kFocusMagic);
if (_menuFocus.front() != kFocusNone) {
switch (_menuFocus.front()) {
case kFocusItems:
if (_enableFlags.get(kItemsMenu)) {
int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots);
if (itemCount == 0)
itemCount = 20;
else if (itemCount > 50)
itemCount = 50;
int lastItem = _mouseOnItem;
_mouseOnItem = mouseOverItem(pos, itemCount);
if (lastItem != _mouseOnItem)
if (_engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + _mouseOnItem) || _engine->getScriptManager()->getStateValue(StateKey_Inv_StartSlot + lastItem))
_redraw = true;
}
break;
case kFocusMagic:
if (_enableFlags.get(kMagicMenu)) {
int lastItem = _mouseOnItem;
_mouseOnItem = mouseOverMagic(pos);
if (lastItem != _mouseOnItem)
if (_engine->getScriptManager()->getStateValue(StateKey_Spell_1 + _mouseOnItem) || _engine->getScriptManager()->getStateValue(StateKey_Spell_1 + lastItem))
_redraw = true;
}
break;
case kFocusMain:
break;
}
}
MenuManager::onMouseMove(pos);
}
int MenuZGI::mouseOverItem(const Common::Point &pos, int itemCount) {
int itemWidth = (_wSideMenu - 28) / itemCount;
Common::Rect itemHotspot = Common::Rect(28, _hSideMenu);
itemHotspot.moveTo(_itemsOrigin + _itemsScroller._pos);
for (int i = 0; i < itemCount; i++) {
if (itemHotspot.contains(pos))
return i;
itemHotspot.translate(itemWidth, 0);
}
return -1;
}
int MenuZGI::mouseOverMagic(const Common::Point &pos) {
Common::Rect magicHotspot(28, _hSideMenu);
magicHotspot.moveTo(_magicOrigin + _magicScroller._pos);
magicHotspot.translate(28, 0); // Offset from end of menu
for (int i = 0; i < 12; i++) {
if (magicHotspot.contains(pos))
return i;
magicHotspot.translate(_magicWidth, 0);
}
return -1;
}
void MenuZGI::process(uint32 deltatime) {
if (_itemsScroller.update(deltatime)) {
_itemsArea.moveTo(_itemsOrigin + _itemsScroller._pos);
_redraw = true;
}
if (_magicScroller.update(deltatime)) {
_magicArea.moveTo(_magicOrigin + _magicScroller._pos);
_redraw = true;
}
MenuManager::process(deltatime);
}
void MenuZGI::redrawAll() {
if (MenuManager::inMenu())
for (int8 i = _menuFocus.size() - 1; i >= 0; i--)
switch (_menuFocus[i]) {
case kFocusItems:
if (_enableFlags.get(kItemsMenu)) {
redrawItems();
}
break;
case kFocusMagic:
if (_enableFlags.get(kMagicMenu)) {
redrawMagic();
}
break;
case kFocusMain:
redrawMain();
break;
default:
break;
}
}
void MenuZGI::redrawMagic() {
const int16 yOrigin = _menuArea.width();
_engine->getRenderManager()->blitSurfaceToMenu(_menuBack[kFocusMagic], yOrigin + _magicScroller._pos.x, 0, 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];
Common::sprintf_s(buf, "gmzwu%2.2x1.tga", curItemId);
_magic[i][0] = _engine->getRenderManager()->loadImage(buf, false);
Common::sprintf_s(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], yOrigin + _magicScroller._pos.x + 28 + _magicWidth * i, 0, 0);
else
_engine->getRenderManager()->blitSurfaceToMenu(*_magic[i][0], yOrigin + _magicScroller._pos.x + 28 + _magicWidth * i, 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;
}
}
_clean = false;
}
void MenuZGI::redrawItems() {
_engine->getRenderManager()->blitSurfaceToMenu(_menuBack[kFocusItems], _itemsScroller._pos.x, 0, 0);
int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots);
if (itemCount == 0)
itemCount = 20;
else if (itemCount > 50)
itemCount = 50;
int itemWidth = (_wSideMenu - 28) / itemCount;
for (int i = 0; i < itemCount; i++) {
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];
Common::sprintf_s(buf, "gmzwu%2.2x1.tga", curItemId);
_items[i][0] = _engine->getRenderManager()->loadImage(buf, false);
Common::sprintf_s(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], _itemsScroller._pos.x + itemWidth * i, 0, 0);
else
_engine->getRenderManager()->blitSurfaceToMenu(*_items[i][0], _itemsScroller._pos.x + itemWidth * 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;
}
}
_clean = false;
}
MenuNemesis::MenuNemesis(ZVision *engine, const Common::Rect menuArea) :
MenuManager(engine, menuArea, nemesisParams) {
// Buffer menu background image
_engine->getRenderManager()->readImageToSurface("bar.tga", _mainBack, false);
char buf[24];
for (int i = 0; i < 4; i++)
// Buffer menu buttons
for (int j = 0; j < 6; j++) {
Common::sprintf_s(buf, "butfrm%d%d.tga", i + 1, j);
_engine->getRenderManager()->readImageToSurface(buf, _mainButtons[i][j], false);
}
}
MenuNemesis::~MenuNemesis() {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 6; j++)
_mainButtons[i][j].free();
}
bool MenuNemesis::inMenu(const Common::Point &pos) const {
return _menuTriggerArea.contains(pos) || (_menuFocus.front() != kFocusNone);
}
void MenuNemesis::onMouseMove(const Common::Point &pos) {
// Trigger main menu scrolldown to get mouse over main trigger area
// Set focus to topmost layer of menus that mouse is currently over
if (_mainArea.contains(pos) || _menuTriggerArea.contains(pos))
setFocus(kFocusMain);
else
setFocus(kFocusNone);
MenuManager::onMouseMove(pos);
}
} // End of namespace ZVision

View File

@@ -0,0 +1,192 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_MENU_H
#define ZVISION_MENU_H
#include "common/array.h"
#include "common/bitarray.h"
#include "common/rect.h"
#include "graphics/surface.h"
#include "zvision/zvision.h"
#include "zvision/common/focus_list.h"
#include "zvision/common/scroller.h"
#include "zvision/scripting/script_manager.h"
namespace ZVision {
enum {
kMainMenuSave = 0,
kMainMenuLoad = 1,
kMainMenuPrefs = 2,
kMainMenuExit = 3,
kItemsMenu = 4,
kMagicMenu = 5
};
struct MenuParams {
int16 wxButs[4][2]; // Widths & X positions of main menu buttons; {Save, Restore, Prefs, Quit}
int16 wMain; // Width of main menu background
int8 idleFrame; // Frame to display of unselected main menu button
int8 activeFrame; // Frame to display of selected main menu button when mouse is down
int8 clickedFrame; // Frame to display of selected main menu button when mouse is down
Common::Point activePos; // Fully scrolled main menu position, relative to origin of menu area
Common::Point idlePos; // Fully retracted main menu position, relative to origin of menu area
int16 period; // Duration of main menu scrolldown
int16 triggerHeight; // Height of menu trigger area when inactive
int16 buttonPeriod; // Duration of main menu button animation
};
// NB - menu area is same width as working window.
static const MenuParams nemesisParams {
{ {120, -1}, {144, 120}, {128, 264}, {120, 392} },
512,
-1,
4,
5,
Common::Point(0, 0),
Common::Point(0, -32),
500,
2,
500
};
static const MenuParams zgiParams {
{ {135, 50}, {135, 185}, {135, 320}, {135, 455} },
580,
0,
1,
1,
Common::Point(30, 0),
Common::Point(30, -20),
250,
32,
0
};
class MenuManager {
public:
MenuManager(ZVision *engine, const Common::Rect menuArea, const MenuParams params);
virtual ~MenuManager();
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);
bool inMenu() const {
return _prevInMenu;
}
virtual bool inMenu(const Common::Point &pos) const {
return false;
} // For widescreen mod; used to suspend panning, tilting & scripting triggers when the mouse is within the working window but also in the menu.
void mainMouseDown(const Common::Point &pos); // Show clicked graphic under selected button
bool mainMouseMove(const Common::Point &pos); // return true if selected button has changed
void setEnable(uint16 flags);
uint16 getEnable() const {
return _menuBarFlag;
}
bool getEnable(uint8 flag) const {
return _enableFlags.get(flag);
}
protected:
virtual void redrawAll() {}
void redrawMain();
int mouseOverMain(const Common::Point &pos);
void setFocus(int8 currentFocus);
bool _prevInMenu = false;
bool _redraw = true;
int _mouseOnItem = -1;
static const uint8 _hMainMenu = 32;
int8 _mainClicked = -1;
ZVision *_engine;
const MenuParams _params;
uint16 _menuBarFlag;
const Common::Rect _menuArea;
const Common::Point _menuOrigin;
const Common::Rect _menuTriggerArea;
Graphics::Surface _mainBack;
Graphics::Surface _mainButtons[4][6];
Common::BitArray _enableFlags;
Common::Rect _mainArea;
Common::Rect _menuHotspots[4];
int8 _mainFrames[4]; // Frame to display of each main menu button; first row is currently displayed, 2nd row is backbuffer for idle animations
Scroller _mainScroller;
FocusList<int8> _menuFocus; // Order in which menus have most recently had focus; determines current mouse focus & order in which to redraw them.
bool _clean = false; // Whether or not to blank
LinearScroller *_buttonAnim[4];
};
class MenuZGI: public MenuManager {
public:
MenuZGI(ZVision *engine, Common::Rect menuArea);
~MenuZGI() override;
void onMouseMove(const Common::Point &pos) override; // NB Pos is in screen coordinates
void onMouseUp(const Common::Point &pos) override;
void process(uint32 deltaTimeInMillis) override;
bool inMenu(const Common::Point &pos) const override;
private:
void redrawAll() override;
Graphics::Surface _menuBack[3];
Graphics::Surface *_items[50][2];
uint _itemId[50];
Graphics::Surface *_magic[12][2];
uint _magicId[12];
// void redrawMain(bool hasFocus) override;
void redrawItems();
void redrawMagic();
int mouseOverItem(const Common::Point &pos, int itemCount);
int mouseOverMagic(const Common::Point &pos);
static const uint16 _hSideMenu = 32;
static const uint16 _wSideMenu = 600;
static const uint16 _wSideMenuTab = 20;
static const int16 _magicWidth = 47;
const int16 _sideMenuPeriod = 300;
Scroller _itemsScroller, _magicScroller;
const Common::Point _magicOrigin;
const Common::Point _itemsOrigin;
Common::Rect _magicArea;
Common::Rect _itemsArea;
};
class MenuNemesis: public MenuManager {
public:
MenuNemesis(ZVision *engine, Common::Rect menuArea);
~MenuNemesis() override;
void onMouseMove(const Common::Point &pos) override;
bool inMenu(const Common::Point &pos) const override;
private:
void redrawAll() override;
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,77 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_PUZZLE_H
#define ZVISION_PUZZLE_H
#include "common/list.h"
#include "common/ptr.h"
#include "zvision/scripting/actions.h"
namespace ZVision {
struct Puzzle {
Puzzle() : key(0), addedBySetState(false) {}
~Puzzle() {
for (auto & action : resultActions)
delete action;
}
/** 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

View File

@@ -0,0 +1,566 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/file.h"
#include "common/scummsys.h"
#include "common/str.h"
#include "common/textconsole.h"
#include "common/tokenizer.h"
#include "zvision/detection.h"
#include "zvision/zvision.h"
#include "zvision/scripting/actions.h"
#include "zvision/scripting/puzzle.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/controls/fist_control.h"
#include "zvision/scripting/controls/hotmov_control.h"
#include "zvision/scripting/controls/input_control.h"
#include "zvision/scripting/controls/lever_control.h"
#include "zvision/scripting/controls/paint_control.h"
#include "zvision/scripting/controls/push_toggle_control.h"
#include "zvision/scripting/controls/safe_control.h"
#include "zvision/scripting/controls/save_control.h"
#include "zvision/scripting/controls/slot_control.h"
#include "zvision/scripting/controls/titler_control.h"
namespace ZVision {
void ScriptManager::parseScrFile(const Common::Path &fileName, ScriptScope &scope) {
auto parse = [&](Common::File & file) {
while (!file.eos()) {
Common::String line = file.readLine();
if (file.err())
error("Error parsing scr file: %s", fileName.toString().c_str());
trimCommentsAndWhiteSpace(&line);
if (line.empty())
continue;
if (line.matchString("puzzle:*", true)) {
Puzzle *puzzle = new Puzzle();
if (sscanf(line.c_str(), "puzzle:%u", &(puzzle->key)) != 1) {
debugC(kDebugScript, "Malformed puzzle string: %s", line.c_str());
continue;
}
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);
}
}
};
Common::File mainFile;
Common::File auxFile;
Common::String auxFileName = fileName.toString();
replace(auxFileName, Common::String(".scr"), Common::String(".aux"));
debugC(1, kDebugFile, "Auxiliary filename %s", auxFileName.c_str());
Common::Path auxFilePath(auxFileName);
debugC(1, kDebugFile, "Auxiliary path %s", auxFilePath.toString().c_str());
if (!mainFile.open(fileName))
error("Script file not found: %s", fileName.toString().c_str());
else {
debugC(1, kDebugScript, "Parsing primary script file %s", fileName.toString().c_str());
parse(mainFile);
// TODO - add config option to disable/enable auxiliary scripting
if (auxFile.exists(auxFilePath)) {
debugC(1, kDebugFile, "Auxiliary script file found");
if (auxFile.open(auxFilePath)) {
debugC(1, kDebugScript, "Parsing auxiliary script file %s", auxFilePath.toString().c_str());
parse(auxFile);
} else
debugC(1, kDebugFile, "Unable to open auxiliary script file %s", auxFilePath.toString().c_str());
}
}
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, puzzle->key);
// WORKAROUNDS:
switch (_engine->getGameId()) {
case GID_NEMESIS:
// 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 (puzzle->key == 19398)
puzzle->resultActions.push_back(new ActionAssign(_engine, 11, "19397, 0"));
break;
case GID_GRANDINQUISITOR:
switch (puzzle->key) {
case 10836:
// WORKAROUND for bug #10604. If the player is looking at the
// cigar box when Antharia Jack returns to examine the lamp,
// pp1f_video_flag remains 1. Later, when the player returns
// to pick up the lantern, the game will try to play the
// cutscene again, but since that script has already been
// run the player gets stuck in a dark room instead. We have
// to add the assignment action to the front, or it won't be
// reached because changing the location terminates the script.
//
// Fixing it this way only keeps the bug from happening. It
// will not repair old savegames.
//
// Note that the bug only affects the DVD version. The CD
// version doesn't have a separate room for the cutscene.
if (_engine->getFeatures() & ADGF_DVD)
puzzle->resultActions.push_front(new ActionAssign(_engine, 11, "10803, 0"));
break;
// WORKAROUND for a script bug in Zork: Grand Inquisitor, room dc10.
// Background heartbeat sound effect never terminates upon location change.
case 2341:
case 2344:
case 17545:
puzzle->resultActions.push_front(new ActionKill(_engine, 11, "02310"));
break;
default:
break;
}
break;
default:
break;
}
} 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>());
// WORKAROUNDS
switch (_engine->getGameId()) {
case GID_NEMESIS:
// 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 (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);
}
break;
case GID_GRANDINQUISITOR:
// WORKAROUND for a script bug in Zork: Grand Inquisitor, room me2j
// (Closing the Time Tunnels). When the time tunnel is open the game
// shows a close-up of only the tunnel, instead of showing the entire
// booth. However, the scripts that draw the lever in its correct
// state do not test this flag, causing it to be drawn when it should
// not be. This fixes bug #6770.
if (key == 9536) {
Puzzle::CriteriaEntry entry;
entry.key = 9404; // me2j_time_tunnel_open
entry.criteriaOperator = Puzzle::EQUAL_TO;
entry.argumentIsAKey = false;
entry.argument = 0;
criteriaList.back().push_back(entry);
}
break;
default:
break;
}
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();
if (sscanf(token.c_str(), "[%u]", &(entry.key)) != 1) {
debugC(kDebugScript, "Malformed criteria ID string %s", token.c_str());
return false;
}
// 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('[')) {
if (sscanf(token.c_str(), "[%u]", &(entry.argument)) != 1) {
debugC(kDebugScript, "Malformed token string %s", token.c_str());
return false;
}
entry.argumentIsAKey = true;
} else {
if (sscanf(token.c_str(), "%u", &(entry.argument)) != 1) {
debugC(kDebugScript, "Malformed token string %s", token.c_str());
return false;
}
entry.argumentIsAKey = false;
}
// WORKAROUND for a script bug in Zork: Grand Inquisitor. If the
// fire timer is killed (e.g. by the inventory screen) with less
// than 10 units left, it will get stuck and never time out. We
// work around that by changing the condition from "greater than
// 10" to "greater than 0 but not 2 (the magic time-out value)".
//
// I have a sneaking suspicion that there may be other timer
// glitches like this, but this one makes the game unplayable
// and is easy to trigger.
if (_engine->getGameId() == GID_GRANDINQUISITOR && key == 17162) {
Puzzle::CriteriaEntry entry0;
entry0.key = 17161; // pe_fire
entry0.criteriaOperator = Puzzle::GREATER_THAN;
entry0.argumentIsAKey = false;
entry0.argument = 0;
criteriaList.back().push_back(entry0);
entry.criteriaOperator = Puzzle::NOT_EQUAL_TO;
entry.argument = 2;
}
criteriaList.back().push_back(entry);
line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
}
return true;
}
void ScriptManager::parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList, uint32 key) 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 within results block
while (!stream.eos() && !line.contains('}')) {
// Skip empty lines
if (line.empty()) {
line = stream.readLine();
trimCommentsAndWhiteSpace(&line);
line.toLowercase();
continue;
}
debugC(4, kDebugScript, "Result line: %s", line.c_str());
const char *chrs = line.c_str();
uint pos;
if (line.matchString("action:*", true))
pos = 6;
else if (line.matchString("event:*", true))
pos = 5;
else if (line.matchString("background:*", true))
pos = 10;
else
continue;
if (pos < line.size()) { // Stuff left
uint startpos = pos + 1; // first character after colon
// Scan for next colon or opening bracket
for (pos = startpos; pos < line.size(); pos++)
if (chrs[pos] == ':' || chrs[pos] == '(')
break;
debugC(4, kDebugScript, "startpos %d, pos %d, line.size %d", startpos, pos, line.size());
int32 slot = 11; // Non-setting default slot
Common::String args = "";
Common::String act(chrs + startpos, chrs + pos);
// Extract arguments, if any
if (pos < line.size()) {
startpos = pos + 1;
if (chrs[pos] == ':') {
for (pos = startpos; pos < line.size(); pos++)
if (chrs[pos] == '(')
break;
// Extract slotkey, if specified
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);
}
}
debugC(4, kDebugScript, "Action string: '%s', slot %d, arguments string '%s'", act.c_str(), slot, args.c_str());
// 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)) {
if (_engine->getGameId() == GID_GRANDINQUISITOR && key == 17761) {
// WORKAROUND for a script bug in Zork: Grand Inquisitor, room tp1e.
// Background looping sound effect continuously restarts on every game cycle.
// This is caused by resetting itself to zero as a result of itself.
// Simple fix is to simply not generate this result assignment action at all.
} else
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];
if (sscanf(line.c_str(), "control:%u %s {", &key, controlTypeBuffer) != 2) {
debugC(kDebugScript, "Malformed control string: %s", line.c_str());
return NULL;
}
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

View File

@@ -0,0 +1,912 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "common/algorithm.h"
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/hashmap.h"
#include "common/scummsys.h"
#include "common/stream.h"
#include "zvision/zvision.h"
#include "zvision/file/save_manager.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/graphics/cursors/cursor_manager.h"
#include "zvision/scripting/actions.h"
#include "zvision/scripting/menu.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/scripting/effects/timer_effect.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(bool restarted) {
if (restarted) {
_globalState.clear();
_globalStateFlags.clear();
}
cleanScriptScope(_universe);
cleanScriptScope(_world);
cleanScriptScope(_room);
cleanScriptScope(_nodeview);
_currentLocation.node = 0;
_currentLocation.world = 0;
_currentLocation.room = 0;
_currentLocation.view = 0;
if (restarted) {
for (auto &fx : _activeSideFx)
delete fx;
_activeSideFx.clear();
_referenceTable.clear();
switch (_engine->getGameId()) {
case GID_GRANDINQUISITOR:
// Bypass logo video
setStateValue(16966, 1);
// Ensure post-logo screen redraw is not inhibited in CD version
setStateValue(5813, 1);
// Bypass additional logo videos in DVD version
setStateValue(19810, 1);
setStateValue(19848, 1);
break;
case GID_NEMESIS:
default:
break;
}
}
parseScrFile("universe.scr", _universe);
changeLocation('g', 'a', 'r', 'y', 0);
_controlEvents.clear();
if (restarted)
_engine->loadSettings();
}
bool ScriptManager::changingLocation() const {
return _currentLocation != _nextLocation;
}
void ScriptManager::process(uint deltaTimeMillis) {
// If the location is changing, the script that did that may have
// triggered other scripts, so we give them all a few extra cycles to
// run. This fixes some missing scoring in ZGI, and quite
// possibly other minor glitches as well.
//
// Another idea would be to change if there are pending scripts
// in the exec queues, but that could cause this to hang
// indefinitely.
for (uint8 pass = 0; pass <= (changingLocation() ? _changeLocationExtraCycles : 0); pass++) {
updateNodes(pass == 0 ? deltaTimeMillis : 0);
debugC(5, kDebugLoop, "Script nodes updated");
if (!execScope(_nodeview))
break;
if (!execScope(_room))
break;
if (!execScope(_world))
break;
if (!execScope(_universe))
break;
}
updateControls(deltaTimeMillis);
if (changingLocation())
ChangeLocationReal(false);
}
bool ScriptManager::execScope(ScriptScope &scope) {
// Swap queues
PuzzleList *tmp = scope.execQueue;
scope.execQueue = scope.scopeQueue;
scope.scopeQueue = tmp;
scope.scopeQueue->clear();
for (auto &puzzle : scope.puzzles)
puzzle->addedBySetState = false;
switch (getStateValue(StateKey_ExecScopeStyle)) {
case 0: // ZGI
if (scope.procCount < 2)
for (auto &puzzle : scope.puzzles) {
if (!checkPuzzleCriteria(puzzle, scope.procCount))
return false;
}
else
for (auto &puzzle : (*scope.execQueue)) {
if (!checkPuzzleCriteria(puzzle, scope.procCount))
return false;
}
break;
case 1: // Nemesis
default:
for (auto &puzzle : scope.puzzles) {
if (!checkPuzzleCriteria(puzzle, scope.procCount))
return false;
}
break;
}
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 (auto &puzzle : scope.puzzles) {
Puzzle *puzzlePtr = puzzle;
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 (auto &criteria : puzzle->criteriaList) {
for (auto &entry : criteria)
referenceTableAddPuzzle(entry.key, ref);
}
}
}
void ScriptManager::updateNodes(uint deltaTimeMillis) {
// If process() returns true, it means the node can be deleted
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end();) {
if ((*fx)->process(deltaTimeMillis)) {
delete(*fx);
// Remove the node
fx = _activeSideFx.erase(fx);
}
else
++fx;
}
}
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 (auto &control : (*_activeControls)) {
if (control->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;
// WORKAROUNDS:
switch (_engine->getGameId()) {
case GID_NEMESIS:
switch (puzzle->key) {
case 16418:
// WORKAROUND for script bug in Zork Nemesis, room mc30 (Monastery Entry)
// Rumble sound effect should cease upon changing location to me10 (Hall of Masks),
// but this puzzle erroneously restarted it immediately after.
if(changingLocation())
return true;
break;
default:
break;
}
break;
default:
break;
}
bool criteriaMet = false;
for (auto &criteria : puzzle->criteriaList) {
criteriaMet = false;
for (auto &entry : criteria) {
// Get the value to compare against
int argumentValue;
if (entry.argumentIsAKey)
argumentValue = getStateValue(entry.argument);
else
argumentValue = entry.argument;
// Do the comparison
switch (entry.criteriaOperator) {
case Puzzle::EQUAL_TO:
criteriaMet = getStateValue(entry.key) == argumentValue;
break;
case Puzzle::NOT_EQUAL_TO:
criteriaMet = getStateValue(entry.key) != argumentValue;
break;
case Puzzle::GREATER_THAN:
criteriaMet = getStateValue(entry.key) > argumentValue;
break;
case Puzzle::LESS_THAN:
criteriaMet = getStateValue(entry.key) < argumentValue;
break;
default:
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) {
debugC(1, kDebugPuzzle, "Puzzle %u criteria passed. Executing its ResultActions", puzzle->key);
// Set the puzzle as completed
setStateValue(puzzle->key, 1);
for (auto &result : puzzle->resultActions) {
if (!result->execute())
return false;
}
}
return true;
}
void ScriptManager::cleanStateTable() {
for (auto entry = _globalState.begin(); entry != _globalState.end(); ++entry) {
// If the value is equal to zero, we can purge it since getStateValue()
// will return zero if _globalState doesn't contain a key
if (entry->_value == 0) {
// Remove the node
_globalState.erase(entry);
}
}
}
void ScriptManager::cleanScriptScope(ScriptScope &scope) {
scope.privQueueOne.clear();
scope.privQueueTwo.clear();
scope.scopeQueue = &scope.privQueueOne;
scope.execQueue = &scope.privQueueTwo;
for (auto &puzzle : scope.puzzles)
delete(puzzle);
scope.puzzles.clear();
for (auto &control : scope.controls)
delete(control);
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 (auto &control : (*_activeControls)) {
if (control->getKey() == key)
return control;
}
return nullptr;
}
void ScriptManager::focusControl(uint32 key) {
if (!_activeControls)
return;
if (_currentlyFocusedControl == key)
return;
for (auto &control : (*_activeControls)) {
uint32 controlKey = control->getKey();
if (controlKey == key)
control->focus();
else if (controlKey == _currentlyFocusedControl)
control->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 (auto &fx : _activeSideFx) {
if (fx->getKey() == key)
return fx;
}
return nullptr;
}
void ScriptManager::deleteSideFx(uint32 key) {
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end(); ++fx) {
if ((*fx)->getKey() == key) {
delete(*fx);
_activeSideFx.erase(fx);
break;
}
}
}
void ScriptManager::stopSideFx(uint32 key) {
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end(); ++fx) {
if ((*fx)->getKey() == key) {
bool ret = (*fx)->stop();
if (ret) {
delete(*fx);
_activeSideFx.erase(fx);
}
break;
}
}
}
void ScriptManager::killSideFx(uint32 key) {
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end(); ++fx) {
if ((*fx)->getKey() == key) {
(*fx)->kill();
delete(*fx);
_activeSideFx.erase(fx);
break;
}
}
}
void ScriptManager::killSideFxType(ScriptingEffect::ScriptingEffectType type) {
for (auto fx = _activeSideFx.begin(); fx != _activeSideFx.end();) {
if ((*fx)->getType() & type) {
(*fx)->kill();
delete(*fx);
fx = _activeSideFx.erase(fx);
} else {
++fx;
}
}
}
void ScriptManager::onMouseDown(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
debugC(1, kDebugMouse, "Mouse screen coordinates: %d, %d, background/script coordinates: %d, %d", screenSpacePos.x, screenSpacePos.y, backgroundImageSpacePos.x, backgroundImageSpacePos.y);
if (!_activeControls)
return;
for (auto control = _activeControls->reverse_begin(); control != _activeControls->end(); control--) {
if ((*control)->onMouseDown(screenSpacePos, backgroundImageSpacePos))
return;
}
}
void ScriptManager::onMouseUp(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (!_activeControls)
return;
for (auto control = _activeControls->reverse_begin(); control != _activeControls->end(); control--) {
if ((*control)->onMouseUp(screenSpacePos, backgroundImageSpacePos))
return;
}
}
bool ScriptManager::onMouseMove(const Common::Point &screenSpacePos, const Common::Point &backgroundImageSpacePos) {
if (!_activeControls)
return false;
for (auto control = _activeControls->reverse_begin(); control != _activeControls->end(); control--) {
if ((*control)->onMouseMove(screenSpacePos, backgroundImageSpacePos))
return true;
}
return false;
}
void ScriptManager::onKeyDown(Common::KeyState keyState) {
if (!_activeControls)
return;
for (auto &control : (*_activeControls)) {
if (control->onKeyDown(keyState))
return;
}
}
void ScriptManager::onKeyUp(Common::KeyState keyState) {
if (!_activeControls)
return;
for (auto &control : (*_activeControls)) {
if (control->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) {
debugC(1, kDebugScript, "\tPreparing to change location");
_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);
debugC(1, kDebugScript, "\tChanging location to: World %c, Room %c, Node %c, View %c, Offset %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); // Necessary to ensure proper redraw in certain locations, in particular the infinite corridor in Zork Grand Inquisitor (room th20)
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->getMenuManager()->setEnable(0xFFFF);
TransitionLevel level = NONE;
Common::Path filePath;
if (_nextLocation.world != _currentLocation.world)
level = WORLD;
else if (_nextLocation.room != _currentLocation.room)
level = ROOM;
else if (_nextLocation.node != _currentLocation.node)
level = NODE;
else if (_nextLocation.view != _currentLocation.view)
level = VIEW;
switch (level) {
case WORLD:
cleanScriptScope(_world);
filePath = Common::Path(Common::String::format("%c.scr", _nextLocation.world));
parseScrFile(filePath, _world);
// fall through
case ROOM:
cleanScriptScope(_room);
filePath = Common::Path(Common::String::format("%c%c.scr", _nextLocation.world, _nextLocation.room));
parseScrFile(filePath, _room);
// fall through
case NODE:
case VIEW:
cleanScriptScope(_nodeview);
filePath = Common::Path(Common::String::format("%c%c%c%c.scr", _nextLocation.world, _nextLocation.room, _nextLocation.node, _nextLocation.view));
parseScrFile(filePath, _nodeview);
addPuzzlesToReferenceTable(_world);
addPuzzlesToReferenceTable(_room);
addPuzzlesToReferenceTable(_nodeview);
break;
case NONE:
default:
break;
}
_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")
level = WORLD;
if (level != NONE)
_currentLocation = _nextLocation;
switch (level) {
case WORLD:
execScope(_world);
// fall through
case ROOM:
execScope(_room);
// fall through
case NODE:
case VIEW:
execScope(_nodeview);
break;
case NONE:
default:
break;
}
_engine->getRenderManager()->checkBorders();
_engine->onMouseMove(); // Trigger a pseudo mouse movement to change cursor if we enter the new location with it already over a hotspot
debugC(1, kDebugScript, "\tLocation change complete");
}
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 (auto &fx : _activeSideFx)
fx->serialize(stream);
stream->writeUint32BE(MKTAG('F', 'L', 'A', 'G'));
int32 slots = _engine->getGameId() == GID_NEMESIS ? 31000 : 21000;
// Original games use key values up to 29500 and 19737, respectively
// Values 30001~31000 and 20001~21000 are now set aside for auxiliary scripting to add extra directional audio effects.
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 (auto &fx : _activeSideFx)
delete fx;
_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) {
auto event = _controlEvents.begin();
while (event != _controlEvents.end()) {
if ((*event).type == type)
event = _controlEvents.erase(event);
else
event++;
}
}
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

View File

@@ -0,0 +1,412 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_SCRIPT_MANAGER_H
#define ZVISION_SCRIPT_MANAGER_H
#include "common/events.h"
#include "common/hashmap.h"
#include "common/queue.h"
#include "zvision/scripting/control.h"
#include "zvision/scripting/puzzle.h"
#include "zvision/scripting/scripting_effect.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, // 0 = Windows, !0 = DOS
StateKey_InstallLevel = 67,
StateKey_CountryCode = 68,
StateKey_CPU = 69, // !1 = 486, 1 = i586/Pentium
StateKey_MovieCursor = 70,
StateKey_NoTurnAnim = 71,
StateKey_WIN958 = 72, // 0 = high system RAM, !0 = low system RAM (<8MB)
StateKey_ShowErrorDlg = 73,
StateKey_DebugCheats = 74,
StateKey_JapanFonts = 75,
StateKey_ExecScopeStyle = 76, // 0 = ZGI, 1 = Nemesis
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;
const uint8 _changeLocationExtraCycles = 16;
uint32 _currentlyFocusedControl;
public:
enum TransitionLevel {
NONE,
VIEW,
NODE,
ROOM,
WORLD
};
void initialize(bool restarted = false);
void process(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);
bool changingLocation() const;
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);
/**
* Check a puzzle's criteria; execute its actions and set its state to 1 if these critera are met.
* Will not check or execute if:
* Puzzle is disabled
* Puzzle has already triggered and has a state value of 1
* procCount has reached zero AND do_me_now is not set
*
* @param puzzle puzzle to check
* @param counter procCount from this puzzle's scope container
* Returns true if OK to keep calling this function this frame; false if we should break and start next frame (only used by RestoreGame action)
*/
bool checkPuzzleCriteria(Puzzle *puzzle, uint counter);
void cleanStateTable(); // Set all global state values to zero
void cleanScriptScope(ScriptScope &scope); // Resets everything in this scope, all lists empty, procCount to zero.
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::Path &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
* @param key Puzzle key (for workarounds)
* @return Created Results object
*/
void parseResults(Common::SeekableReadStream &stream, Common::List<ResultAction *> &actionList, uint32 key) 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);
};
/**
* Instances of this polymorphic class function either as a store of a single value, or as a "slot" that returns a StateValue
*
* @param line The line initially read
* @param slotValue A text string containing a number, which may be enclosed within square braces.
* If square braces are not present, getValue() will return slotValue.
* If square braces are present, getValue() will return the StateValue to which slotValue is the key.
*
* Once instantiated, the value and nature of slotValue may not be changed.
*/
class ValueSlot {
public:
ValueSlot(ScriptManager *scriptManager, const char *slotValue);
int16 getValue();
private:
int16 _value;
bool _slot;
ScriptManager *_scriptManager;
};
} // End of namespace ZVision
#endif

View File

@@ -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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#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