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

4
engines/saga/POTFILES Normal file
View File

@@ -0,0 +1,4 @@
engines/saga/detection_tables.h
engines/saga/metaengine.cpp
engines/saga/music.cpp
engines/saga/saga.cpp

1275
engines/saga/actor.cpp Normal file

File diff suppressed because it is too large Load Diff

688
engines/saga/actor.h Normal file
View File

@@ -0,0 +1,688 @@
/* 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/>.
*
*/
// Actor management module header file
#ifndef SAGA_ACTOR_H
#define SAGA_ACTOR_H
#include "common/savefile.h"
#include "saga/sprite.h"
#include "saga/itedata.h"
#include "saga/saga.h"
#include "saga/font.h"
namespace Saga {
class HitZone;
//#define ACTOR_DEBUG 1 //only for actor pathfinding debug!
#define ACTOR_BARRIERS_MAX 16
#define ACTOR_MAX_STEPS_COUNT 32
#define ACTOR_DIALOGUE_HEIGHT 100
#define ACTOR_LMULT 4
#define ACTOR_SPEED 72
#define ACTOR_CLIMB_SPEED 8
#define ACTOR_COLLISION_WIDTH 32
#define ACTOR_COLLISION_HEIGHT 8
#define ACTOR_DIRECTIONS_COUNT 4 // for ActorFrameSequence
#define ACTOR_SPEECH_STRING_MAX 16 // speech const
#define ACTOR_SPEECH_ACTORS_MAX 8
#define ACTOR_DRAGON_TURN_MOVES 4
#define ACTOR_DRAGON_INDEX 133
#define ACTOR_NO_ENTRANCE -1
#define ACTOR_EXP_KNOCK_RIF 24
#define PATH_NODE_EMPTY -1
#define ACTOR_INHM_SIZE 228
enum ActorDirections {
kDirectionRight = 0,
kDirectionLeft = 1,
kDirectionUp = 2,
kDirectionDown = 3
};
enum ActorActions {
kActionWait = 0,
kActionWalkToPoint = 1,
kActionWalkToLink = 2,
kActionWalkDir = 3,
kActionSpeak = 4,
kActionAccept = 5,
kActionStoop = 6,
kActionLook = 7,
kActionCycleFrames = 8,
kActionPongFrames = 9,
kActionFreeze = 10,
kActionFall = 11,
kActionClimb = 12
};
enum ActorFrameIds {
//ITE
kFrameITEStand = 0,
kFrameITEWalk = 1,
kFrameITESpeak = 2,
kFrameITEGive = 3,
kFrameITEGesture = 4,
kFrameITEWait = 5,
kFrameITEPickUp = 6,
kFrameITELook = 7,
//IHNM
kFrameIHNMStand = 0,
kFrameIHNMSpeak = 1,
kFrameIHNMWait = 2,
kFrameIHNMGesture = 3,
kFrameIHNMWalk = 4
};
enum SpeechFlags {
kSpeakNoAnimate = 1,
kSpeakAsync = 2,
kSpeakSlow = 4,
kSpeakForceText = 8
};
enum ActorFrameTypes {
kFrameStand,
kFrameWalk,
kFrameSpeak,
kFrameGive,
kFrameGesture,
kFrameWait,
kFramePickUp,
kFrameLook
};
// Lookup table to convert 8 cardinal directions to 4
static const int actorDirectionsLUT[8] = {
kDirectionUp, // kDirUp
kDirectionRight, // kDirUpRight
kDirectionRight, // kDirRight
kDirectionRight, // kDirDownRight
kDirectionDown, // kDirDown
kDirectionLeft, // kDirDownLeft
kDirectionLeft, // kDirLeft
kDirectionLeft // kDirUpLeft
};
enum ActorFlagsEx {
kActorNoCollide = (1 << 0),
kActorNoFollow = (1 << 1),
kActorCollided = (1 << 2),
kActorBackwards = (1 << 3),
kActorContinuous = (1 << 4),
kActorFinalFace = (1 << 5),
kActorFinishLeft = ((1 << 5) | (kDirLeft << 6)),
kActorFinishRight = ((1 << 5) | (kDirRight << 6)),
kActorFinishUp = ((1 << 5) | (kDirUp << 6)),
kActorFinishDown = ((1 << 5) | (kDirDown << 6)),
kActorFacingMask = (0xf << 5),
kActorRandom = (1 << 10)
};
enum PathCellType {
kPathCellEmpty = -1,
//kDirUp = 0 .... kDirUpLeft = 7
kPathCellBarrier = 0x57
};
enum DragonMoveTypes {
kDragonMoveUpLeft = 0,
kDragonMoveUpRight = 1,
kDragonMoveDownLeft = 2,
kDragonMoveDownRight = 3,
kDragonMoveUpLeft_Left = 4,
kDragonMoveUpLeft_Right = 5,
kDragonMoveUpRight_Left = 6,
kDragonMoveUpRight_Right = 7,
kDragonMoveDownLeft_Left = 8,
kDragonMoveDownLeft_Right = 9,
kDragonMoveDownRight_Left = 10,
kDragonMoveDownRight_Right = 11,
kDragonMoveInvalid = 12
};
struct PathDirectionData {
int8 direction;
int32 x;
int32 y;
};
struct ActorFrameRange {
int frameIndex;
int frameCount;
};
struct ActorFrameSequence {
ActorFrameRange directions[ACTOR_DIRECTIONS_COUNT];
};
typedef Common::Array<ActorFrameSequence> ActorFrameSequences;
uint pathLine(PointList &pointList, uint idx, const Point &point1, const Point &point2);
struct Location {
int32 x; // logical coordinates
int32 y; //
int32 z; //
Location() {
x = y = z = 0;
}
void saveState(Common::OutSaveFile *out) {
out->writeSint32LE(x);
out->writeSint32LE(y);
out->writeSint32LE(z);
}
void loadState(Common::InSaveFile *in) {
x = in->readSint32LE();
y = in->readSint32LE();
z = in->readSint32LE();
}
int distance(const Location &location) const {
return MAX(ABS(x - location.x), ABS(y - location.y));
}
int32 &u() {
return x;
}
int32 &v() {
return y;
}
int32 u() const {
return x;
}
int32 v() const {
return y;
}
int32 uv() const {
return u() + v();
}
void delta(const Location &location, Location &result) const {
result.x = x - location.x;
result.y = y - location.y;
result.z = z - location.z;
}
void addXY(const Location &location) {
x += location.x;
y += location.y;
}
void add(const Location &location) {
x += location.x;
y += location.y;
z += location.z;
}
void fromScreenPoint(const Point &screenPoint) {
x = (screenPoint.x * ACTOR_LMULT);
y = (screenPoint.y * ACTOR_LMULT);
z = 0;
}
void toScreenPointXY(Point &screenPoint) const {
screenPoint.x = x / ACTOR_LMULT;
screenPoint.y = y / ACTOR_LMULT;
}
void toScreenPointUV(Point &screenPoint) const {
screenPoint.x = u();
screenPoint.y = v();
}
void toScreenPointXYZ(Point &screenPoint) const {
screenPoint.x = x / ACTOR_LMULT;
screenPoint.y = y / ACTOR_LMULT - z;
}
void fromStream(Common::ReadStream &stream) {
x = stream.readUint16LE();
y = stream.readUint16LE();
z = stream.readUint16LE();
}
};
class CommonObjectData {
public:
//constant
int32 _index; // index in local array
uint16 _id; // object id
int32 _scriptEntrypointNumber; // script entrypoint number
//variables
uint16 _flags; // initial flags
int32 _nameIndex; // index in name string list
int32 _sceneNumber; // scene
int32 _spriteListResourceId; // sprite list resource id
Location _location; // logical coordinates
Point _screenPosition; // screen coordinates
int32 _screenDepth; //
int32 _screenScale; //
void saveState(Common::OutSaveFile *out) {
out->writeUint16LE(_flags);
out->writeSint32LE(_nameIndex);
out->writeSint32LE(_sceneNumber);
out->writeSint32LE(_spriteListResourceId);
_location.saveState(out);
out->writeSint16LE(_screenPosition.x);
out->writeSint16LE(_screenPosition.y);
out->writeSint32LE(_screenDepth);
out->writeSint32LE(_screenScale);
}
void loadState(Common::InSaveFile *in) {
_flags = in->readUint16LE();
_nameIndex = in->readSint32LE();
_sceneNumber = in->readSint32LE();
_spriteListResourceId = in->readSint32LE();
_location.loadState(in);
_screenPosition.x = in->readSint16LE();
_screenPosition.y = in->readSint16LE();
_screenDepth = in->readSint32LE();
_screenScale = in->readSint32LE();
}
CommonObjectData() {
_index = 0;
_id = 0;
_scriptEntrypointNumber = 0;
_flags = 0;
_nameIndex = 0;
_sceneNumber = 0;
_spriteListResourceId = 0;
_screenDepth = 0;
_screenScale = 0;
}
};
typedef CommonObjectData *CommonObjectDataPointer;
typedef Common::List<CommonObjectDataPointer> CommonObjectOrderList;
class ObjectData: public CommonObjectData {
public:
//constant
uint16 _interactBits;
ObjectData() {
_interactBits = 0;
}
};
typedef Common::Array<ObjectData> ObjectDataArray;
class ActorData: public CommonObjectData {
public:
//constant
SpriteList _spriteList; // sprite list data
ActorFrameSequences *_frames; // Actor's frames
ActorFrameSequences _framesContainer; // Actor's frames
int _frameListResourceId; // Actor's frame list resource id
byte _speechColor; // Actor dialogue color
//
bool _inScene;
//variables
uint16 _actorFlags; // dynamic flags
int32 _currentAction; // ActorActions type
int32 _facingDirection; // orientation
int32 _actionDirection;
int32 _actionCycle;
uint16 _targetObject;
const HitZone *_lastZone;
int32 _cycleFrameSequence;
uint8 _cycleDelay;
uint8 _cycleTimeCount;
uint8 _cycleFlags;
int16 _fallVelocity;
int16 _fallAcceleration;
int16 _fallPosition;
uint8 _dragonBaseFrame;
uint8 _dragonStepCycle;
uint8 _dragonMoveType;
int32 _frameNumber; // current frame number
ByteArray _tileDirections;
Common::Array<Point> _walkStepsPoints;
int32 _walkStepsCount;
int32 _walkStepIndex;
Location _finalTarget;
Location _partialTarget;
int32 _walkFrameSequence;
public:
ActorData();
void saveState(Common::OutSaveFile *out);
void loadState(uint32 version, Common::InSaveFile *in);
void cycleWrap(int cycleLimit);
void addWalkStepPoint(const Point &point);
bool shareFrames() {
return ((_frames != NULL) && (_frames != &_framesContainer));
}
};
typedef Common::Array<ActorData> ActorDataArray;
struct ProtagStateData {
ActorFrameSequences _frames; // Actor's frames
};
struct SpeechData {
int speechColor[ACTOR_SPEECH_ACTORS_MAX];
int outlineColor[ACTOR_SPEECH_ACTORS_MAX];
int speechFlags;
const char *strings[ACTOR_SPEECH_STRING_MAX];
Rect speechBox;
Rect drawRect;
int stringsCount;
int slowModeCharIndex;
uint16 actorIds[ACTOR_SPEECH_ACTORS_MAX];
int actorsCount;
int sampleResourceId;
bool playing;
int playingTime;
SpeechData() {
for (uint i = 0; i < ACTOR_SPEECH_ACTORS_MAX; i++) {
speechColor[i] = 0;
outlineColor[i] = 0;
}
speechFlags = 0;
for (uint i = 0; i < ACTOR_SPEECH_ACTORS_MAX; i++) {
strings[i] = nullptr;
}
// speechBox is initialized by Rect constructor
// drawRect is initialized by Rect constructor
stringsCount = 0;
slowModeCharIndex = 0;
for (uint i = 0; i < ACTOR_SPEECH_ACTORS_MAX; i++) {
actorIds[i] = 0;
}
actorsCount = 0;
sampleResourceId = 0;
playing = false;
playingTime = 0;
}
FontEffectFlags getFontFlags(int i) {
if (outlineColor[i] != 0) {
return kFontOutline;
} else {
return kFontNormal;
}
}
};
typedef int (*CompareFunction) (const CommonObjectDataPointer& a, const CommonObjectDataPointer& b);
class Actor {
friend class IsoMap;
friend class SagaEngine;
friend class Puzzle;
public:
Actor(SagaEngine *vm);
~Actor();
void cmdActorWalkTo(int argc, const char **argv);
bool validActorId(uint16 id) {
return (id == ID_PROTAG) || ((id >= objectIndexToId(kGameObjectActor, 0)) && (id < objectIndexToId(kGameObjectActor, _actors.size())));
}
int actorIdToIndex(uint16 id) { return (id == ID_PROTAG) ? 0 : objectIdToIndex(id); }
uint16 actorIndexToId(int index) { return (index == 0) ? ID_PROTAG : objectIndexToId(kGameObjectActor, index); }
ActorData *getActor(uint16 actorId);
// clarification: Obj - means game object, such Hat, Spoon etc, Object - means Actor,Obj,HitZone,StepZone
bool validObjId(uint16 id) { return (id >= objectIndexToId(kGameObjectObject, 0)) && (id < objectIndexToId(kGameObjectObject, _objs.size())); }
int objIdToIndex(uint16 id) { return objectIdToIndex(id); }
uint16 objIndexToId(int index) { return objectIndexToId(kGameObjectObject, index); }
ObjectData *getObj(uint16 objId);
int getObjectScriptEntrypointNumber(uint16 id) {
int objectType;
objectType = objectTypeId(id);
if (!(objectType & (kGameObjectObject | kGameObjectActor))) {
error("Actor::getObjectScriptEntrypointNumber wrong id 0x%X", id);
}
return (objectType == kGameObjectObject) ? getObj(id)->_scriptEntrypointNumber : getActor(id)->_scriptEntrypointNumber;
}
int getObjectFlags(uint16 id) {
int objectType;
objectType = objectTypeId(id);
if (!(objectType & (kGameObjectObject | kGameObjectActor))) {
error("Actor::getObjectFlags wrong id 0x%X", id);
}
return (objectType == kGameObjectObject) ? getObj(id)->_flags : getActor(id)->_flags;
}
void direct(int msec);
void drawActors();
void updateActorsScene(int actorsEntrance); // calls from scene loading to update Actors info
void drawSpeech();
#ifdef ACTOR_DEBUG
void drawPathTest();
#endif
uint16 hitTest(const Point &testPoint, bool skipProtagonist);
void takeExit(uint16 actorId, const HitZone *hitZone);
bool actorEndWalk(uint16 actorId, bool recurse);
bool actorWalkTo(uint16 actorId, const Location &toLocation);
int getFrameType(ActorFrameTypes frameType);
ActorFrameRange *getActorFrameRange(uint16 actorId, int frameType);
void actorFaceTowardsPoint(uint16 actorId, const Location &toLocation);
void actorFaceTowardsObject(uint16 actorId, uint16 objectId);
void realLocation(Location &location, uint16 objectId, uint16 walkFlags);
// speech
void actorSpeech(uint16 actorId, const char **strings, int stringsCount, int sampleResourceId, int speechFlags);
void nonActorSpeech(const Common::Rect &box, const char **strings, int stringsCount, int sampleResourceId, int speechFlags);
void simulSpeech(const char *string, uint16 *actorIds, int actorIdsCount, int speechFlags, int sampleResourceId);
void setSpeechColor(int speechColor, int outlineColor) {
_activeSpeech.speechColor[0] = speechColor;
_activeSpeech.outlineColor[0] = outlineColor;
}
void abortAllSpeeches();
void abortSpeech();
bool isSpeaking() {
return _activeSpeech.stringsCount > 0;
}
int isForcedTextShown() {
return _activeSpeech.speechFlags & kSpeakForceText;
}
void saveState(Common::OutSaveFile *out);
void loadState(Common::InSaveFile *in);
void setProtagState(int state);
int getProtagState() { return _protagState; }
void loadActorList(int protagonistIdx, int actorCount, int actorsResourceID,
int protagStatesCount, int protagStatesResourceID);
void loadObjList(int objectCount, int objectsResourceID);
protected:
friend class Script;
void loadActorResources(ActorData *actor);
void loadFrameList(int frameListResourceId, ActorFrameSequences &frames);
private:
void stepZoneAction(ActorData *actor, const HitZone *hitZone, bool exit, bool stopped);
void loadActorSpriteList(ActorData *actor);
void drawOrderListAdd(const CommonObjectDataPointer& element, CompareFunction compareFunction);
void createDrawOrderList();
bool calcScreenPosition(CommonObjectData *commonObjectData);
bool getSpriteParams(CommonObjectData *commonObjectData, int &frameNumber, SpriteList *&spriteList);
bool followProtagonist(ActorData *actor);
void findActorPath(ActorData *actor, const Point &fromPoint, const Point &toPoint);
void handleSpeech(int msec);
void handleActions(int msec, bool setup);
bool validPathCellPoint(const Point &testPoint) {
return !((testPoint.x < 0) || (testPoint.x >= _xCellCount) ||
(testPoint.y < 0) || (testPoint.y >= _yCellCount));
}
void setPathCell(const Point &testPoint, int8 value) {
#ifdef ACTOR_DEBUG
if (!validPathCellPoint(testPoint)) {
error("Actor::setPathCell wrong point");
}
#endif
_pathCell[testPoint.x + testPoint.y * _xCellCount] = value;
}
int8 getPathCell(const Point &testPoint) {
#ifdef ACTOR_DEBUG
if (!validPathCellPoint(testPoint)) {
error("Actor::getPathCell wrong point");
}
#endif
return _pathCell[testPoint.x + testPoint.y * _xCellCount];
}
bool scanPathLine(const Point &point1, const Point &point2);
int fillPathArray(const Point &fromPoint, const Point &toPoint, Point &bestPoint);
void setActorPath(ActorData *actor, const Point &fromPoint, const Point &toPoint);
void pathToNode();
void condenseNodeList();
void removeNodes();
void nodeToPath();
void removePathPoints();
bool validFollowerLocation(const Location &location);
void moveDragon(ActorData *actor);
protected:
//constants
ActorDataArray _actors;
ObjectDataArray _objs;
SagaEngine *_vm;
ResourceContext *_actorContext;
int _lastTickMsec;
CommonObjectOrderList _drawOrderList;
//variables
public:
ActorData *_centerActor;
ActorData *_protagonist;
int _handleActionDiv;
Rect _speechBoxScript;
StringsTable _objectsStrings;
StringsTable _actorsStrings;
protected:
SpeechData _activeSpeech;
int _protagState;
bool _dragonHunt;
private:
Common::Array<ProtagStateData> _protagStates;
//path stuff
struct PathNode {
Point point;
int link;
PathNode() : link(0) {}
PathNode(const Point &p) : point(p), link(0) {}
PathNode(const Point &p, int l) : point(p), link(l) {}
};
typedef Common::Array<PathNode> PathNodeList;
Rect _barrierList[ACTOR_BARRIERS_MAX];
int _barrierCount;
Common::Array<int8> _pathCell;
int _xCellCount;
int _yCellCount;
Rect _pathRect;
PointList _pathList;
uint _pathListIndex;
PathNodeList _pathNodeList;
public:
#ifdef ACTOR_DEBUG
#ifndef SAGA_DEBUG
#error You must also define SAGA_DEBUG
#endif
//path debug - use with care
struct DebugPoint {
Point point;
byte color;
DebugPoint() : color(0) {}
DebugPoint(const Point &p, byte c): point(p), color(c) {}
};
Common::Array<DebugPoint> _debugPoints;
uint _debugPointsCount;
// we still need this trick to speedup debug points addition
void addDebugPoint(const Point &point, byte color) {
if (_debugPointsCount < _debugPoints.size()) {
_debugPoints[_debugPointsCount].point = point;
_debugPoints[_debugPointsCount].color = color;
} else {
_debugPoints.push_back(DebugPoint(point, color));
}
++_debugPointsCount;
}
#endif
};
} // End of namespace Saga
#endif

660
engines/saga/actor_path.cpp Normal file
View File

@@ -0,0 +1,660 @@
/* 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 "saga/saga.h"
#include "saga/actor.h"
#include "saga/objectmap.h"
#include "saga/scene.h"
namespace Saga {
static const PathDirectionData pathDirectionLUT[8][3] = {
{ { 0, 0, -1 }, { 7, -1, -1 }, { 4, 1, -1 } },
{ { 1, 1, 0 }, { 4, 1, -1 }, { 5, 1, 1 } },
{ { 2, 0, 1 }, { 5, 1, 1 }, { 6, -1, 1 } },
{ { 3, -1, 0 }, { 6, -1, 1 }, { 7, -1, -1 } },
{ { 0, 0, -1 }, { 1, 1, 0 }, { 4, 1, -1 } },
{ { 1, 1, 0 }, { 2, 0, 1 }, { 5, 1, 1 } },
{ { 2, 0, 1 }, { 3, -1, 0 }, { 6, -1, 1 } },
{ { 3, -1, 0 }, { 0, 0, -1 }, { 7, -1, -1 } }
};
static const int pathDirectionLUT2[8][2] = {
{ 0, -1 },
{ 1, 0 },
{ 0, 1 },
{ -1, 0 },
{ 1, -1 },
{ 1, 1 },
{ -1, 1 },
{ -1, -1 }
};
inline int16 int16Compare(int16 i1, int16 i2) {
return ((i1) > (i2) ? 1 : ((i1) < (i2) ? -1 : 0));
}
inline int16 quickDistance(const Point &point1, const Point &point2, int16 compressX) {
Point delta;
delta.x = ABS(point1.x - point2.x) / compressX;
delta.y = ABS(point1.y - point2.y);
return ((delta.x < delta.y) ? (delta.y + delta.x / 2) : (delta.x + delta.y / 2));
}
inline void calcDeltaS(const Point &point1, const Point &point2, Point &delta, Point &s) {
delta.x = point2.x - point1.x;
if (delta.x == 0) {
s.x = 0;
} else {
if (delta.x > 0) {
s.x = 1;
} else {
s.x = -1;
delta.x = -delta.x;
}
}
delta.y = point2.y - point1.y;
if (delta.y == 0) {
s.y = 0;
} else {
if (delta.y > 0) {
s.y = 1;
} else {
s.y = -1;
delta.y = -delta.y;
}
}
}
void Actor::findActorPath(ActorData *actor, const Point &fromPoint, const Point &toPoint) {
Point iteratorPoint;
Point bestPoint;
int maskType;
int i;
Rect intersect;
#ifdef ACTOR_DEBUG
_debugPointsCount = 0;
#endif
// WORKAROUND for bug #5793. Path finding in IHNM is a bit buggy
// compared to the original, which occasionally leads to the player
// leaving the room instead of interacting with an object. So far, no
// one has figured out how to fix this properly. As a temporary [*]
// solution, we try to fix this on a case-by-case basis.
//
// The workaround is to assume that the player wants to stay in the
// room, unless he or she explicitly clicked on an exit zone.
//
// [*] And there is nothing more permanent than a temporary solution...
bool pathFindingWorkaround = false;
if (_vm->getGameId() == GID_IHNM) {
int chapter = _vm->_scene->currentChapterNumber();
int scene = _vm->_scene->currentSceneNumber();
// Ellen, in the room with the monitors.
if (chapter == 3 && scene == 54)
pathFindingWorkaround = true;
// Nimdok in the recovery room
if (chapter == 4 && scene == 71)
pathFindingWorkaround = true;
}
int hitZoneIndex;
const HitZone *hitZone;
bool restrictToRoom = false;
if (pathFindingWorkaround) {
restrictToRoom = true;
hitZoneIndex = _vm->_scene->_actionMap->hitTest(toPoint);
if (hitZoneIndex != -1) {
hitZone = _vm->_scene->_actionMap->getHitZone(hitZoneIndex);
if (hitZone->getFlags() & kHitZoneExit) {
restrictToRoom = false;
}
}
}
actor->_walkStepsCount = 0;
if (fromPoint == toPoint) {
actor->addWalkStepPoint(toPoint);
return;
}
for (iteratorPoint.y = 0; iteratorPoint.y < _yCellCount; iteratorPoint.y++) {
for (iteratorPoint.x = 0; iteratorPoint.x < _xCellCount; iteratorPoint.x++) {
if (_vm->_scene->validBGMaskPoint(iteratorPoint)) {
maskType = _vm->_scene->getBGMaskType(iteratorPoint);
setPathCell(iteratorPoint, _vm->_scene->getDoorState(maskType) ? kPathCellBarrier : kPathCellEmpty);
if (restrictToRoom) {
hitZoneIndex = _vm->_scene->_actionMap->hitTest(iteratorPoint);
if (hitZoneIndex != -1) {
hitZone = _vm->_scene->_actionMap->getHitZone(hitZoneIndex);
if (hitZone->getFlags() & kHitZoneExit) {
setPathCell(iteratorPoint, kPathCellBarrier);
}
}
}
} else {
setPathCell(iteratorPoint, kPathCellBarrier);
}
}
}
for (i = 0; i < _barrierCount; i++) {
intersect.left = MAX(_pathRect.left, _barrierList[i].left);
intersect.top = MAX(_pathRect.top, _barrierList[i].top);
intersect.right = MIN(_pathRect.right, _barrierList[i].right);
intersect.bottom = MIN(_pathRect.bottom, _barrierList[i].bottom);
for (iteratorPoint.y = intersect.top; iteratorPoint.y < intersect.bottom; iteratorPoint.y++) {
for (iteratorPoint.x = intersect.left; iteratorPoint.x < intersect.right; iteratorPoint.x++) {
setPathCell(iteratorPoint, kPathCellBarrier);
}
}
}
#ifdef ACTOR_DEBUG
for (iteratorPoint.y = 0; iteratorPoint.y < _yCellCount; iteratorPoint.y++) {
for (iteratorPoint.x = 0; iteratorPoint.x < _xCellCount; iteratorPoint.x++) {
if (getPathCell(iteratorPoint) == kPathCellBarrier) {
addDebugPoint(iteratorPoint, 24);
}
}
}
#endif
if (scanPathLine(fromPoint, toPoint)) {
actor->addWalkStepPoint(fromPoint);
actor->addWalkStepPoint(toPoint);
return;
}
i = fillPathArray(fromPoint, toPoint, bestPoint);
if (fromPoint == bestPoint) {
actor->addWalkStepPoint(bestPoint);
return;
}
if (i == 0) {
error("fillPathArray returns zero");
}
setActorPath(actor, fromPoint, bestPoint);
}
bool Actor::scanPathLine(const Point &point1, const Point &point2) {
Point point;
Point delta;
Point s;
Point fDelta;
int16 errterm;
calcDeltaS(point1, point2, delta, s);
point = point1;
fDelta.x = delta.x * 2;
fDelta.y = delta.y * 2;
if (delta.y > delta.x) {
errterm = fDelta.x - delta.y;
while (delta.y > 0) {
while (errterm >= 0) {
point.x += s.x;
errterm -= fDelta.y;
}
point.y += s.y;
errterm += fDelta.x;
if (!validPathCellPoint(point)) {
return false;
}
if (getPathCell(point) == kPathCellBarrier) {
return false;
}
delta.y--;
}
} else {
errterm = fDelta.y - delta.x;
while (delta.x > 0) {
while (errterm >= 0) {
point.y += s.y;
errterm -= fDelta.x;
}
point.x += s.x;
errterm += fDelta.y;
if (!validPathCellPoint(point)) {
return false;
}
if (getPathCell(point) == kPathCellBarrier) {
return false;
}
delta.x--;
}
}
return true;
}
int Actor::fillPathArray(const Point &fromPoint, const Point &toPoint, Point &bestPoint) {
int bestRating;
int currentRating;
Point bestPath;
int pointCounter;
const PathDirectionData *samplePathDirection;
Point nextPoint;
int directionCount;
int16 compressX = (_vm->getGameId() == GID_ITE) ? 2 : 1;
Common::List<PathDirectionData> pathDirectionQueue;
pointCounter = 0;
bestRating = quickDistance(fromPoint, toPoint, compressX);
bestPath = fromPoint;
for (int8 startDirection = 0; startDirection < 4; startDirection++) {
PathDirectionData tmp = { startDirection, fromPoint.x, fromPoint.y };
pathDirectionQueue.push_back(tmp);
}
if (validPathCellPoint(fromPoint)) {
setPathCell(fromPoint, kDirUp);
#ifdef ACTOR_DEBUG
addDebugPoint(fromPoint, 24+36);
#endif
}
while (!pathDirectionQueue.empty()) {
PathDirectionData curPathDirection = pathDirectionQueue.front();
pathDirectionQueue.pop_front();
for (directionCount = 0; directionCount < 3; directionCount++) {
samplePathDirection = &pathDirectionLUT[curPathDirection.direction][directionCount];
nextPoint = Point(curPathDirection.x, curPathDirection.y);
nextPoint.x += samplePathDirection->x;
nextPoint.y += samplePathDirection->y;
if (!validPathCellPoint(nextPoint)) {
continue;
}
if (getPathCell(nextPoint) != kPathCellEmpty) {
continue;
}
setPathCell(nextPoint, samplePathDirection->direction);
#ifdef ACTOR_DEBUG
addDebugPoint(nextPoint, samplePathDirection->direction + 96);
#endif
PathDirectionData tmp = {
samplePathDirection->direction,
nextPoint.x, nextPoint.y };
pathDirectionQueue.push_back(tmp);
++pointCounter;
if (nextPoint == toPoint) {
bestPoint = toPoint;
return pointCounter;
}
currentRating = quickDistance(nextPoint, toPoint, compressX);
if (currentRating < bestRating) {
bestRating = currentRating;
bestPath = nextPoint;
}
}
}
bestPoint = bestPath;
return pointCounter;
}
void Actor::setActorPath(ActorData *actor, const Point &fromPoint, const Point &toPoint) {
Point nextPoint;
int8 direction;
_pathList[0] = toPoint;
nextPoint = toPoint;
_pathListIndex = 0;
while (!(nextPoint == fromPoint)) {
direction = getPathCell(nextPoint);
if ((direction < 0) || (direction >= 8)) {
error("Actor::setActorPath error direction 0x%X", direction);
}
nextPoint.x -= pathDirectionLUT2[direction][0];
nextPoint.y -= pathDirectionLUT2[direction][1];
++_pathListIndex;
if (_pathListIndex >= _pathList.size()) {
_pathList.push_back(nextPoint);
} else {
_pathList[_pathListIndex] = nextPoint;
}
#ifdef ACTOR_DEBUG
addDebugPoint(nextPoint, 0x8a);
#endif
}
pathToNode();
removeNodes();
nodeToPath();
removePathPoints();
for (uint i = 0; i < _pathNodeList.size(); i++) {
actor->addWalkStepPoint(_pathNodeList[i].point);
}
}
void Actor::pathToNode() {
Point point1, point2, delta;
int direction;
int i;
direction = 0;
_pathNodeList.clear();
_pathNodeList.push_back(PathNode(_pathList[_pathListIndex]));
for (i = _pathListIndex; i > 0; i--) {
point1 = _pathList[i];
point2 = _pathList[i - 1];
if (direction == 0) {
delta.x = int16Compare(point2.x, point1.x);
delta.y = int16Compare(point2.y, point1.y);
direction++;
}
if ((point1.x + delta.x != point2.x) || (point1.y + delta.y != point2.y)) {
_pathNodeList.push_back(PathNode(point1));
direction--;
i++;
}
}
_pathNodeList.push_back(PathNode(_pathList[0]));
}
uint pathLine(PointList &pointList, uint idx, const Point &point1, const Point &point2) {
Point point;
Point delta;
Point tempPoint;
Point s;
int16 errterm;
uint res;
calcDeltaS(point1, point2, delta, s);
point = point1;
tempPoint.x = delta.x * 2;
tempPoint.y = delta.y * 2;
if (delta.y > delta.x) {
errterm = tempPoint.x - delta.y;
res = delta.y;
while (delta.y > 0) {
while (errterm >= 0) {
point.x += s.x;
errterm -= tempPoint.y;
}
point.y += s.y;
errterm += tempPoint.x;
if (idx >= pointList.size()) {
pointList.push_back(point);
} else {
pointList[idx] = point;
}
++idx;
delta.y--;
}
} else {
errterm = tempPoint.y - delta.x;
res = delta.x;
while (delta.x > 0) {
while (errterm >= 0) {
point.y += s.y;
errterm -= tempPoint.x;
}
point.x += s.x;
errterm += tempPoint.y;
if (idx >= pointList.size()) {
pointList.push_back(point);
} else {
pointList[idx] = point;
}
++idx;
delta.x--;
}
}
return res;
}
void Actor::nodeToPath() {
uint i;
Point point1, point2;
for (i = 0; i < _pathList.size(); i++) {
_pathList[i].x = _pathList[i].y = PATH_NODE_EMPTY;
}
_pathListIndex = 1;
_pathList[0] = _pathNodeList[0].point;
_pathNodeList[0].link = 0;
for (i = 0; i < _pathNodeList.size() - 1; i++) {
point1 = _pathNodeList[i].point;
point2 = _pathNodeList[i + 1].point;
_pathListIndex += pathLine(_pathList, _pathListIndex, point1, point2);
_pathNodeList[i + 1].link = _pathListIndex - 1;
}
_pathListIndex--;
_pathNodeList.back().link = _pathListIndex;
}
void Actor::removeNodes() {
uint i, j, k;
// If there are only two nodes, nothing to be done
if (_pathNodeList.size() <= 2)
return;
// If there is a direct connection between the first and last node, we can
// remove all nodes in between and are done.
if (scanPathLine(_pathNodeList.front().point, _pathNodeList.back().point)) {
_pathNodeList[1] = _pathNodeList.back();
_pathNodeList.resize(2);
}
if (_pathNodeList.size() <= 4)
return;
// Scan the nodes in reverse order, and look for a node which can be
// directly reached from the first node. If we find any, skip directly
// from the first node to that node (by marking all nodes in between as
// empty).
for (i = _pathNodeList.size() - 2; i > 1; i--) {
if (_pathNodeList[i].point.x == PATH_NODE_EMPTY) {
continue;
}
if (scanPathLine(_pathNodeList.front().point, _pathNodeList[i].point)) {
for (j = 1; j < i; j++) {
_pathNodeList[j].point.x = PATH_NODE_EMPTY;
}
break;
}
}
// Now do the reverse: Find the first node which is directly connected
// to the end node; if we find any, skip over all nodes in between.
for (i = 1; i < _pathNodeList.size() - 2; i++) {
if (_pathNodeList[i].point.x == PATH_NODE_EMPTY) {
continue;
}
if (scanPathLine(_pathNodeList.back().point, _pathNodeList[i].point)) {
for (j = i + 1; j < _pathNodeList.size() - 1; j++) {
_pathNodeList[j].point.x = PATH_NODE_EMPTY;
}
break;
}
}
condenseNodeList();
// Finally, try arbitrary combinations of non-adjacent nodes and see
// if we can skip over any of them.
for (i = 1; i < _pathNodeList.size() - 2; i++) {
if (_pathNodeList[i].point.x == PATH_NODE_EMPTY) {
continue;
}
for (j = i + 2; j < _pathNodeList.size() - 1; j++) {
if (_pathNodeList[j].point.x == PATH_NODE_EMPTY) {
continue;
}
if (scanPathLine(_pathNodeList[i].point, _pathNodeList[j].point)) {
for (k = i + 1; k < j; k++) {
_pathNodeList[k].point.x = PATH_NODE_EMPTY;
}
}
}
}
condenseNodeList();
}
/** Remove all empty nodes from _pathNodeList. */
void Actor::condenseNodeList() {
uint i, j, count;
count = _pathNodeList.size();
for (i = 1; i < _pathNodeList.size() - 1; i++) {
if (_pathNodeList[i].point.x == PATH_NODE_EMPTY) {
j = i + 1;
while (_pathNodeList[j].point.x == PATH_NODE_EMPTY) {
j++;
}
_pathNodeList[i] = _pathNodeList[j];
count = i + 1;
_pathNodeList[j].point.x = PATH_NODE_EMPTY;
if (j == _pathNodeList.size() - 1) {
break;
}
}
}
_pathNodeList.resize(count);
}
void Actor::removePathPoints() {
uint i, j, l;
int start;
int end;
Point point1, point2;
if (_pathNodeList.size() <= 2)
return;
PathNodeList newPathNodeList;
// Add the first node
newPathNodeList.push_back(_pathNodeList.front());
// Process all nodes between the first and the last.
for (i = 1; i < _pathNodeList.size() - 1; i++) {
newPathNodeList.push_back(_pathNodeList[i]);
for (j = 5; j > 0; j--) {
start = _pathNodeList[i].link - j;
end = _pathNodeList[i].link + j;
if (start < 0 || end > (int)_pathListIndex) {
continue;
}
point1 = _pathList[start];
point2 = _pathList[end];
if ((point1.x == PATH_NODE_EMPTY) || (point2.x == PATH_NODE_EMPTY)) {
continue;
}
if (scanPathLine(point1, point2)) {
for (l = 1; l < newPathNodeList.size(); l++) {
if (start <= newPathNodeList[l].link) {
newPathNodeList.resize(l + 1);
newPathNodeList.back().point = point1;
newPathNodeList.back().link = start;
newPathNodeList.resize(l + 2);
break;
}
}
newPathNodeList.back().point = point2;
newPathNodeList.back().link = end;
for (int k = start + 1; k < end; k++) {
_pathList[k].x = PATH_NODE_EMPTY;
}
break;
}
}
}
// Add the last node
newPathNodeList.push_back(_pathNodeList.back());
// Copy newPathNodeList into _pathNodeList, skipping any duplicate points
_pathNodeList.clear();
for (i = 0; i < newPathNodeList.size(); i++) {
if (((newPathNodeList.size() - 1) == i) || (newPathNodeList[i].point != newPathNodeList[i + 1].point)) {
_pathNodeList.push_back(newPathNodeList[i]);
}
}
}
#ifdef ACTOR_DEBUG
void Actor::drawPathTest() {
uint i;
for (i = 0; i < _debugPointsCount; i++) {
_vm->_gfx->setPixelColor(_debugPoints[i].point.x, _debugPoints[i].point.y, _debugPoints[i].color);
}
}
#endif
} // End of namespace Saga

1395
engines/saga/actor_walk.cpp Normal file

File diff suppressed because it is too large Load Diff

1062
engines/saga/animation.cpp Normal file

File diff suppressed because it is too large Load Diff

208
engines/saga/animation.h Normal file
View File

@@ -0,0 +1,208 @@
/* 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/>.
*
*/
// Background animation management module private header
#ifndef SAGA_ANIMATION_H
#define SAGA_ANIMATION_H
namespace Saga {
#define MAX_ANIMATIONS 10
#define DEFAULT_FRAME_TIME 140
#define SAGA_FRAME_START 0xF
#define SAGA_FRAME_END 0x3F
#define SAGA_FRAME_NOOP 0x1F
#define SAGA_FRAME_REPOSITION 0x30
#define SAGA_FRAME_ROW_END 0x2F
#define SAGA_FRAME_LONG_COMPRESSED_RUN 0x20
#define SAGA_FRAME_LONG_UNCOMPRESSED_RUN 0x10
#define SAGA_FRAME_COMPRESSED_RUN 0x80
#define SAGA_FRAME_UNCOMPRESSED_RUN 0x40
#define SAGA_FRAME_EMPTY_RUN 0xC0
#define SAGA_FRAME_AMIGA_OPCODE_REPOSITION 0x00
#define SAGA_FRAME_AMIGA_OPCODE_LITERAL 0x40
#define SAGA_FRAME_AMIGA_OPCODE_TRANSPARENT 0xC0
#define SAGA_FRAME_AMIGA_OPCODE_NEWLINE 0x80
#define SAGA_FRAME_AMIGA_OPCODE_MASK 0xC0
#define SAGA_FRAME_AMIGA_PARAM_MASK 0x3F
#define SAGA_FRAME_AMIGA_END 0x3F
#define SAGA_FRAME_AMIGA_START 0x3E
enum AnimationState {
ANIM_PLAYING = 0x01,
ANIM_PAUSE = 0x02,
ANIM_STOPPING = 0x03
};
enum AnimationFlags {
ANIM_FLAG_NONE = 0x00,
ANIM_FLAG_ENDSCENE = 0x01 // When animation ends, dispatch scene end event
};
// Cutaway info array member. Cutaways are basically animations with a really
// bad attitude.
struct Cutaway {
uint16 backgroundResourceId;
uint16 animResourceId;
int16 cycles;
int16 frameRate;
};
// Animation info array member
struct AnimationData {
ByteArray resourceData;
uint16 magic;
uint16 screenWidth;
uint16 screenHeight;
byte unknown06;
byte unknown07;
int16 maxFrame;
int16 loopFrame;
int16 currentFrame;
Common::Array<size_t> frameOffsets;
uint16 completed;
uint16 cycles;
int frameTime;
AnimationState state;
int16 linkId;
uint16 flags;
};
class Anim {
public:
Anim(SagaEngine *vm);
~Anim();
void loadCutawayList(const ByteArray &resourceData);
void clearCutawayList();
int playCutaway(int cut, bool fade);
void endCutaway();
void returnFromCutaway();
void clearCutaway();
void showCutawayBg(int bg);
void startVideo(int vid, bool fade);
void endVideo();
void returnFromVideo();
void load(uint16 animId, const ByteArray &resourceData);
void freeId(uint16 animId);
void play(uint16 animId, int vectorTime, bool playing = true);
void link(int16 animId1, int16 animId2);
void setFlag(uint16 animId, uint16 flag);
void clearFlag(uint16 animId, uint16 flag);
void setFrameTime(uint16 animId, int time);
void reset();
void animInfo();
void cutawayInfo();
void setCycles(uint16 animId, int cycles);
void stop(uint16 animId);
void finish(uint16 animId);
void resume(uint16 animId, int cycles);
void resumeAll();
int16 getCurrentFrame(uint16 animId);
int getFrameTime(uint16 animId);
int getCycles(uint16 animId);
bool isPlaying(uint16 animId);
bool hasAnimation(uint16 animId) {
if (animId >= MAX_ANIMATIONS) {
if (animId < MAX_ANIMATIONS + ARRAYSIZE(_cutawayAnimations))
return (_cutawayAnimations[animId - MAX_ANIMATIONS] != NULL);
return false;
}
return (_animations[animId] != NULL);
}
bool hasCutaway() { return _cutawayActive; }
void setCutAwayMode(int mode) { _cutAwayMode = mode; }
// int cutawayListLength() { return _cutawayListLength; }
// int cutawayBgResourceID(int cutaway) { return _cutawayList[cutaway].backgroundResourceId; }
// int cutawayAnimResourceID(int cutaway) { return _cutawayList[cutaway].animResourceId; }
private:
void decodeFrame(AnimationData *anim, size_t frameOffset, byte *buf, size_t bufLength);
int fillFrameOffsets(AnimationData *anim, bool reallyFill = true);
void validateAnimationId(uint16 animId) {
if (animId >= MAX_ANIMATIONS) {
// Cutaway
if (animId >= MAX_ANIMATIONS + ARRAYSIZE(_cutawayAnimations))
error("validateAnimationId: animId out of range");
if (_cutawayAnimations[animId - MAX_ANIMATIONS] == NULL) {
error("validateAnimationId: animId=%i unassigned", animId);
}
} else {
// Animation
if (_animations[animId] == NULL) {
error("validateAnimationId: animId=%i unassigned.", animId);
}
}
}
bool isLongData() const {
if ((_vm->getGameId() == GID_ITE) && (_vm->getPlatform() != Common::kPlatformMacintosh)) {
return false;
}
return true;
}
AnimationData* getAnimation(uint16 animId) {
validateAnimationId(animId);
if (animId >= MAX_ANIMATIONS)
return _cutawayAnimations[animId - MAX_ANIMATIONS];
return _animations[animId];
}
uint16 getAnimationCount() const {
uint16 i = 0;
for (; i < MAX_ANIMATIONS; i++) {
if (_animations[i] == NULL) {
break;
}
}
return i;
}
SagaEngine *_vm;
AnimationData *_animations[MAX_ANIMATIONS];
AnimationData *_cutawayAnimations[2];
Common::Array<Cutaway> _cutawayList;
PalEntry saved_pal[PAL_ENTRIES];
bool _cutawayActive;
int _cutAwayMode;
bool _cutAwayFade;
};
} // End of namespace Saga
#endif // ANIMATION_H_

View File

@@ -0,0 +1,4 @@
# This file is included from the main "configure" script
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
add_engine saga "SAGA" yes "ihnm" "ITE" "" "midi fmtowns_pc98_audio"
add_engine ihnm "IHNM" yes "" "" "highres"

302
engines/saga/console.cpp Normal file
View File

@@ -0,0 +1,302 @@
/* 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/>.
*
*/
// Console module
#include "saga/saga.h"
#include "saga/actor.h"
#include "saga/animation.h"
#include "saga/music.h"
#include "saga/scene.h"
#include "saga/script.h"
#include "saga/sndres.h"
#include "saga/console.h"
namespace Saga {
Console::Console(SagaEngine *vm) : GUI::Debugger() {
_vm = vm;
registerCmd("continue", WRAP_METHOD(Console, cmdExit));
// Actor commands
registerCmd("actor_walk_to", WRAP_METHOD(Console, cmdActorWalkTo));
// Animation commands
registerCmd("anim_info", WRAP_METHOD(Console, cmdAnimInfo));
registerCmd("cutaway_info", WRAP_METHOD(Console, cmdCutawayInfo));
registerCmd("play_cutaway", WRAP_METHOD(Console, cmdPlayCutaway));
// Sound commands
registerCmd("play_music", WRAP_METHOD(Console, cmdPlayMusic));
registerCmd("play_sound", WRAP_METHOD(Console, cmdPlaySound));
registerCmd("play_voice", WRAP_METHOD(Console, cmdPlayVoice));
// Scene commands
registerCmd("current_scene", WRAP_METHOD(Console, cmdCurrentScene));
registerCmd("current_chapter", WRAP_METHOD(Console, cmdCurrentChapter));
registerCmd("scene_change", WRAP_METHOD(Console, cmdSceneChange));
registerCmd("chapter_change", WRAP_METHOD(Console, cmdChapterChange));
registerCmd("action_map_info", WRAP_METHOD(Console, cmdActionMapInfo));
registerCmd("object_map_info", WRAP_METHOD(Console, cmdObjectMapInfo));
// Script commands
registerCmd("wake_up_threads", WRAP_METHOD(Console, cmdWakeUpThreads));
// Panel commands
registerCmd("current_panel_mode", WRAP_METHOD(Console, cmdCurrentPanelMode));
registerCmd("set_panel_mode", WRAP_METHOD(Console, cmdSetPanelMode));
// Font commands
registerCmd("set_font_mapping", WRAP_METHOD(Console, cmdSetFontMapping));
// Global flags commands
registerCmd("global_flags_info", WRAP_METHOD(Console, cmdGlobalFlagsInfo));
registerCmd("set_global_flag", WRAP_METHOD(Console, cmdSetGlobalFlag));
registerCmd("clear_global_flag", WRAP_METHOD(Console, cmdClearGlobalFlag));
}
Console::~Console() {
}
bool Console::cmdActorWalkTo(int argc, const char **argv) {
if (argc != 4)
debugPrintf("Usage: %s <Actor id> <lx> <ly>\n", argv[0]);
else
_vm->_actor->cmdActorWalkTo(argc, argv);
return true;
}
bool Console::cmdAnimInfo(int argc, const char **argv) {
_vm->_anim->animInfo();
return true;
}
bool Console::cmdCutawayInfo(int argc, const char **argv) {
#ifdef ENABLE_IHNM
_vm->_anim->cutawayInfo();
#endif
return true;
}
bool Console::cmdPlayCutaway(int argc, const char **argv) {
#ifdef ENABLE_IHNM
if (argc != 2)
debugPrintf("Usage: %s <Cutaway number>\n", argv[0]);
else
_vm->_anim->playCutaway(atoi(argv[1]), false);
#endif
return true;
}
bool Console::cmdPlayMusic(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s <Music number>\n", argv[0]);
} else {
if (_vm->getGameId() == GID_ITE)
_vm->_music->play(atoi(argv[1]) + 9);
else
_vm->_music->play(atoi(argv[1]));
}
return true;
}
bool Console::cmdPlaySound(int argc, const char **argv) {
if (argc != 2)
debugPrintf("Usage: %s <Sound number>\n", argv[0]);
else
_vm->_sndRes->playSound(atoi(argv[1]), 255, false);
return true;
}
bool Console::cmdPlayVoice(int argc, const char **argv) {
if (argc < 2) {
debugPrintf("Usage: %s <Voice number> <Voice bank>\n", argv[0]);
} else {
int voiceBank = 0;
if (argc == 3) {
voiceBank = _vm->_sndRes->getVoiceBank();
_vm->_sndRes->setVoiceBank(atoi(argv[2]));
}
_vm->_sndRes->playVoice(atoi(argv[1]));
if (argc == 3)
_vm->_sndRes->setVoiceBank(voiceBank);
}
return true;
}
bool Console::cmdCurrentScene(int argc, const char **argv) {
debugPrintf("Current Scene is: %i, scene resource id: %i\n",
_vm->_scene->currentSceneNumber(), _vm->_scene->currentSceneResourceId());
return true;
}
bool Console::cmdCurrentChapter(int argc, const char **argv) {
debugPrintf("Current Chapter is: %i\n", _vm->_scene->currentChapterNumber());
return true;
}
bool Console::cmdSceneChange(int argc, const char **argv) {
if (argc != 2)
debugPrintf("Usage: %s <Scene number>\n", argv[0]);
else
_vm->_scene->cmdSceneChange(argc, argv);
return true;
}
bool Console::cmdChapterChange(int argc, const char **argv) {
if (argc != 3)
debugPrintf("Usage: %s <Chapter number> <Scene number>\n", argv[0]);
else {
_vm->_scene->setChapterNumber(atoi(argv[2]));
_vm->_scene->cmdSceneChange(argc, argv);
}
return true;
}
bool Console::cmdActionMapInfo(int argc, const char **argv) {
_vm->_scene->cmdActionMapInfo();
return true;
}
bool Console::cmdObjectMapInfo(int argc, const char **argv) {
_vm->_scene->cmdObjectMapInfo();
return true;
}
bool Console::cmdWakeUpThreads(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s <wait type>\n", argv[0]);
debugPrintf("e.g.: 1 for kWaitTypeDelay, 2 for kWaitTypeSpeech, 10 for kWaitTypeWaitFrames");
debugPrintf("Refer to saga/script.h for additional types");
} else {
_vm->_script->wakeUpThreads(atoi(argv[1]));
}
return true;
}
bool Console::cmdCurrentPanelMode(int argc, const char **argv) {
debugPrintf("Current Panel Mode is: %i\n", _vm->_interface->getMode());
return true;
}
bool Console::cmdSetPanelMode(int argc, const char **argv) {
if (argc != 2)
debugPrintf("Usage: %s <Panel mode number>\n", argv[0]);
else
_vm->_interface->setMode(atoi(argv[1]));
return true;
}
bool Console::cmdSetFontMapping(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Sets font mapping\nUsage: %s <Font mapping flag>\n", argv[0]);
debugPrintf("Mapping flags:\n0 - default game behavior\n1 - force font mapping\n2 - ignore font mapping\n");
} else {
_vm->_font->setFontMapping(atoi(argv[1]));
}
return true;
}
bool Console::cmdGlobalFlagsInfo(int argc, const char **argv) {
debugPrintf("Global flags status for IHNM:\n");
// Global flags in IHNM:
// 00: Tested when Gorrister's chapter ends. 0: Gorrister failed, 1: Gorrister won
// 01: Tested when Gorrister's chapter ends, when Gorrister fails (flag 0 is 0). 0: Gorrister died, 1: Gorrister failed
// 02: Unknown, set when Gorrister's chapter ends (perhaps it signifies that Gorrister's chapter is done)
// 03: Unknown
// 04: Unknown
// 05: Unknown
// 06: Unknown
// 07: Unknown
// 08: Unknown
// 09: Unknown
// 10: Unknown
// 11: Unknown
// 12: Nimdok looked at mirror
// 13: Nimdok ordered the golem to destroy the Lost Tribe
// 14: Unknown
// 15: Unknown
// 16: Used in the final chapter. If it's 0 when a character dies, the "bad" ending for that character is shown
// 17: Unknown
// 18: Unknown
// 19: Unknown, used after any chapter ends
// 20: Unknown
// 21: Unknown
// 22: Unknown
// 23: Unknown
// 24: Unknown
// 25: Unknown
// 26: Unknown
// 27: Unknown
// 28: Unknown
// 29: Unknown
// 30: Unknown
// 31: Unknown
int i = 0, k = 0, flagStatus = 0;
for (i = 0; i < 32; i += 8) {
for (k = i; k < i + 8; k ++) {
flagStatus = _vm->_globalFlags & (1 << k) ? 1 : 0;
_vm->_console->debugPrintf("%02d: %u |", k, flagStatus);
}
_vm->_console->debugPrintf("\n");
}
return true;
}
bool Console::cmdSetGlobalFlag(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s <Global flag number>\nValid flag numbers are 0 - 31\n", argv[0]);
} else {
int flagNumber = atoi(argv[1]);
if (flagNumber >= 0 && flagNumber <= 31) {
_vm->_globalFlags |= (1 << flagNumber);
} else {
debugPrintf("Valid flag numbers are 0 - 31\n");
}
}
return true;
}
bool Console::cmdClearGlobalFlag(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Usage: %s <Global flag number>\nValid flag numbers are 0 - 31\n", argv[0]);
} else {
int flagNumber = atoi(argv[1]);
if (flagNumber >= 0 && flagNumber <= 31) {
_vm->_globalFlags &= ~(1 << flagNumber);
} else {
debugPrintf("Valid flag numbers are 0 - 31\n");
}
}
return true;
}
} // End of namespace Saga

72
engines/saga/console.h Normal file
View File

@@ -0,0 +1,72 @@
/* 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/>.
*
*/
// Console module header file
#ifndef SAGA_CONSOLE_H
#define SAGA_CONSOLE_H
#include "gui/debugger.h"
namespace Saga {
class Console : public GUI::Debugger {
public:
Console(SagaEngine *vm);
~Console() override;
private:
bool cmdActorWalkTo(int argc, const char **argv);
bool cmdAnimInfo(int argc, const char **argv);
bool cmdCutawayInfo(int argc, const char **argv);
bool cmdPlayCutaway(int argc, const char **argv);
bool cmdPlayMusic(int argc, const char **argv);
bool cmdPlaySound(int argc, const char **argv);
bool cmdPlayVoice(int argc, const char **argv);
bool cmdCurrentScene(int argc, const char **argv);
bool cmdCurrentChapter(int argc, const char **argv);
bool cmdSceneChange(int argc, const char **argv);
bool cmdChapterChange(int argc, const char **argv);
bool cmdActionMapInfo(int argc, const char **argv);
bool cmdObjectMapInfo(int argc, const char **argv);
bool cmdWakeUpThreads(int argc, const char **argv);
bool cmdCurrentPanelMode(int argc, const char **argv);
bool cmdSetPanelMode(int argc, const char **argv);
bool cmdSetFontMapping(int argc, const char **argv);
bool cmdGlobalFlagsInfo(int argc, const char **argv);
bool cmdSetGlobalFlag(int argc, const char **argv);
bool cmdClearGlobalFlag(int argc, const char **argv);
private:
SagaEngine *_vm;
};
} // End of namespace Saga
#endif

7
engines/saga/credits.pl Normal file
View File

@@ -0,0 +1,7 @@
begin_section("SAGA");
add_person("Torbj&ouml;rn Andersson", "eriktorbjorn", "");
add_person("Daniel Balsom", "DanielFox", "Original engine reimplementation author (retired)");
add_person("Filippos Karapetis", "bluegr", "");
add_person("Andrew Kurushin", "ajax16384", "");
add_person("Eugene Sandulenko", "sev", "");
end_section();

View File

@@ -0,0 +1,73 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 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/>.
*
*/
// Game detection, general game parameters
#include "base/plugins.h"
#include "engines/advancedDetector.h"
#include "saga/detection.h"
static const PlainGameDescriptor sagaGames[] = {
{"ite", "Inherit the Earth: Quest for the Orb"},
{"ihnm", "I Have No Mouth and I Must Scream"},
{nullptr, nullptr}
};
#include "saga/detection_tables.h"
class SagaMetaEngineDetection : public AdvancedMetaEngineDetection<Saga::SAGAGameDescription> {
public:
SagaMetaEngineDetection() : AdvancedMetaEngineDetection(Saga::gameDescriptions, sagaGames) {
static const char *const DIRECTORY_GLOBS[3] = { "music", "ITE Data Files", nullptr };
_maxScanDepth = 2;
_directoryGlobs = DIRECTORY_GLOBS;
_flags = kADFlagUseExtraAsHint;
}
const char *getName() const override {
return "saga";
}
const char *getEngineName() const override {
return "SAGA ["
#if defined(ENABLE_IHNM)
"all games"
#else
"ITE"
#if defined(ENABLE_IHNM)
", IHNM"
#endif
#endif
"]";
;
}
const char *getOriginalCopyright() const override {
return "Inherit the Earth (C) Wyrmkeep Entertainment";
}
};
REGISTER_PLUGIN_STATIC(SAGA_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, SagaMetaEngineDetection);

143
engines/saga/detection.h Normal file
View File

@@ -0,0 +1,143 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 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 SAGA_DETECTION_H
#define SAGA_DETECTION_H
#include "engines/advancedDetector.h"
namespace Saga {
enum GameIds {
GID_ITE = 0,
GID_IHNM = 1
};
enum GameFeatures {
GF_ITE_FLOPPY = 1 << 0,
GF_ITE_DOS_DEMO = 1 << 1,
GF_EXTRA_ITE_CREDITS = 1 << 2,
GF_8BIT_UNSIGNED_PCM = 1 << 3,
GF_IHNM_COLOR_FIX = 1 << 4,
GF_SOME_MAC_RESOURCES= 1 << 5,
GF_AGA_GRAPHICS = 1 << 6,
GF_ECS_GRAPHICS = 1 << 7,
GF_INSTALLER = 1 << 8,
GF_EMBED_FONT = 1 << 9,
GF_POWERPACK_GFX = 1 << 10,
};
enum GameFileTypes {
// Common
GAME_RESOURCEFILE = 1 << 0, // Game resources
GAME_SCRIPTFILE = 1 << 1, // Game scripts
GAME_SOUNDFILE = 1 << 2, // SFX (also contains voices and MIDI music in SAGA 2 games)
GAME_VOICEFILE = 1 << 3, // Voices (also contains SFX in the ITE floppy version)
// ITE specific
GAME_DIGITALMUSICFILE = 1 << 4, // ITE digital music, added by Wyrmkeep
GAME_MACBINARY = 1 << 5, // ITE Mac CD Guild
GAME_DEMOFILE = 1 << 6, // Early ITE demo
GAME_SWAPENDIAN = 1 << 7, // Used to identify the BE voice file in the ITE combined version
// IHNM specific
GAME_MUSICFILE_FM = 1 << 8, // IHNM
GAME_MUSICFILE_GM = 1 << 9, // IHNM, ITE Mac CD Guild
GAME_PATCHFILE = 1 << 10 // IHNM patch file (patch.re_/patch.res)
};
// Make sure to update ResourceLists in saga.cpp if this enum is reordered.
enum GameResourceList : uint8 {
RESOURCELIST_NONE = 0,
RESOURCELIST_ITE,
RESOURCELIST_ITE_ENGLISH_ECS,
RESOURCELIST_ITE_GERMAN_AGA,
RESOURCELIST_ITE_GERMAN_ECS,
RESOURCELIST_ITE_DEMO,
RESOURCELIST_IHNM,
RESOURCELIST_IHNM_DEMO,
RESOURCELIST_MAX
};
// Make sure to update FontLists in font.cpp if this enum is reordered.
enum GameFontList : uint8 {
FONTLIST_NONE = 0,
FONTLIST_ITE,
FONTLIST_ITE_DEMO,
FONTLIST_ITE_WIN_DEMO,
FONTLIST_IHNM_DEMO,
FONTLIST_IHNM_CD,
FONTLIST_IHNM_ZH,
FONTLIST_MAX
};
// Make sure to update PatchLists in resource.cpp if this enum is reordered.
enum GamePatchList : uint8 {
PATCHLIST_NONE = 0,
PATCHLIST_ITE,
PATCHLIST_ITE_MAC,
PATCHLIST_MAX
};
// Make sure to update ITE_IntroLists in introproc_ite.cpp if this enum is reordered.
enum GameIntroList : uint8 {
INTROLIST_NONE = 0,
INTROLIST_ITE_DEFAULT,
INTROLIST_ITE_AMIGA_ENGLISH_ECS,
INTROLIST_ITE_AMIGA_GERMAN_AGA,
INTROLIST_ITE_AMIGA_GERMAN_ECS,
INTROLIST_ITE_DOS_DEMO,
INTROLIST_MAX
};
struct SAGAGameDescription {
ADGameDescription desc;
int gameId;
uint32 features;
int startSceneNumber;
GameResourceList resourceList;
GameFontList fontList;
GamePatchList patchList;
GameIntroList introList;
// Only used if GF_INSTALLER is set
ADGameFileDescription filesInArchive[5];
uint32 sizeBuffer() const {
uint32 ret = desc.sizeBuffer();
for(int i = 0; i < ARRAYSIZE(filesInArchive); i++) {
ret += filesInArchive[i].sizeBuffer();
}
return ret;
}
void *toBuffer(void *buffer) {
buffer = desc.toBuffer(buffer);
for(int i = 0; i < ARRAYSIZE(filesInArchive); i++) {
buffer = filesInArchive[i].toBuffer(buffer);
}
return buffer;
}
};
#define GAMEOPTION_COPY_PROTECTION GUIO_GAMEOPTIONS1
} // End of namespace Saga
#endif // SAGA_DETECTION_H

File diff suppressed because it is too large Load Diff

540
engines/saga/displayinfo.h Normal file
View File

@@ -0,0 +1,540 @@
/* 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/>.
*
*/
// Interface widget display information
#ifndef SAGA_DISPLAYINFO_H
#define SAGA_DISPLAYINFO_H
namespace Saga {
struct PanelButton {
PanelButtonType type;
int xOffset;
int yOffset;
int width;
int height;
int id;
uint16 customType;
int state;
int upSpriteNumber;
int downSpriteNumber;
int overSpriteNumber;
};
struct GameDisplayInfo {
int width;
int height;
int pathStartY;
int sceneHeight;
int statusXOffset;
int statusYOffset;
int statusWidth;
int statusHeight;
int statusTextY;
int statusTextColor;
int statusBGColor;
int saveReminderXOffset;
int saveReminderYOffset;
int saveReminderWidth;
int saveReminderHeight;
int saveReminderFirstSpriteNumber;
int saveReminderNumSprites;
int leftPortraitXOffset;
int leftPortraitYOffset;
int rightPortraitXOffset;
int rightPortraitYOffset;
int inventoryUpButtonIndex;
int inventoryDownButtonIndex;
int inventoryRows;
int inventoryColumns;
int mainPanelXOffset;
int mainPanelYOffset;
int mainPanelButtonsCount;
PanelButton *mainPanelButtons;
int converseMaxTextWidth;
int converseTextHeight;
int converseTextLines;
int converseUpButtonIndex;
int converseDownButtonIndex;
int conversePanelXOffset;
int conversePanelYOffset;
int conversePanelButtonsCount;
PanelButton *conversePanelButtons;
int optionSaveFilePanelIndex;
int optionSaveFileSliderIndex;
uint32 optionSaveFileVisible;
int optionPanelXOffset;
int optionPanelYOffset;
int optionPanelButtonsCount;
PanelButton *optionPanelButtons;
int quitPanelXOffset;
int quitPanelYOffset;
int quitPanelWidth;
int quitPanelHeight;
int quitPanelButtonsCount;
PanelButton *quitPanelButtons;
int loadPanelXOffset;
int loadPanelYOffset;
int loadPanelWidth;
int loadPanelHeight;
int loadPanelButtonsCount;
PanelButton *loadPanelButtons;
int saveEditIndex;
int savePanelXOffset;
int savePanelYOffset;
int savePanelWidth;
int savePanelHeight;
int savePanelButtonsCount;
PanelButton *savePanelButtons;
int protectEditIndex;
int protectPanelXOffset;
int protectPanelYOffset;
int protectPanelWidth;
int protectPanelHeight;
int protectPanelButtonsCount;
PanelButton *protectPanelButtons;
};
#define ITE_CONVERSE_MAX_TEXT_WIDTH (256 - 60)
#define ITE_CONVERSE_TEXT_HEIGHT 10
#define ITE_CONVERSE_TEXT_LINES 4
// ITE section
static PanelButton ITE_MainPanelButtons[] = {
{kPanelButtonVerb, 52, 4, 57,10, kVerbITEWalkTo, kActionWalkTo, 0, 0, 1, 0},
{kPanelButtonVerb, 52, 15, 57,10, kVerbITELookAt, kActionLookAt, 0, 2, 3, 0},
{kPanelButtonVerb, 52, 26, 57,10, kVerbITEPickUp, kActionPickUp, 0, 4, 5, 0},
{kPanelButtonVerb, 52, 37, 57,10, kVerbITETalkTo, kActionTalkTo, 0, 0, 1, 0},
{kPanelButtonVerb, 110, 4, 56,10, kVerbITEOpen, kActionOpen, 0, 6, 7, 0},
{kPanelButtonVerb, 110,15, 56,10, kVerbITEClose, kActionClose, 0, 8, 9, 0},
{kPanelButtonVerb, 110,26, 56,10, kVerbITEUse, kActionUse, 0, 10, 11, 0},
{kPanelButtonVerb, 110,37, 56,10, kVerbITEGive, kActionGive, 0, 12, 13, 0},
{kPanelButtonArrow, 306,6, 8,5, -1,'U',0, 0,4,2},
{kPanelButtonArrow, 306,41, 8,5, 1,'D',0, 1,5,3},
{kPanelButtonInventory, 181 + 32*0,6, 27,18, 0,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*1,6, 27,18, 1,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*2,6, 27,18, 2,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*3,6, 27,18, 3,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*0,27, 27,18, 4,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*1,27, 27,18, 5,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*2,27, 27,18, 6,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*3,27, 27,18, 7,'-',0, 0,0,0}
};
static PanelButton ITE_MainPanelButtons_ZH[] = {
{kPanelButtonVerb, 53, 0, 34,16, kVerbITEWalkTo, kActionWalkTo, 0, 0,1,0},
{kPanelButtonVerb, 53,17, 34,16, kVerbITELookAt, kActionLookAt, 0, 2,3,0},
{kPanelButtonVerb, 53,34, 34,16, kVerbITEPickUp, kActionPickUp, 0, 4,5,0},
{kPanelButtonVerb, 92, 0, 34,16, kVerbITETalkTo, kActionTalkTo, 0, 0,1,0},
{kPanelButtonVerb, 92,17, 34,16, kVerbITEOpen, kActionOpen, 0, 6,7,0},
{kPanelButtonVerb, 92,34, 34,16, kVerbITEClose, kActionClose, 0, 8,9,0},
{kPanelButtonVerb, 132, 0, 34,16, kVerbITEUse, kActionUse, 0, 10,11,0},
{kPanelButtonVerb, 132,17, 34,16, kVerbITEGive, kActionGive, 0, 12,13,0},
{kPanelButtonArrow, 306,6, 8,5, -1,'U',0, 0,4,2},
{kPanelButtonArrow, 306,41, 8,5, 1,'D',0, 1,5,3},
{kPanelButtonInventory, 181 + 32*0,2, 27,18, 0,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*1,2, 27,18, 1,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*2,2, 27,18, 2,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*3,2, 27,18, 3,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*0,23, 27,18, 4,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*1,23, 27,18, 5,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*2,23, 27,18, 6,'-',0, 0,0,0},
{kPanelButtonInventory, 181 + 32*3,23, 27,18, 7,'-',0, 0,0,0}
};
static PanelButton ITE_ConversePanelButtons[] = {
{kPanelButtonConverseText, 52,6 + ITE_CONVERSE_TEXT_HEIGHT * 0, ITE_CONVERSE_MAX_TEXT_WIDTH,ITE_CONVERSE_TEXT_HEIGHT, 0,'1',0, 0,0,0},
{kPanelButtonConverseText, 52,6 + ITE_CONVERSE_TEXT_HEIGHT * 1, ITE_CONVERSE_MAX_TEXT_WIDTH,ITE_CONVERSE_TEXT_HEIGHT, 1,'2',0, 0,0,0},
{kPanelButtonConverseText, 52,6 + ITE_CONVERSE_TEXT_HEIGHT * 2, ITE_CONVERSE_MAX_TEXT_WIDTH,ITE_CONVERSE_TEXT_HEIGHT, 2,'3',0, 0,0,0},
{kPanelButtonConverseText, 52,6 + ITE_CONVERSE_TEXT_HEIGHT * 3, ITE_CONVERSE_MAX_TEXT_WIDTH,ITE_CONVERSE_TEXT_HEIGHT, 3,'4',0, 0,0,0},
{kPanelButtonArrow, 257,6, 9,6, -1,'u',0, 0,4,2},
{kPanelButtonArrow, 257,41, 9,6, 1,'d',0, 1,5,3},
};
static PanelButton ITE_OptionPanelButtons[] = {
{kPanelButtonOptionSlider, 284,19, 13,75, 0,'-',0, 0,0,0}, //slider-scroller
{kPanelButtonOption, 113,18, 45,17, kTextReadingSpeed, kActionOptionReadingSpeed, 0, 0,0,0}, //read speed
{kPanelButtonOption, 113,37, 45,17, kTextMusic, kActionOptionMusic, 0, 0,0,0}, //music
{kPanelButtonOption, 113,56, 45,17, kTextSound, kActionOptionSound, 0, 0,0,0}, //sound-noise
{kPanelButtonOption, 13,79, 135,17, kTextQuitGame, kActionOptionQuitGame, 0, 0,0,0}, //quit
{kPanelButtonOption, 13,98, 135,17, kTextContinuePlaying, kActionOptionContinue, 0, 0,0,0}, //continue
{kPanelButtonOption, 164,98, 57,17, kTextLoad, kActionOptionLoad, 0, 0,0,0}, //load
{kPanelButtonOption, 241,98, 57,17, kTextSave, kActionOptionSaveGame, 0, 0,0,0}, //save
{kPanelButtonOptionSaveFiles, 166,20, 112,74, 0,'-',0, 0,0,0}, //savefiles
{kPanelButtonOptionText,-1,4, 0,0, kTextGameOptions,'-',0, 0,0,0}, // text: game options
{kPanelButtonOptionText,5,18, 109,17, kTextReadingSpeed,'-',0, 0,0,0}, // text: read speed
{kPanelButtonOptionText,5,18, 109,17, kTextShowDialog,'-',0, 0,0,0}, // text: read speed
{kPanelButtonOptionText,5,37, 109,17, kTextMusic,'-',0, 0,0,0}, // text: music
{kPanelButtonOptionText,5,56, 109,17, kTextSound,'-',0, 0,0,0}, // text: noise
};
static PanelButton ITE_OptionPanelButtons_ZH[] = {
{kPanelButtonOptionSlider, 284,19, 13,75, 0,'-',0, 0,0,0}, //slider-scroller
{kPanelButtonOption, 113,18, 45,17, kTextReadingSpeed, kActionOptionReadingSpeed, 0, 0,0,0}, //read speed
{kPanelButtonOption, 113,37, 45,17, kTextMusic, kActionOptionMusic, 0, 0,0,0}, //music
{kPanelButtonOption, 113,56, 45,17, kTextSound, kActionOptionSound, 0, 0,0,0}, //sound-noise
{kPanelButtonOption, 13,79, 135,17, kTextQuitGame, kActionOptionQuitGame, 0, 0,0,0}, //quit
{kPanelButtonOption, 13,98, 135,17, kTextContinuePlaying, kActionOptionContinue, 0, 0,0,0}, //continue
{kPanelButtonOption, 164,98, 57,17, kTextLoad, kActionOptionLoad, 0, 0,0,0}, //load
{kPanelButtonOption, 241,98, 57,17, kTextSave, kActionOptionSaveGame, 0, 0,0,0}, //save
{kPanelButtonOptionSaveFiles, 166,20, 112,74, 0,'-',0, 0,0,0}, //savefiles
{kPanelButtonOptionText,9,0, 165,17, kTextGameOptions,'-',0, 0,0,0}, // text: game options
{kPanelButtonOptionText,2,18, 96,17, kTextReadingSpeed,'-',0, 0,0,0}, // text: read speed
{kPanelButtonOptionText,2,18, 80,17, kTextShowDialog,'-',0, 0,0,0}, // text: read speed
{kPanelButtonOptionText,2,37, 80,17, kTextMusic,'-',0, 0,0,0}, // text: music
{kPanelButtonOptionText,2,56, 80,17, kTextSound,'-',0, 0,0,0}, // text: noise
};
static PanelButton ITE_QuitPanelButtons[] = {
{kPanelButtonQuit, 11,17, 60,16, kTextQuit,kActionOptionQuit,0, 0,0,0},
{kPanelButtonQuit, 121,17, 60,16, kTextCancel,kActionOptionCancel,0, 0,0,0},
{kPanelButtonQuitText, -1,5, 0,0, kTextQuitTheGameQuestion,'-',0, 0,0,0},
};
static PanelButton ITE_LoadPanelButtons[] = {
{kPanelButtonLoad, 101,19, 60,16, kTextOK,kActionOptionOkay,0, 0,0,0},
{kPanelButtonLoadText, -1,5, 0,0, kTextLoadSuccessful,'-',0, 0,0,0},
};
static PanelButton ITE_SavePanelButtons[] = {
{kPanelButtonSave, 11,37, 60,16, kTextSave,kActionOptionSave,0, 0,0,0},
{kPanelButtonSave, 101,37, 60,16, kTextCancel,kActionOptionCancel,0, 0,0,0},
{kPanelButtonSaveEdit, 26,17, 119,17, 0,'-',0, 0,0,0},
{kPanelButtonSaveText, -1,5, 0,0, kTextEnterSaveGameName,'-',0, 0,0,0},
};
static PanelButton ITE_ProtectPanelButtons[] = {
{kPanelButtonProtectEdit, 26,17, 119,17, 0,'-',0, 0,0,0},
{kPanelButtonProtectText, -1,5, 0,0, kTextEnterProtectAnswer,'-',0, 0,0,0},
};
/*
static PanelButton ITE_ProtectionPanelButtons[] = {
{kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO
};*/
static const GameDisplayInfo ITE_DisplayInfo = {
320, 200, // logical width&height
35, // scene path y offset
137, // scene height
0, // status x offset
137, // status y offset
320, // status width
11, // status height
2, // status text y offset
kITEDOSColorGreen, // status text color
kITEDOSColorBlack, // status BG color
308,137, // save reminder pos
12,12, // save reminder w & h
6, // save reminder first sprite number
2, // number of save reminder sprites
5, 4, // left portrait x, y offset
274, 4, // right portrait x, y offset
8, 9, // inventory Up & Down button indexes
2, 4, // inventory rows, columns
0, 148, // main panel offsets
ARRAYSIZE(ITE_MainPanelButtons),
ITE_MainPanelButtons,
ITE_CONVERSE_MAX_TEXT_WIDTH,
ITE_CONVERSE_TEXT_HEIGHT,
ITE_CONVERSE_TEXT_LINES,
4, 5, // converse Up & Down button indexes
0, 148, // converse panel offsets
ARRAYSIZE(ITE_ConversePanelButtons),
ITE_ConversePanelButtons,
8, 0, // save file index
8, // optionSaveFileVisible
8, 8, // option panel offsets
ARRAYSIZE(ITE_OptionPanelButtons),
ITE_OptionPanelButtons,
64,54, // quit panel offsets
192,38, // quit panel width & height
ARRAYSIZE(ITE_QuitPanelButtons),
ITE_QuitPanelButtons,
74, 53, // load panel offsets
172, 40, // load panel width & height
ARRAYSIZE(ITE_LoadPanelButtons),
ITE_LoadPanelButtons,
2, // save edit index
74, 44, // save panel offsets
172, 58, // save panel width & height
ARRAYSIZE(ITE_SavePanelButtons),
ITE_SavePanelButtons,
0, // protect edit index
74, 44, // protect panel offsets
172, 58, // protect panel width & height
ARRAYSIZE(ITE_ProtectPanelButtons),
ITE_ProtectPanelButtons
};
static const GameDisplayInfo ITE_DisplayInfo_ZH = {
320, 200, // logical width&height
35, // scene path y offset
137, // scene height
0, // status x offset
137, // status y offset
320, // status width
14, // status height
0, // status text y offset
186, // status text color
15, // status BG color
308,137, // save reminder pos
12,12, // save reminder w & h
6, // save reminder first sprite number
2, // number of save reminder sprites
5, 0, // left portrait x, y offset
274, 0, // right portrait x, y offset
8, 9, // inventory Up & Down button indexes
2, 4, // inventory rows, columns
0, 153, // main panel offsets
ARRAYSIZE(ITE_MainPanelButtons_ZH),
ITE_MainPanelButtons_ZH,
ITE_CONVERSE_MAX_TEXT_WIDTH,
ITE_CONVERSE_TEXT_HEIGHT,
ITE_CONVERSE_TEXT_LINES,
4, 5, // converse Up & Down button indexes
0, 148, // converse panel offsets
ARRAYSIZE(ITE_ConversePanelButtons),
ITE_ConversePanelButtons,
8, 0, // save file index
8, // optionSaveFileVisible
8, 8, // option panel offsets
ARRAYSIZE(ITE_OptionPanelButtons_ZH),
ITE_OptionPanelButtons_ZH,
64,54, // quit panel offsets
192,38, // quit panel width & height
ARRAYSIZE(ITE_QuitPanelButtons),
ITE_QuitPanelButtons,
74, 53, // load panel offsets
172, 40, // load panel width & height
ARRAYSIZE(ITE_LoadPanelButtons),
ITE_LoadPanelButtons,
2, // save edit index
74, 44, // save panel offsets
172, 58, // save panel width & height
ARRAYSIZE(ITE_SavePanelButtons),
ITE_SavePanelButtons,
0, // protect edit index
74, 44, // protect panel offsets
172, 58, // protect panel width & height
ARRAYSIZE(ITE_ProtectPanelButtons),
ITE_ProtectPanelButtons
};
#if defined(ENABLE_IHNM)
// IHNM
#define IHNM_CONVERSE_MAX_TEXT_WIDTH (485 - 8)
#define IHNM_CONVERSE_TEXT_HEIGHT 10
#define IHNM_CONVERSE_TEXT_LINES 11
static PanelButton IHNM_MainPanelButtons[] = {
{kPanelButtonVerb, 106,12, 114,30, kVerbIHNMWalk, kActionWalkTo, 0, 0, 1,0},
{kPanelButtonVerb, 106,44, 114,30, kVerbIHNMLookAt, kActionLookAt, 0, 2, 3,0},
{kPanelButtonVerb, 106,76, 114,30, kVerbIHNMTake, kActionTake, 0, 4, 5,0},
{kPanelButtonVerb, 106,108, 114,30, kVerbIHNMUse, kActionUse, 0, 6, 7,0},
{kPanelButtonVerb, 223,12, 114,30, kVerbIHNMTalkTo, kActionTalkTo, 0, 8, 9,0},
{kPanelButtonVerb, 223,44, 114,30, kVerbIHNMSwallow, kActionSwallow, 0, 10,11,0},
{kPanelButtonVerb, 223,76, 114,30, kVerbIHNMGive, kActionGive, 0, 12,13,0},
{kPanelButtonVerb, 223,108, 114,30, kVerbIHNMPush, kActionPush, 0, 14,15,0},
{kPanelButtonArrow, 606,22, 20,25, -1,'[',0, 2,3,4}, // TODO: IHNM needs more states hre
{kPanelButtonArrow, 606,108, 20,25, 1,']',0, 6,7,8},
{kPanelButtonInventory, 357 + 64*0,18, 54,54, 0,'-',0, 0,0,0},
{kPanelButtonInventory, 357 + 64*1,18, 54,54, 1,'-',0, 0,0,0},
{kPanelButtonInventory, 357 + 64*2,18, 54,54, 2,'-',0, 0,0,0},
{kPanelButtonInventory, 357 + 64*3,18, 54,54, 3,'-',0, 0,0,0},
{kPanelButtonInventory, 357 + 64*0,80, 54,54, 4,'-',0, 0,0,0},
{kPanelButtonInventory, 357 + 64*1,80, 54,54, 5,'-',0, 0,0,0},
{kPanelButtonInventory, 357 + 64*2,80, 54,54, 6,'-',0, 0,0,0},
{kPanelButtonInventory, 357 + 64*3,80, 54,54, 7,'-',0, 0,0,0}
};
static PanelButton IHNM_ConversePanelButtons[] = {
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 0, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 0,'1',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 1, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 1,'2',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 2, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 2,'3',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 3, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 3,'4',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 4, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 4,'5',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 5, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 5,'6',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 6, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 6,'7',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 7, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 7,'8',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 8, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 8,'9',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 9, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 9,'10',0, 0,0,0},
{kPanelButtonConverseText, 117,18 + IHNM_CONVERSE_TEXT_HEIGHT * 10, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 10,'11',0, 0,0,0},
{kPanelButtonArrow, 606,22, 20,25, -1,'[',0, 2,3,4}, // TODO: IHNM needs more states hre
{kPanelButtonArrow, 606,108, 20,25, 1,']',0, 6,7,8},
};
static PanelButton IHNM_OptionPanelButtons[] = {
{kPanelButtonOptionSlider, 421,16, 16,138, 0,'-',0, 0,0,0}, //slider-scroller
{kPanelButtonOptionText,11,30, 139,21, kTextReadingSpeed,'-',0, 0,0,0}, // text: read speed
{kPanelButtonOptionText,11,55, 139,21, kTextMusic,'-',0, 0,0,0}, // text: music
{kPanelButtonOptionText,11,80, 139,21, kTextSound,'-',0, 0,0,0}, // text: noise
{kPanelButtonOptionText,11,105, 139,21, kTextVoices,'-',0, 0,0,0}, // text: voices
{kPanelButtonOption, 154,30, 79,23, kTextReadingSpeed,kActionOptionReadingSpeed,0, 0,0,0}, //read speed
{kPanelButtonOption, 154,55, 79,23, kTextMusic,kActionOptionMusic,0, 0,0,0}, //music
{kPanelButtonOption, 154,80, 79,23, kTextSound,kActionOptionSound,0, 0,0,0}, //sound-noise
{kPanelButtonOption, 154,105,79,23, kTextVoices,kActionOptionVoices,0, 0,0,0}, //voices
{kPanelButtonOption, 20,150, 200,25, kTextQuitGame,kActionOptionQuitGame,0, 0,0,0}, //quit
{kPanelButtonOption, 20,178, 200,25, kTextContinuePlaying,kActionOptionContinue,0, 0,0,0}, //continue
{kPanelButtonOptionSaveFiles, 244,18, 170,138, 0,'-',0, 0,0,0}, //savefiles
{kPanelButtonOption, 243,163, 79,23, kTextLoad,kActionOptionLoad,0, 0,0,0}, //load
{kPanelButtonOption, 334,163, 79,23, kTextSave,kActionOptionSaveGame,0, 0,0,0}, //save
};
static PanelButton IHNM_QuitPanelButtons[] = {
{kPanelButtonQuit, 26,80, 80,25, kTextQuit,kActionOptionQuit,0, 0,0,0},
{kPanelButtonQuit, 156,80, 80,25, kTextCancel,kActionOptionCancel,0, 0,0,0},
{kPanelButtonQuitText, -1,30, 0,0, kTextQuitTheGameQuestion,'-',0, 0,0,0},
};
static PanelButton IHNM_LoadPanelButtons[] = {
{kPanelButtonLoad, 26,80, 80,25, kTextOK,kActionOptionOkay,0, 0,0,0},
{kPanelButtonLoad, 156,80, 80,25, kTextCancel,kActionOptionCancel,0, 0,0,0},
{kPanelButtonLoadText, -1,30, 0,0, kTextLoadSavedGame,'-',0, 0,0,0},
};
static PanelButton IHNM_SavePanelButtons[] = {
{kPanelButtonSave, 25,79, 80,25, kTextSave,kActionOptionSave,0, 0,0,0},
{kPanelButtonSave, 155,79, 80,25, kTextCancel,kActionOptionCancel,0, 0,0,0},
{kPanelButtonSaveEdit, 22,56, 216,17, 0,'-',0, 0,0,0},
{kPanelButtonSaveText, -1,30, 0,0, kTextEnterSaveGameName,'-',0, 0,0,0},
};
#endif
#ifdef ENABLE_IHNM
static const GameDisplayInfo IHNM_DisplayInfo = {
640, 480, // logical width&height
0, // scene path y offset
304, // scene height
0, // status x offset
304, // status y offset
616, // status width
24, // status height
8, // status text y offset
253, // status text color
250, // status BG color
616, 304, // save reminder pos
24, 24, // save reminder w&h
0, // save reminder first sprite number
16, // number of save reminder sprites
11, 12, // left portrait x, y offset
-1, -1, // right portrait x, y offset
8, 9, // inventory Up & Down button indexes
2, 4, // inventory rows, columns
0, 328, // main panel offsets
ARRAYSIZE(IHNM_MainPanelButtons),
IHNM_MainPanelButtons,
IHNM_CONVERSE_MAX_TEXT_WIDTH,
IHNM_CONVERSE_TEXT_HEIGHT,
IHNM_CONVERSE_TEXT_LINES,
11, 12, // converse Up & Down button indexes
0, 328, // converse panel offsets
ARRAYSIZE(IHNM_ConversePanelButtons),
IHNM_ConversePanelButtons,
11, 0, // save file index
15, // optionSaveFileVisible
92, 46, // option panel offsets
ARRAYSIZE(IHNM_OptionPanelButtons),
IHNM_OptionPanelButtons,
190,94, // quit panel offsets
260,115, // quit panel width & height
ARRAYSIZE(IHNM_QuitPanelButtons),
IHNM_QuitPanelButtons,
190, 94, // load panel offsets
260, 115, // load panel width & height
ARRAYSIZE(IHNM_LoadPanelButtons),
IHNM_LoadPanelButtons,
2, // save edit index
190, 94, // save panel offsets
260, 115, // save panel width & height
ARRAYSIZE(IHNM_SavePanelButtons),
IHNM_SavePanelButtons,
// No protection panel in IHNM
-1, // protect edit index
0, 0, // protect panel offsets
0, 0, // protect panel width & height
ARRAYSIZE(IHNM_SavePanelButtons),
IHNM_SavePanelButtons
};
#endif
} // End of namespace Saga
#endif

657
engines/saga/events.cpp Normal file
View File

@@ -0,0 +1,657 @@
/* 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/>.
*
*/
// Event management module
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/animation.h"
#include "saga/console.h"
#include "saga/scene.h"
#include "saga/interface.h"
#include "saga/palanim.h"
#include "saga/render.h"
#include "saga/sndres.h"
#include "saga/resource.h"
#include "saga/music.h"
#include "saga/actor.h"
#include "saga/events.h"
namespace Saga {
Events::Events(SagaEngine *vm) : _vm(vm) {
debug(8, "Initializing event subsystem...");
}
Events::~Events() {
debug(8, "Shutting down event subsystem...");
}
// Function to process event list once per frame.
// First advances event times, then processes each event with the appropriate
// handler depending on the type of event.
void Events::handleEvents(long msec) {
long delta_time;
int result;
// Advance event times
processEventTime(msec);
// Process each event in list
for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) {
Event *event_p = &eventi->front();
// Call the appropriate event handler for the specific event type
switch (event_p->type) {
case kEvTOneshot:
result = handleOneShot(event_p);
break;
case kEvTContinuous:
result = handleContinuous(event_p);
break;
case kEvTInterval:
result = handleInterval(event_p);
break;
case kEvTImmediate:
result = handleImmediate(event_p);
break;
default:
result = kEvStInvalidCode;
warning("Invalid event code encountered");
break;
}
// Process the event appropriately based on result code from
// handler
if ((result == kEvStDelete) || (result == kEvStInvalidCode)) {
// If there is no event chain, delete the base event.
if (eventi->size() < 2) {
eventi = _eventList.reverse_erase(eventi);
} else {
// If there is an event chain present, move the next event
// in the chain up, adjust it by the previous delta time,
// and reprocess the event
delta_time = event_p->time;
eventi->pop_front();
event_p = &eventi->front();
event_p->time += delta_time;
--eventi;
}
} else if (result == kEvStBreak) {
break;
}
}
}
int Events::handleContinuous(Event *event) {
double event_pc = 0.0; // Event completion percentage
int event_done = 0;
BGInfo bgInfo;
Rect rect;
if (event->duration != 0) {
event_pc = ((double)event->duration - event->time) / event->duration;
} else {
event_pc = 1.0;
}
if (event_pc >= 1.0) {
// Cap percentage to 100
event_pc = 1.0;
event_done = 1;
}
if (event_pc < 0.0) {
// Event not signaled, skip it
return kEvStContinue;
} else if (!(event->code & kEvFSignaled)) {
// Signal event
event->code |= kEvFSignaled;
event_pc = 0.0;
}
switch (event->code & EVENT_MASK) {
case kPalEvent:
switch (event->op) {
case kEventBlackToPal:
_vm->_gfx->blackToPal((PalEntry *)event->data, event_pc);
break;
case kEventPalToBlack:
_vm->_gfx->palToBlack((PalEntry *)event->data, event_pc);
break;
#ifdef ENABLE_IHNM
case kEventPalFade:
_vm->_gfx->palFade((PalEntry *)event->data, event->param, event->param2, event->param3, event->param4, event_pc);
break;
#endif
default:
break;
}
break;
case kTransitionEvent:
switch (event->op) {
case kEventDissolve:
_vm->_scene->getBGInfo(bgInfo);
rect.left = rect.top = 0;
rect.right = bgInfo.bounds.width();
rect.bottom = bgInfo.bounds.height();
_vm->_render->getBackGroundSurface()->transitionDissolve(bgInfo.buffer, rect, 0, event_pc);
_vm->_render->setFullRefresh(true);
break;
case kEventDissolveBGMask:
// we dissolve it centered.
// set flag of Dissolve to 1. It is a hack to simulate zero masking.
int w, h;
byte *maskBuffer;
_vm->_scene->getBGMaskInfo(w, h, maskBuffer);
rect.left = (_vm->getDisplayInfo().width - w) / 2;
rect.top = (_vm->getDisplayInfo().height - h) / 2;
rect.setWidth(w);
rect.setHeight(h);
_vm->_render->getBackGroundSurface()->transitionDissolve(maskBuffer, rect, 1, event_pc);
_vm->_render->setFullRefresh(true);
break;
default:
break;
}
break;
default:
break;
}
if (event_done) {
return kEvStDelete;
}
return kEvStContinue;
}
int Events::handleImmediate(Event *event) {
double event_pc = 0.0; // Event completion percentage
bool event_done = false;
// Duration might be 0 so dont do division then
if (event->duration != 0) {
event_pc = ((double)event->duration - event->time) / event->duration;
} else {
// Just make sure that event_pc is 1.0 so event_done is true
event_pc = 1.0;
}
if (event_pc >= 1.0) {
// Cap percentage to 100
event_pc = 1.0;
event_done = true;
}
if (event_pc < 0.0) {
// Event not signaled, skip it
return kEvStBreak;
} else if (!(event->code & kEvFSignaled)) {
// Signal event
event->code |= kEvFSignaled;
event_pc = 0.0;
}
switch (event->code & EVENT_MASK) {
case kPalEvent:
switch (event->op) {
case kEventBlackToPal:
_vm->_gfx->blackToPal((PalEntry *)event->data, event_pc);
break;
case kEventPalToBlack:
_vm->_gfx->palToBlack((PalEntry *)event->data, event_pc);
break;
#ifdef ENABLE_IHNM
case kEventPalFade:
_vm->_gfx->palFade((PalEntry *)event->data, event->param, event->param2, event->param3, event->param4, event_pc);
break;
#endif
default:
break;
}
break;
case kScriptEvent:
case kBgEvent:
case kInterfaceEvent:
case kSceneEvent:
case kAnimEvent:
case kCutawayEvent:
case kActorEvent:
handleOneShot(event);
event_done = true;
break;
default:
warning("Unhandled Immediate event type (%d)", event->code & EVENT_MASK);
break;
}
if (event_done) {
return kEvStDelete;
}
return kEvStBreak;
}
int Events::handleOneShot(Event *event) {
Rect rect;
if (event->time > 0) {
return kEvStContinue;
}
// Event has been signaled
switch (event->code & EVENT_MASK) {
case kTextEvent:
switch (event->op) {
case kEventDisplay:
((TextListEntry *)event->data)->display = true;
break;
case kEventRemove: {
TextListEntry entry = *((TextListEntry *)event->data);
_vm->_scene->_textList.remove(entry);
} break;
default:
break;
}
break;
case kSoundEvent:
_vm->_sound->stopSound();
if (event->op == kEventPlay)
_vm->_sndRes->playSound(event->param, event->param2, event->param3 != 0);
break;
case kVoiceEvent:
_vm->_sndRes->playVoice(event->param);
break;
case kMusicEvent:
if (event->op == kEventPlay)
_vm->_music->play(event->param, (MusicFlags)event->param2);
break;
case kBgEvent:
{
Surface *backGroundSurface = _vm->_render->getBackGroundSurface();
BGInfo bgInfo;
byte black = _vm->iteColorBlack();
if (!(_vm->_scene->getFlags() & kSceneFlagISO)) {
_vm->_scene->getBGInfo(bgInfo);
backGroundSurface->blit(bgInfo.bounds, bgInfo.buffer);
// If it is inset scene then draw black border
if (bgInfo.bounds.width() < _vm->getDisplayInfo().width || bgInfo.bounds.height() < _vm->_scene->getHeight()) {
Common::Rect rect1(2, bgInfo.bounds.height() + 4);
Common::Rect rect2(bgInfo.bounds.width() + 4, 2);
Common::Rect rect3(2, bgInfo.bounds.height() + 4);
Common::Rect rect4(bgInfo.bounds.width() + 4, 2);
rect1.moveTo(bgInfo.bounds.left - 2, bgInfo.bounds.top - 2);
rect2.moveTo(bgInfo.bounds.left - 2, bgInfo.bounds.top - 2);
rect3.moveTo(bgInfo.bounds.right, bgInfo.bounds.top - 2);
rect4.moveTo(bgInfo.bounds.left - 2, bgInfo.bounds.bottom);
backGroundSurface->drawRect(rect1, black);
backGroundSurface->drawRect(rect2, black);
backGroundSurface->drawRect(rect3, black);
backGroundSurface->drawRect(rect4, black);
}
if (event->param == kEvPSetPalette) {
PalEntry *palPointer;
#ifdef ENABLE_IHNM
if (_vm->getGameId() == GID_IHNM) {
PalEntry portraitBgColor = _vm->_interface->_portraitBgColor;
byte portraitColor = (_vm->getLanguage() == Common::ES_ESP) ? 253 : 254;
// Set the portrait bg color, in case a saved state is restored from the
// launcher. In this case, sfSetPortraitBgColor is not called, thus the
// portrait color will always be 0 (black).
if (portraitBgColor.red == 0 && portraitBgColor.green == 0 && portraitBgColor.blue == 0)
portraitBgColor.green = 255;
if (_vm->_spiritualBarometer > 255)
_vm->_gfx->setPaletteColor(portraitColor, 0xff, 0xff, 0xff);
else
_vm->_gfx->setPaletteColor(portraitColor,
_vm->_spiritualBarometer * portraitBgColor.red / 256,
_vm->_spiritualBarometer * portraitBgColor.green / 256,
_vm->_spiritualBarometer * portraitBgColor.blue / 256);
}
#endif
_vm->_scene->getBGPal(palPointer);
_vm->_gfx->setPalette(palPointer);
}
}
_vm->_render->clearFlag(RF_DISABLE_ACTORS);
}
break;
case kPsychicProfileBgEvent:
{
ResourceContext *context = _vm->_resource->getContext(GAME_RESOURCEFILE);
ByteArray resourceData;
_vm->_resource->loadResource(context, _vm->getResourceDescription()->psychicProfileResourceId, resourceData);
ByteArray image;
int width;
int height;
_vm->decodeBGImage(resourceData, image, &width, &height);
const PalEntry *palette = (const PalEntry *)_vm->getImagePal(resourceData);
const Rect profileRect(width, height);
_vm->_render->getBackGroundSurface()->blit(profileRect, image.getBuffer());
_vm->_render->addDirtyRect(profileRect);
_vm->_frameCount++;
_vm->_gfx->setPalette(palette);
// Draw the scene. It won't be drawn by Render::drawScene(), as a placard is up
_vm->_scene->draw();
}
break;
case kAnimEvent:
switch (event->op) {
case kEventPlay:
_vm->_anim->play(event->param, event->time, true);
break;
case kEventStop:
_vm->_anim->stop(event->param);
break;
case kEventFrame:
_vm->_anim->play(event->param, event->time, false);
break;
case kEventSetFlag:
_vm->_anim->setFlag(event->param, event->param2);
break;
case kEventClearFlag:
_vm->_anim->clearFlag(event->param, event->param2);
break;
case kEventResumeAll:
_vm->_anim->resumeAll();
break;
default:
break;
}
break;
case kSceneEvent:
switch (event->op) {
case kEventDraw:
{
BGInfo bgInfo;
_vm->_scene->getBGInfo(bgInfo);
_vm->_render->getBackGroundSurface()->blit(bgInfo.bounds, bgInfo.buffer);
_vm->_render->addDirtyRect(bgInfo.bounds);
_vm->_scene->draw();
}
break;
case kEventEnd:
_vm->_scene->nextScene();
return kEvStBreak;
default:
break;
}
break;
case kPalAnimEvent:
switch (event->op) {
case kEventCycleStart:
_vm->_palanim->cycleStart();
break;
case kEventCycleStep:
_vm->_palanim->cycleStep(event->time);
break;
default:
break;
}
break;
case kInterfaceEvent:
switch (event->op) {
case kEventActivate:
_vm->_interface->activate();
break;
case kEventDeactivate:
_vm->_interface->deactivate();
break;
case kEventSetStatus:
_vm->_interface->setStatusText((const char*)event->data);
_vm->_interface->drawStatusBar();
break;
case kEventClearStatus:
_vm->_interface->setStatusText("");
_vm->_interface->drawStatusBar();
break;
case kEventSetFadeMode:
_vm->_interface->setFadeMode(event->param);
break;
case kEventRestoreMode:
_vm->_interface->restoreMode();
break;
case kEventSetMode:
_vm->_interface->setMode(event->param);
break;
default:
break;
}
break;
case kScriptEvent:
switch (event->op) {
case kEventExecBlocking:
case kEventExecNonBlocking: {
debug(6, "Exec module number %ld script entry number %ld", event->param, event->param2);
ScriptThread &sthread = _vm->_script->createThread(event->param, event->param2);
sthread._threadVars[kThreadVarAction] = event->param3;
sthread._threadVars[kThreadVarObject] = event->param4;
sthread._threadVars[kThreadVarWithObject] = event->param5;
sthread._threadVars[kThreadVarActor] = event->param6;
if (event->op == kEventExecBlocking)
_vm->_script->completeThread();
break;
}
case kEventThreadWake:
_vm->_script->wakeUpThreads(event->param);
break;
default:
break;
}
break;
case kCursorEvent:
switch (event->op) {
case kEventShow:
_vm->_gfx->showCursor(true);
break;
case kEventHide:
_vm->_gfx->showCursor(false);
break;
case kEventSetNormalCursor:
// in ITE and IHNM demo there is just one cursor
// ITE never makes this call
if (!_vm->isIHNMDemo())
_vm->_gfx->setCursor(kCursorNormal);
break;
case kEventSetBusyCursor:
// in ITE and IHNM demo there is just one cursor
// ITE never makes this call
if (!_vm->isIHNMDemo())
_vm->_gfx->setCursor(kCursorBusy);
break;
default:
break;
}
break;
case kGraphicsEvent:
switch (event->op) {
case kEventFillRect:
rect.top = event->param2;
rect.bottom = event->param3;
rect.left = event->param4;
rect.right = event->param5;
_vm->_gfx->drawRect(rect, event->param);
break;
case kEventSetFlag:
_vm->_render->setFlag(event->param);
break;
case kEventClearFlag:
_vm->_render->clearFlag(event->param);
break;
default:
break;
}
break;
#ifdef ENABLE_IHNM
case kCutawayEvent:
switch (event->op) {
case kEventClear:
_vm->_anim->clearCutaway();
break;
case kEventShowCutawayBg:
_vm->_anim->showCutawayBg(event->param);
break;
default:
break;
}
break;
#endif
case kActorEvent:
switch (event->op) {
case kEventMove:
// TODO (check Actor::direct)
break;
default:
break;
}
break;
default:
break;
}
return kEvStDelete;
}
int Events::handleInterval(Event *event) {
return kEvStDelete;
}
EventColumns *Events::chain(EventColumns *eventColumns, const Event &event) {
if (eventColumns == nullptr) {
EventColumns tmp;
_eventList.push_back(tmp);
eventColumns = &_eventList.back();
}
eventColumns->push_back(event);
initializeEvent(eventColumns->back());
return eventColumns;
}
EventColumns *Events::chainMusic(EventColumns *eventColumns, long musicId, bool loop, long time) {
Event event;
event.type = kEvTOneshot;
event.code = kMusicEvent;
event.param = musicId;
event.param2 = loop ? MUSIC_NORMAL : MUSIC_LOOP;
event.op = kEventPlay;
event.time = time;
return chain(eventColumns, event);
}
void Events::initializeEvent(Event &event) {
switch (event.type) {
case kEvTOneshot:
break;
case kEvTContinuous:
case kEvTImmediate:
event.time += event.duration;
break;
case kEvTInterval:
break;
default:
break;
}
}
void Events::clearList(bool playQueuedMusic) {
// Walk down event list
for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) {
// Only remove events not marked kEvFNoDestory (engine events)
if (!(eventi->front().code & kEvFNoDestory)) {
// Handle queued music change events before deleting them
// This can happen in IHNM by music events set by sfQueueMusic()
// Fixes bug #3880 - "IHNM: Music stops in Ellen's chapter"
if (playQueuedMusic && ((eventi->front().code & EVENT_MASK) == kMusicEvent)) {
_vm->_music->stop();
if (eventi->front().op == kEventPlay)
_vm->_music->play(eventi->front().param, (MusicFlags)eventi->front().param2);
}
eventi = _eventList.reverse_erase(eventi);
}
}
}
// Removes all events from the list (even kEvFNoDestory)
void Events::freeList() {
_eventList.clear();
}
// Walks down the event list, updating event times by 'msec'.
void Events::processEventTime(long msec) {
uint16 event_count = 0;
for (auto &eventi : _eventList) {
eventi.front().time -= msec;
event_count++;
if (eventi.front().type == kEvTImmediate)
break;
if (event_count > EVENT_WARNINGCOUNT) {
warning("Event list exceeds %u", EVENT_WARNINGCOUNT);
}
}
}
} // End of namespace Saga

202
engines/saga/events.h Normal file
View File

@@ -0,0 +1,202 @@
/* 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/>.
*
*/
// Event management module header file
#ifndef SAGA_EVENT_H
#define SAGA_EVENT_H
#include "common/list.h"
namespace Saga {
enum EventTypes {
kEvTOneshot, // Event takes no time
kEvTContinuous, // Event takes time; next event starts immediately
kEvTInterval, // Not yet implemented
kEvTImmediate // Event takes time; next event starts when event is done
};
enum EventFlags {
kEvFSignaled = 0x8000,
kEvFNoDestory = 0x4000
};
enum EventCodes {
kBgEvent = 1,
kAnimEvent,
kMusicEvent,
kVoiceEvent,
kSoundEvent,
kSceneEvent,
kTextEvent,
kPalEvent,
kPalAnimEvent,
kTransitionEvent,
kInterfaceEvent,
kActorEvent,
kScriptEvent,
kCursorEvent,
kGraphicsEvent,
kCutawayEvent,
kPsychicProfileBgEvent
};
enum EventOps {
// INSTANTANEOUS events
// BG events
kEventDisplay = 1,
// ANIM events
kEventPlay = 1, // used in music and sound events too
kEventStop = 2, // used in music and sound events too
kEventFrame = 3,
kEventSetFlag = 4, // used in graphics events too
kEventClearFlag = 5, // used in graphics events too
kEventResumeAll = 6,
// MUSIC and SOUND events
// Reused: kEventPlay, kEventStop
// SCENE events
kEventDraw = 1,
kEventEnd = 2,
// TEXT events
kEventRemove = 3,
// Reused: kEventHide
// PALANIM events
kEventCycleStart = 1,
kEventCycleStep = 2,
// INTERFACE events
kEventActivate = 1,
kEventDeactivate = 2,
kEventSetStatus = 3,
kEventClearStatus = 4,
kEventSetFadeMode = 5,
kEventRestoreMode = 6,
kEventSetMode = 7,
// ACTOR events
kEventMove = 1,
// SCRIPT events
kEventExecBlocking = 1,
kEventExecNonBlocking = 2,
kEventThreadWake = 3,
// CURSOR events
kEventShow = 1,
kEventHide = 2, // used in text events too
kEventSetNormalCursor = 3,
kEventSetBusyCursor = 4,
// GRAPHICS events
kEventFillRect = 1,
// Reused: kEventSetFlag, kEventClearFlag
// CONTINUOUS events
//
// PALETTE events
kEventPalToBlack = 1,
kEventBlackToPal = 2,
kEventPalFade = 3,
// TRANSITION events
kEventDissolve = 1,
kEventDissolveBGMask = 2,
// CUTAWAY events
kEventClear = 1,
kEventShowCutawayBg = 2
};
enum EventParams {
kEvPNoSetPalette,
kEvPSetPalette
};
struct Event {
unsigned int type;
unsigned int code; // Event operation category & flags
int op; // Event operation
long param; // Optional event parameter
long param2;
long param3;
long param4;
long param5;
long param6;
void *data; // Optional event data
long time; // Elapsed time until event
long duration; // Duration of event
long d_reserved;
Event() {
memset(this, 0, sizeof(*this));
}
};
typedef Common::List<Event> EventColumns;
typedef Common::List<EventColumns> EventList;
#define EVENT_WARNINGCOUNT 1000
#define EVENT_MASK 0x00FF
enum EventStatusCode {
kEvStInvalidCode = 0,
kEvStDelete,
kEvStContinue,
kEvStBreak
};
class Events {
public:
Events(SagaEngine *vm);
~Events();
void handleEvents(long msec);
void clearList(bool playQueuedMusic = true);
void freeList();
// Schedules an event in the event list; returns a pointer to the scheduled
// event columns suitable for chaining if desired.
EventColumns *queue(const Event &event) {
return chain(NULL, event);
}
// Schedules a music event in the event list; returns a pointer to the scheduled
// event columns suitable for chaining if desired.
EventColumns *queueMusic(long musicId, bool loop = false, long time = 0) {
return chainMusic(NULL, musicId, loop, time);
}
// Places a 'event' on the end of an event columns given by 'eventColumns'
EventColumns *chain(EventColumns *eventColumns, const Event &event);
// Places a music 'event' on the end of an event columns given by 'eventColumns'
EventColumns *chainMusic(EventColumns *eventColumns, long musicId, bool loop = false, long time = 0);
private:
int handleContinuous(Event *event);
int handleOneShot(Event *event);
int handleInterval(Event *event);
int handleImmediate(Event *event);
void processEventTime(long msec);
void initializeEvent(Event &event);
private:
SagaEngine *_vm;
EventList _eventList;
};
} // End of namespace Saga
#endif

1207
engines/saga/font.cpp Normal file

File diff suppressed because it is too large Load Diff

307
engines/saga/font.h Normal file
View File

@@ -0,0 +1,307 @@
/* 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/>.
*
*/
// Font management and font drawing header file
#ifndef SAGA_FONT_H
#define SAGA_FONT_H
#include "common/list.h"
#include "saga/gfx.h"
namespace Graphics {
class FontSJIS;
}
namespace Saga {
#define FONT_SHOWUNDEFINED 1 // Define to draw undefined characters * as ?'s
// The first defined character (!) is the only one that may
// have a valid offset of '0'
#define FONT_FIRSTCHAR 33
#define FONT_CH_TAB 9
#define FONT_CH_SPACE 32
#define FONT_CH_QMARK 63
// Minimum font header size without font data
// (6 + 512 + 256 + 256 + 256 )
#define FONT_DESCSIZE 1286
#define FONT_CHARCOUNT 256
#define FONT_CHARMASK 0xFFU
#define SAGA_FONT_HEADER_LEN 6
#define TEXT_CENTERLIMIT 50
#define TEXT_MARGIN 10
#define TEXT_LINESPACING 2
enum FontEffectFlags {
kFontNormal = 0,
kFontOutline = 1 << 0,
kFontShadow = 1 << 1,
kFontBold = 1 << 2,
kFontCentered = 1 << 3,
kFontDontmap = 1 << 4
};
enum KnownFont {
kKnownFontSmall,
kKnownFontMedium,
kKnownFontBig,
kKnownFontPause,
kKnownFontScript,
kKnownFontVerb
};
struct TextListEntry {
bool display;
bool useRect;
Common::Point point;
Common::Rect rect;
KnownColor knownColor;
KnownColor effectKnownColor;
FontEffectFlags flags;
KnownFont font;
const char *text;
TextListEntry() {
display = false;
useRect = false;
// point initialized by Common::Point constructor
// rect initialized by Common::Rect constructor
knownColor = kKnownColorTransparent;
effectKnownColor = kKnownColorTransparent;
flags = kFontNormal;
font = kKnownFontSmall;
text = nullptr;
}
bool operator==(const TextListEntry &e) const {
return 0 == memcmp(this, &e, sizeof(*this));
}
};
class TextList: public Common::List<TextListEntry> {
public:
TextListEntry *addEntry(const TextListEntry &entry) {
Common::List<TextListEntry>::push_back(entry);
return &*--Common::List<TextListEntry>::end();
}
};
struct FontHeader {
int charHeight;
int charWidth;
int rowLength;
};
struct FontCharEntry {
int index;
int byteWidth;
int width;
int flag;
int tracking;
};
struct FontStyle {
FontHeader header;
FontCharEntry fontCharEntry[256];
#ifndef __DS__
ByteArray font;
#else
byte* font;
#endif
};
struct FontData {
FontStyle normal;
FontStyle outline;
};
class Font {
public:
Font(SagaEngine *vm) : _vm(vm) {}
virtual ~Font() {}
int getStringWidth(KnownFont font, const char *text, size_t count, FontEffectFlags flags) {
return getStringWidth(knownFont2FontIdx(font), text, count, flags);
}
int getHeight(KnownFont font) {
return getHeight(knownFont2FontIdx(font));
}
int getHeight(KnownFont font, const char *text, int width, FontEffectFlags flags) {
return getHeight(knownFont2FontIdx(font), text, width, flags);
}
void textDraw(KnownFont font, const char *string, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) {
textDraw(knownFont2FontIdx(font), string, point, color, effectColor, flags);
}
void textDrawRect(KnownFont font, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) {
textDrawRect(knownFont2FontIdx(font), text, rect, color, effectColor, flags);
}
virtual void setFontMapping(int) {}
protected:
enum FontId {
kSmallFont,
kMediumFont,
kBigFont,
kIHNMUnknown,
kIHNMFont8,
kIHNMUnknown2,
kIHNMMainFont
};
SagaEngine *_vm;
private:
FontId knownFont2FontIdx(KnownFont font);
void textDraw(FontId fontId, const char *string, const Common::Point &point, int color, int effectColor, FontEffectFlags flags);
virtual void textDrawRect(FontId fontId, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) = 0;
virtual int translateChar(int charId) = 0;
virtual int getStringLength(const char *text) = 0;
virtual int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) = 0;
virtual int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags) = 0;
virtual int getHeight(FontId fontId) = 0;
virtual bool valid(FontId) = 0;
virtual void draw(FontId fontId, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) = 0;
};
class DefaultFont : public Font {
public:
DefaultFont(SagaEngine *vm);
~DefaultFont() override;
void setFontMapping(int mapping) override {
_fontMapping = mapping;
}
private:
void textDrawRect(FontId fontId, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) override;
int translateChar(int charId) override;
int getStringLength(const char *text) override {
return strlen(text);
}
int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) override;
int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags) override;
int getHeight(FontId fontId) override {
return getFont(fontId)->normal.header.charHeight;
}
int getHeight(FontId fontId, const char *text);
void validate(FontId fontId) {
if (!valid(fontId)) {
error("Font::validate: Invalid font id");
}
}
bool valid(FontId fontId) override {
return (uint(fontId) < _fonts.size());
}
FontData *getFont(FontId fontId) {
validate(fontId);
return &_fonts[fontId];
}
void draw(FontId fontId, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) override;
void outFont(const FontStyle &drawFont, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags);
void loadFont(FontData *font, uint32 fontResourceId);
void loadFont(FontData *font, const ByteArray& fontResourceData, bool isBigEndian);
void loadChineseFontIHNM(FontData *font, uint32 fontResourceId);
void loadChineseFontITE(const Common::Path &fileName);
void loadKoreanFontIHNM(const Common::Path &fileName);
void saveBig5Index(byte head, byte tail, uint curIdx);
void createOutline(FontData *font);
void blitGlyph(const Common::Point &textPoint, const byte* bitmap, int charWidth, int charHeight, int rowLength, byte color);
int getByteLen(int numBits) const {
int byteLength = numBits / 8;
if (numBits % 8) {
byteLength++;
}
return byteLength;
}
static const int _charMap[128];
Common::Array<FontData> _fonts;
int _fontMapping;
byte *_chineseFont;
Common::Array<int> _chineseFontIndex;
int _cjkFontWidth;
int _cjkFontHeight;
byte *_koreanFont;
static const int kIHNMKoreanGlyphBytes = 32;
static const int kIHNMKoreanInitials = 20;
static const int kIHNMKoreanInitialVariants = 8;
static const int kIHNMKoreanMidOffset = kIHNMKoreanInitialVariants * kIHNMKoreanInitials;
static const int kIHNMKoreanMids = 22;
static const int kIHNMKoreanMidVariants = 4;
static const int kIHNMKoreanFinalsOffset = kIHNMKoreanMidOffset + kIHNMKoreanMids * kIHNMKoreanMidVariants;
static const int kIHNMKoreanFinals = 28;
static const int kIHNMKoreanFinalVariants = 4;
static const int kIHNMKoreanNonJamoOffset = kIHNMKoreanFinalsOffset + kIHNMKoreanFinals * kIHNMKoreanFinalVariants;
static const int kIHNMKoreanNonJamo = 126;
};
class SJISFont : public Font {
public:
SJISFont(SagaEngine *vm);
~SJISFont() override;
private:
void textDrawRect(FontId fontId, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) override;
int translateChar(int charId) override { return charId; }
int getStringLength(const char *text) override;
int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) override;
int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags) override;
int getHeight(FontId fontId) override;
bool valid(FontId fontId) override { return fontId != kBigFont; }
void draw(FontId fontId, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) override;
uint16 fetchChar(const char *&s) const;
bool preventLineBreakForCharacter(uint16 ch) const;
Graphics::FontSJIS *_font;
};
} // End of namespace Saga
#endif

164
engines/saga/font_map.cpp Normal file
View File

@@ -0,0 +1,164 @@
/* 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/>.
*
*/
// Font module character mapping table (MS CP-850 to ISO 8859-1)
// Translation table derived from
// https://web.archive.org/web/20040404103818/http://www.kostis.net/charsets/
#include "saga/saga.h"
#include "saga/font.h"
namespace Saga {
const int DefaultFont::_charMap[128] = {
// Characters 0 - 127 are mapped directly to ISO 8859-1
199, // 128 LATIN CAPITAL LETTER C WITH CEDILLA
252, // 129 LATIN SMALL LETTER U WITH DIAERESIS
233, // 130 LATIN SMALL LETTER E WITH ACUTE
226, // 131 LATIN SMALL LETTER A WITH CIRCUMFLEX
228, // 132 LATIN SMALL LETTER A WITH DIAERESIS
224, // 133 LATIN SMALL LETTER A WITH GRAVE
229, // 134 LATIN SMALL LETTER A WITH RING ABOVE
231, // 135 LATIN SMALL LETTER C WITH CEDILLA
234, // 136 LATIN SMALL LETTER E WITH CIRCUMFLEX
235, // 137 LATIN SMALL LETTER E WITH DIAERESIS
232, // 138 LATIN SMALL LETTER E WITH GRAVE
239, // 139 LATIN SMALL LETTER I WITH DIAERESIS
238, // 140 LATIN SMALL LETTER I WITH CIRCUMFLEX
236, // 141 LATIN SMALL LETTER I WITH GRAVE
196, // 142 LATIN CAPITAL LETTER A WITH DIAERESIS
197, // 143 LATIN CAPITAL LETTER A WITH RING ABOVE
201, // 144 LATIN CAPITAL LETTER E WITH ACUTE
230, // 145 LATIN SMALL LETTER AE
198, // 146 LATIN CAPITAL LETTER AE
244, // 147 LATIN SMALL LETTER O WITH CIRCUMFLEX
246, // 148 LATIN SMALL LETTER O WITH DIAERESIS
242, // 149 LATIN SMALL LETTER O WITH GRAVE
251, // 150 LATIN SMALL LETTER U WITH CIRCUMFLEX
249, // 151 LATIN SMALL LETTER U WITH GRAVE
255, // 152 LATIN SMALL LETTER Y WITH DIAERESIS
214, // 153 LATIN CAPITAL LETTER O WITH DIAERESIS
220, // 154 LATIN CAPITAL LETTER U WITH DIAERESIS
248, // 155 LATIN SMALL LETTER O WITH STROKE
163, // 156 POUND SIGN
216, // 157 LATIN CAPITAL LETTER O WITH STROKE
215, // 158 MULTIPLICATION SIGN
0, // 159 LATIN SMALL LETTER F WITH HOOK
225, // 160 LATIN SMALL LETTER A WITH ACUTE
237, // 161 LATIN SMALL LETTER I WITH ACUTE
243, // 162 LATIN SMALL LETTER O WITH ACUTE
250, // 163 LATIN SMALL LETTER U WITH ACUTE
241, // 164 LATIN SMALL LETTER N WITH TILDE
209, // 165 LATIN CAPITAL LETTER N WITH TILDE
170, // 166 FEMININE ORDINAL INDICATOR
186, // 167 MASCULINE ORDINAL INDICATOR
191, // 168 INVERTED QUESTION MARK
174, // 169 REGISTERED SIGN
172, // 170 NOT SIGN
189, // 171 VULGAR FRACTION ONE HALF
188, // 172 VULGAR FRACTION ONE QUARTER
161, // 173 INVERTED EXCLAMATION MARK
171, // 174 LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
187, // 175 RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
0, // 176 LIGHT SHADE
0, // 177 MEDIUM SHADE
0, // 178 DARK SHADE
0, // 179 BOX DRAWINGS LIGHT VERTICAL
0, // 180 BOX DRAWINGS LIGHT VERTICAL AND LEFT
193, // 181 LATIN CAPITAL LETTER A WITH ACUTE
194, // 182 LATIN CAPITAL LETTER A WITH CIRCUMFLEX
192, // 183 LATIN CAPITAL LETTER A WITH GRAVE
169, // 184 COPYRIGHT SIGN
0, // 185 BOX DRAWINGS DOUBLE VERTICAL AND LEFT
0, // 186 BOX DRAWINGS DOUBLE VERTICAL
0, // 187 BOX DRAWINGS DOUBLE DOWN AND LEFT
0, // 188 BOX DRAWINGS DOUBLE UP AND LEFT
162, // 189 CENT SIGN
165, // 190 YEN SIGN
0, // 191 BOX DRAWINGS LIGHT DOWN AND LEFT
0, // 192 BOX DRAWINGS LIGHT UP AND RIGHT
0, // 193 BOX DRAWINGS LIGHT UP AND HORIZONTAL
0, // 194 BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
0, // 195 BOX DRAWINGS LIGHT VERTICAL AND RIGHT
0, // 196 BOX DRAWINGS LIGHT HORIZONTAL
0, // 197 BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
227, // 198 LATIN SMALL LETTER A WITH TILDE
195, // 199 LATIN CAPITAL LETTER A WITH TILDE
0, // 200 BOX DRAWINGS DOUBLE UP AND RIGHT
0, // 201 BOX DRAWINGS DOUBLE DOWN AND RIGHT
0, // 202 BOX DRAWINGS DOUBLE UP AND HORIZONTAL
0, // 203 BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
0, // 204 BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
0, // 205 BOX DRAWINGS DOUBLE HORIZONTAL
0, // 206 BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
164, // 207 CURRENCY SIGN
240, // 208 LATIN SMALL LETTER ETH
208, // 209 LATIN CAPITAL LETTER ETH
202, // 210 LATIN CAPITAL LETTER E WITH CIRCUMFLEX
203, // 211 LATIN CAPITAL LETTER E WITH DIAERESIS
200, // 212 LATIN CAPITAL LETTER E WITH GRAVE
305, // 213 LATIN SMALL LETTER DOTLESS I
205, // 214 LATIN CAPITAL LETTER I WITH ACUTE
206, // 215 LATIN CAPITAL LETTER I WITH CIRCUMFLEX
207, // 216 LATIN CAPITAL LETTER I WITH DIAERESIS
0, // 217 BOX DRAWINGS LIGHT UP AND LEFT
0, // 218 BOX DRAWINGS LIGHT DOWN AND RIGHT
0, // 219 FULL BLOCK
0, // 220 LOWER HALF BLOCK
166, // 221 BROKEN BAR
204, // 222 LATIN CAPITAL LETTER I WITH GRAVE
0, // 223 UPPER HALF BLOCK
211, // 224 LATIN CAPITAL LETTER O WITH ACUTE
223, // 225 LATIN SMALL LETTER SHARP S
212, // 226 LATIN CAPITAL LETTER O WITH CIRCUMFLEX
210, // 227 LATIN CAPITAL LETTER O WITH GRAVE
245, // 228 LATIN SMALL LETTER O WITH TILDE
213, // 229 LATIN CAPITAL LETTER O WITH TILDE
181, // 230 MICRO SIGN
254, // 231 LATIN SMALL LETTER THORN
222, // 232 LATIN CAPITAL LETTER THORN
218, // 233 LATIN CAPITAL LETTER U WITH ACUTE
219, // 234 LATIN CAPITAL LETTER U WITH CIRCUMFLEX
217, // 235 LATIN CAPITAL LETTER U WITH GRAVE
253, // 236 LATIN SMALL LETTER Y WITH ACUTE
221, // 237 LATIN CAPITAL LETTER Y WITH ACUTE
175, // 238 MACRON
180, // 239 ACUTE ACCENT
173, // 240 SOFT HYPHEN
177, // 241 PLUS-MINUS SIGN
0, // 242 DOUBLE LOW LINE
190, // 243 VULGAR FRACTION THREE QUARTERS
182, // 244 PILCROW SIGN
167, // 245 SECTION SIGN
247, // 246 DIVISION SIGN
184, // 247 CEDILLA
176, // 248 DEGREE SIGN
168, // 249 DIAERESIS
183, // 250 MIDDLE DOT
185, // 251 SUPERSCRIPT ONE
179, // 252 SUPERSCRIPT THREE
178, // 253 SUPERSCRIPT TWO
0, // 254 BLACK SQUARE
160 // 255 NO-BREAK SPACE
};
} // End of namespace Saga

706
engines/saga/gfx.cpp Normal file
View File

@@ -0,0 +1,706 @@
/* 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/>.
*
*/
// Misc. graphics routines
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/interface.h"
#include "saga/resource.h"
#include "saga/scene.h"
#include "saga/render.h"
#include "common/system.h"
#include "graphics/cursorman.h"
#include "graphics/paletteman.h"
#include "engines/util.h"
namespace Saga {
#define RID_IHNM_DEFAULT_PALETTE 1
#define RID_IHNM_HOURGLASS_CURSOR 11 // not in demo
Gfx::Gfx(SagaEngine *vm, OSystem *system, int width, int height) : _vm(vm), _system(system) {
if (vm->getLanguage() == Common::JA_JPN)
initGraphics(width << 1, height << 1);
else
initGraphics(width, height);
debug(5, "Init screen %dx%d", width, height);
// Convert surface data to R surface data
_backBuffer.create(width, height, Graphics::PixelFormat::createFormatCLUT8());
if (vm->getLanguage() == Common::JA_JPN)
_sjisBackBuffer.create(width << 1, height << 1, Graphics::PixelFormat::createFormatCLUT8());
// Start with the cursor shown. It will be hidden before the intro, if
// there is an intro. (With boot params, there may not be.)
setCursor(kCursorNormal);
showCursor(true);
}
Gfx::~Gfx() {
_backBuffer.free();
_sjisBackBuffer.free();
}
#ifdef SAGA_DEBUG
void Surface::drawPalette() {
int x;
int y;
int color = 0;
Rect palRect;
for (y = 0; y < 16; y++) {
palRect.top = (y * 8) + 4;
palRect.bottom = palRect.top + 8;
for (x = 0; x < 16; x++) {
palRect.left = (x * 8) + 4;
palRect.right = palRect.left + 8;
drawRect(palRect, color);
color++;
}
}
}
#endif
// * Copies a rectangle from a raw 8 bit pixel buffer to the specified surface.
// - The surface must match the logical dimensions of the buffer exactly.
void Surface::blit(const Common::Rect &destRect, const byte *sourceBuffer) {
const byte *readPointer;
byte *writePointer;
int row;
ClipData clipData;
clipData.sourceRect.left = 0;
clipData.sourceRect.top = 0;
clipData.sourceRect.right = destRect.width();
clipData.sourceRect.bottom = destRect.height();
clipData.destPoint.x = destRect.left;
clipData.destPoint.y = destRect.top;
clipData.destRect.left = 0;
clipData.destRect.right = w;
clipData.destRect.top = 0;
clipData.destRect.bottom = h;
if (!clipData.calcClip()) {
return;
}
// Transfer buffer data to surface
readPointer = (sourceBuffer + clipData.drawSource.x) +
(clipData.sourceRect.right * clipData.drawSource.y);
writePointer = ((byte *)pixels + clipData.drawDest.x) + (pitch * clipData.drawDest.y);
for (row = 0; row < clipData.drawHeight; row++) {
memcpy(writePointer, readPointer, clipData.drawWidth);
writePointer += pitch;
readPointer += clipData.sourceRect.right;
}
}
void Surface::drawPolyLine(const Point *points, int count, int color) {
int i;
if (count >= 3) {
for (i = 1; i < count; i++) {
drawLine(points[i].x, points[i].y, points[i - 1].x, points[i - 1].y, color);
}
drawLine(points[count - 1].x, points[count - 1].y, points[0].x, points[0].y, color);
}
}
// Dissolve one image with another. If flags is set to 1, do zero masking.
void Surface::transitionDissolve(const byte *sourceBuffer, const Common::Rect &sourceRect, int flags, double percent) {
#define XOR_MASK 0xB400;
int pixelcount = w * h;
int seqlimit = (int)(65535 * percent);
int seq = 1;
int i, x1, y1;
byte color;
for (i = 0; i < seqlimit; i++) {
if (seq & 1) {
seq = (seq >> 1) ^ XOR_MASK;
} else {
seq = seq >> 1;
}
if (seq == 1) {
return;
}
if (seq >= pixelcount) {
continue;
} else {
x1 = seq % w;
y1 = seq / w;
if (sourceRect.contains(x1, y1)) {
color = sourceBuffer[(x1-sourceRect.left) + sourceRect.width()*(y1-sourceRect.top)];
if (flags == 0 || color)
((byte *)pixels)[seq] = color;
}
}
}
}
void Gfx::initPalette() {
if (_vm->getGameId() == GID_ITE)
return;
ResourceContext *resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
if (resourceContext == nullptr) {
error("Resource::loadGlobalResources() resource context not found");
}
ByteArray resourceData;
_vm->_resource->loadResource(resourceContext, RID_IHNM_DEFAULT_PALETTE, resourceData);
ByteArrayReadStreamEndian metaS(resourceData);
for (int i = 0; i < 256; i++) {
_globalPalette[i].red = metaS.readByte();
_globalPalette[i].green = metaS.readByte();
_globalPalette[i].blue = metaS.readByte();
}
setPalette(_globalPalette, true);
}
void Gfx::loadECSExtraPalettes() {
if (!_vm->isECS())
return;
static const PalEntry ecsExtraPal[64] = {
// Bottom palette
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00 },
{ 0x33, 0x11, 0x11 },
{ 0x00, 0x44, 0x33 },
{ 0x55, 0x33, 0x22 },
{ 0x33, 0xdd, 0x44 },
{ 0x44, 0x00, 0x66 },
{ 0x77, 0x22, 0x00 },
{ 0x22, 0x55, 0xaa },
{ 0x77, 0x55, 0x44 },
{ 0x66, 0x11, 0x88 },
{ 0xBB, 0x55, 0x22 },
{ 0x88, 0x88, 0x66 },
{ 0xEE, 0x77, 0x33 },
{ 0xCC, 0xBB, 0x66 },
{ 0xFF, 0xFF, 0xFF },
{ 0x00, 0x00, 0x00 },
{ 0x33, 0x00, 0x44 },
{ 0x33, 0x33, 0x55 },
{ 0x22, 0x00, 0x77 },
{ 0x00, 0x33, 0x77 },
{ 0x00, 0x44, 0x99 },
{ 0x66, 0x66, 0x88 },
{ 0x00, 0x55, 0xBB },
{ 0x44, 0x77, 0xBB },
{ 0x00, 0x77, 0xDD },
{ 0x55, 0x99, 0xCC },
{ 0x55, 0x99, 0xCC },
{ 0xDD, 0x99, 0x66 },
{ 0xCC, 0xBB, 0x99 },
{ 0xAA, 0xCC, 0xCC },
{ 0xBB, 0xCC, 0xCC },
// Options palette
{ 0x00, 0x00, 0x00 },
{ 0x00, 0x33, 0x77 },
{ 0x00, 0x55, 0xbb },
{ 0x00, 0x44, 0x33 },
{ 0x44, 0x77, 0xbb },
{ 0x00, 0x66, 0x44 },
{ 0x44, 0x00, 0x66 },
{ 0x77, 0x22, 0x00 },
{ 0x00, 0x33, 0x77 },
{ 0x55, 0x99, 0xcc },
{ 0x66, 0x11, 0x88 },
{ 0xbb, 0x55, 0x22 },
{ 0x55, 0x99, 0xcc },
{ 0xee, 0x77, 0x33 },
{ 0xcc, 0xbb, 0x66 },
{ 0xff, 0xff, 0xff },
{ 0x00, 0x00, 0x00 },
{ 0x22, 0x22, 0x44 },
{ 0x33, 0x33, 0x55 },
{ 0x22, 0x00, 0x77 },
{ 0x00, 0x33, 0x77 },
{ 0x00, 0x44, 0x99 },
{ 0x44, 0x11, 0x99 },
{ 0x00, 0x55, 0xbb },
{ 0x22, 0x66, 0xbb },
{ 0x44, 0x77, 0xbb },
{ 0x00, 0x77, 0xdd },
{ 0x55, 0x99, 0xcc },
{ 0x77, 0xaa, 0xdd },
{ 0x88, 0xbb, 0xdd },
{ 0xbb, 0xc3, 0xcf },
{ 0xcc, 0xee, 0xff },
// Here you can add more colors if it simplifies the code
};
int i;
for (i = 0; i < ARRAYSIZE(ecsExtraPal); i++) {
_currentPal[(i + 32) * 3] = _globalPalette[i + 32].red = ecsExtraPal[i].red;
_currentPal[(i + 32) * 3 + 1] = _globalPalette[i + 32].green = ecsExtraPal[i].green;
_currentPal[(i + 32) * 3 + 2] = _globalPalette[i + 32].blue = ecsExtraPal[i].blue;
}
for (i += 32; i < PAL_ENTRIES; i++) {
_currentPal[i * 3] = _globalPalette[i].red = 0;
_currentPal[i * 3 + 1] = _globalPalette[i].green = 0;
_currentPal[i * 3 + 2] = _globalPalette[i].blue = 0;
}
}
void Gfx::setPalette(const PalEntry *pal, bool full) {
int i;
byte *ppal;
int from, numcolors;
if (_vm->getGameId() == GID_ITE || full) {
from = 0;
numcolors = _vm->getPalNumEntries();
} else {
from = 0;
numcolors = 248;
}
for (i = 0, ppal = &_currentPal[from * 3]; i < numcolors; i++, ppal += 3) {
ppal[0] = _globalPalette[i].red = pal[i].red;
ppal[1] = _globalPalette[i].green = pal[i].green;
ppal[2] = _globalPalette[i].blue = pal[i].blue;
}
if (_vm->isECS()) {
loadECSExtraPalettes();
}
// Color 0 should always be black in IHNM
if (_vm->getGameId() == GID_IHNM)
memset(&_currentPal[0 * 3], 0, 3);
// Make 256th color black. See bug #2120
if ((_vm->getPlatform() == Common::kPlatformMacintosh) && !_vm->_scene->isInIntro())
memset(&_currentPal[255 * 3], 0, 3);
_system->getPaletteManager()->setPalette(_currentPal, 0, PAL_ENTRIES);
}
void Gfx::setPaletteColor(int n, int r, int g, int b) {
bool update = false;
// This function may get called a lot. To avoid forcing full-screen
// updates, only update the palette if the color actually changes.
if (_currentPal[3 * n + 0] != r) {
_currentPal[3 * n + 0] = _globalPalette[n].red = r;
update = true;
}
if (_currentPal[3 * n + 1] != g) {
_currentPal[3 * n + 1] = _globalPalette[n].green = g;
update = true;
}
if (_currentPal[3 * n + 2] != b) {
_currentPal[3 * n + 2] = _globalPalette[n].blue = b;
update = true;
}
if (update)
_system->getPaletteManager()->setPalette(_currentPal + n * 3, n, 1);
}
void Gfx::getCurrentPal(PalEntry *src_pal) {
int i;
byte *ppal;
for (i = 0, ppal = _currentPal; i < PAL_ENTRIES; i++, ppal += 3) {
src_pal[i].red = ppal[0];
src_pal[i].green = ppal[1];
src_pal[i].blue = ppal[2];
}
}
void Gfx::palToBlack(PalEntry *srcPal, double percent) {
int i;
//int fade_max = 255;
int new_entry;
byte *ppal;
PalEntry *palE;
int from, numcolors;
double fpercent;
if (_vm->getGameId() == GID_ITE) {
from = 0;
numcolors = _vm->getPalNumEntries();
} else {
from = 0;
numcolors = 248;
}
if (percent > 1.0) {
percent = 1.0;
}
// Exponential fade
fpercent = percent * percent;
fpercent = 1.0 - fpercent;
// Use the correct percentage change per frame for each palette entry
for (i = 0, ppal = _currentPal; i < (int) _vm->getPalNumEntries(); i++, ppal += 3) {
if (i < from || i >= from + numcolors)
palE = &_globalPalette[i];
else
palE = &srcPal[i];
new_entry = (int)(palE->red * fpercent);
if (new_entry < 0) {
ppal[0] = 0;
} else {
ppal[0] = (byte) new_entry;
}
new_entry = (int)(palE->green * fpercent);
if (new_entry < 0) {
ppal[1] = 0;
} else {
ppal[1] = (byte) new_entry;
}
new_entry = (int)(palE->blue * fpercent);
if (new_entry < 0) {
ppal[2] = 0;
} else {
ppal[2] = (byte) new_entry;
}
}
// Color 0 should always be black in IHNM
if (_vm->getGameId() == GID_IHNM)
memset(&_currentPal[0 * 3], 0, 3);
// Make 256th color black. See bug #2120
if ((_vm->getPlatform() == Common::kPlatformMacintosh) && !_vm->_scene->isInIntro())
memset(&_currentPal[255 * 3], 0, 3);
_system->getPaletteManager()->setPalette(_currentPal, 0, PAL_ENTRIES);
}
void Gfx::blackToPal(PalEntry *srcPal, double percent) {
int new_entry;
double fpercent;
byte *ppal;
int i;
PalEntry *palE;
int from, numcolors;
if (_vm->getGameId() == GID_ITE) {
from = 0;
numcolors = _vm->getPalNumEntries();
} else {
from = 0;
numcolors = 248;
}
if (percent > 1.0) {
percent = 1.0;
}
// Exponential fade
fpercent = percent * percent;
// Use the correct percentage change per frame for each palette entry
for (i = 0, ppal = _currentPal; i < (int) _vm->getPalNumEntries(); i++, ppal += 3) {
if (i < from || i >= from + numcolors)
palE = &_globalPalette[i];
else
palE = &srcPal[i];
new_entry = (int)(palE->red * fpercent);
if (new_entry < 0) {
ppal[0] = 0;
} else {
ppal[0] = (byte)new_entry;
}
new_entry = (int)(palE->green * fpercent);
if (new_entry < 0) {
ppal[1] = 0;
} else {
ppal[1] = (byte) new_entry;
}
new_entry = (int)(palE->blue * fpercent);
if (new_entry < 0) {
ppal[2] = 0;
} else {
ppal[2] = (byte) new_entry;
}
}
// Color 0 should always be black in IHNM
if (_vm->getGameId() == GID_IHNM)
memset(&_currentPal[0 * 3], 0, 3);
// Make 256th color black. See bug #2120
if ((_vm->getPlatform() == Common::kPlatformMacintosh) && !_vm->_scene->isInIntro())
memset(&_currentPal[255 * 3], 0, 3);
_system->getPaletteManager()->setPalette(_currentPal, 0, PAL_ENTRIES);
}
#ifdef ENABLE_IHNM
// Used in IHNM only
void Gfx::palFade(PalEntry *srcPal, int16 from, int16 to, int16 start, int16 numColors, double percent) {
int i;
int new_entry;
byte *ppal;
PalEntry *palE;
from = CLIP<int16>(from, 0, 256);
to = CLIP<int16>(to, 0, 256);
if (from == 0 || to == 0) {
// This case works like palToBlack or blackToPal, so no changes are needed
} else {
double x = from > to ? from / to : to / from;
percent /= x;
if (from < to)
percent += 1 / x;
}
percent = percent > 1.0 ? 1.0 : percent;
if (from > to)
percent = 1.0 - percent;
byte fadePal[PAL_ENTRIES * 3];
// Use the correct percentage change per frame for each palette entry
for (i = start, ppal = fadePal + start * 3; i < start + numColors; i++, ppal += 3) {
palE = &srcPal[i];
new_entry = (int)(palE->red * percent);
if (new_entry < 0) {
ppal[0] = 0;
} else {
ppal[0] = (byte) new_entry;
}
new_entry = (int)(palE->green * percent);
if (new_entry < 0) {
ppal[1] = 0;
} else {
ppal[1] = (byte) new_entry;
}
new_entry = (int)(palE->blue * percent);
if (new_entry < 0) {
ppal[2] = 0;
} else {
ppal[2] = (byte) new_entry;
}
}
// Color 0 should always be black in IHNM
memset(&fadePal[0 * 3], 0, 3);
_system->getPaletteManager()->setPalette(&fadePal[start * 3], start, numColors);
}
#endif
void Gfx::showCursor(bool state) {
// Don't show the mouse cursor in the non-interactive part of the IHNM demo
if (_vm->_scene->isNonInteractiveIHNMDemoPart())
state = false;
CursorMan.showMouse(state);
}
void Gfx::setCursor(CursorType cursorType) {
if (_vm->getGameId() == GID_ITE) {
// Set up the mouse cursor
const byte A = _vm->isECS() ? kITEECSColorWhite : kITEDOSColorLightGrey;
const byte B = _vm->isECS() ? kITEECSColorTransBlack : kITEDOSColorWhite;
const byte cursor_img_default[CURSOR_W * CURSOR_H] = {
0, 0, 0, A, 0, 0, 0,
0, 0, 0, A, 0, 0, 0,
0, 0, 0, A, 0, 0, 0,
A, A, A, B, A, A, A,
0, 0, 0, A, 0, 0, 0,
0, 0, 0, A, 0, 0, 0,
0, 0, 0, A, 0, 0, 0,
};
const byte cursor_img_pc98[CURSOR_PC98_W * CURSOR_PC98_H] = {
A, A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
A, B, A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
A, B, B, A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
A, B, B, B, A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
A, B, B, B, B, A, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
A, B, B, B, B, B, A, 0, 0, 0, 0, 0, 0, 0, 0, 0,
A, B, B, B, B, B, B, A, 0, 0, 0, 0, 0, 0, 0, 0,
A, B, B, B, B, B, B, B, A, 0, 0, 0, 0, 0, 0, 0,
A, B, B, B, B, B, B, B, B, A, 0, 0, 0, 0, 0, 0,
A, B, B, B, B, B, B, B, B, B, A, 0, 0, 0, 0, 0,
A, B, B, B, B, B, B, B, B, B, B, A, 0, 0, 0, 0,
A, B, B, B, B, B, B, B, B, B, B, B, A, 0, 0, 0,
A, B, B, B, B, B, B, B, B, B, B, B, B, A, 0, 0,
A, A, A, A, A, A, B, B, B, A, A, A, A, A, A, 0,
0, 0, 0, 0, 0, 0, A, B, B, B, A, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, A, A, A, A, A, 0, 0, 0, 0
};
if (_vm->getPlatform() == Common::kPlatformPC98)
CursorMan.replaceCursor(cursor_img_pc98, CURSOR_PC98_W, CURSOR_PC98_H, 0, 0, 0);
else
CursorMan.replaceCursor(cursor_img_default, CURSOR_W, CURSOR_H, 3, 3, 0);
} else {
uint32 resourceId;
switch (cursorType) {
case kCursorBusy:
if (!_vm->isIHNMDemo())
resourceId = RID_IHNM_HOURGLASS_CURSOR;
else
resourceId = (uint32)-1;
break;
default:
resourceId = (uint32)-1;
break;
}
ByteArray resourceData;
ByteArray image;
int width, height;
if (resourceId != (uint32)-1) {
ResourceContext *context = _vm->_resource->getContext(GAME_RESOURCEFILE);
_vm->_resource->loadResource(context, resourceId, resourceData);
_vm->decodeBGImage(resourceData, image, &width, &height);
} else {
width = height = 31;
image.resize(width * height);
for (int i = 0; i < 14; i++) {
image[15 * 31 + i] = 1;
image[15 * 31 + 30 - i] = 1;
image[i * 31 + 15] = 1;
image[(30 - i) * 31 + 15] = 1;
}
}
// Note: Hard-coded hotspot
CursorMan.replaceCursor(image.getBuffer(), width, height, 15, 15, 0);
}
}
bool hitTestPoly(const Point *points, unsigned int npoints, const Point& test_point) {
int yflag0;
int yflag1;
bool inside_flag = false;
unsigned int pt;
const Point *vtx0 = &points[npoints - 1];
const Point *vtx1 = &points[0];
yflag0 = (vtx0->y >= test_point.y);
for (pt = 0; pt < npoints; pt++, vtx1++) {
yflag1 = (vtx1->y >= test_point.y);
if (yflag0 != yflag1) {
if (((vtx1->y - test_point.y) * (vtx0->x - vtx1->x) >=
(vtx1->x - test_point.x) * (vtx0->y - vtx1->y)) == yflag1) {
inside_flag = !inside_flag;
}
}
yflag0 = yflag1;
vtx0 = vtx1;
}
return inside_flag;
}
// This method adds a dirty rectangle automatically
void Gfx::drawFrame(const Common::Point &p1, const Common::Point &p2, int color) {
Common::Rect rect(MIN(p1.x, p2.x), MIN(p1.y, p2.y), MAX(p1.x, p2.x) + 1, MAX(p1.y, p2.y) + 1);
_backBuffer.frameRect(rect, color);
_vm->_render->addDirtyRect(rect);
}
// This method adds a dirty rectangle automatically
void Gfx::drawRect(const Common::Rect &destRect, int color) {
_backBuffer.drawRect(destRect, color);
_sjisBackBuffer.clearRect2x(destRect);
_vm->_render->addDirtyRect(destRect);
}
// This method adds a dirty rectangle automatically
void Gfx::fillRect(const Common::Rect &destRect, uint32 color) {
_backBuffer.fillRect(destRect, color);
// Clear corresponding area of the sjis text layer (if the pixels buffer was actually created)
_sjisBackBuffer.clearRect2x(destRect);
_vm->_render->addDirtyRect(destRect);
}
// This method adds a dirty rectangle automatically
void Gfx::drawRegion(const Common::Rect &destRect, const byte *sourceBuffer) {
_backBuffer.blit(destRect, sourceBuffer);
// Clear corresponding area of the sjis text layer (if the pixels buffer was actually created)
_sjisBackBuffer.clearRect2x(destRect);
_vm->_render->addDirtyRect(destRect);
}
// This method does not add a dirty rectangle automatically
void Gfx::drawBgRegion(const Common::Rect &destRect, const byte *sourceBuffer) {
_backBuffer.blit(destRect, sourceBuffer);
// Clear corresponding area of the sjis text layer (if the pixels buffer was actually created)
_sjisBackBuffer.clearRect2x(destRect);
}
} // End of namespace Saga

279
engines/saga/gfx.h Normal file
View File

@@ -0,0 +1,279 @@
/* 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/>.
*
*/
// Graphics maniuplation routines - private header file
#ifndef SAGA_GFX_H
#define SAGA_GFX_H
#include "common/rect.h"
#include "graphics/surface.h"
namespace Saga {
using Common::Point;
using Common::Rect;
enum CursorType {
kCursorNormal,
kCursorBusy
};
struct ClipData {
// input members
Rect sourceRect;
Rect destRect;
Point destPoint;
// output members
Point drawSource;
Point drawDest;
int drawWidth;
int drawHeight;
bool calcClip() {
Common::Rect s;
// Adjust the rect to draw to its screen coordinates
s = sourceRect;
s.left += destPoint.x;
s.right += destPoint.x;
s.top += destPoint.y;
s.bottom += destPoint.y;
s.clip(destRect);
if ((s.width() <= 0) || (s.height() <= 0)) {
return false;
}
drawSource.x = s.left - sourceRect.left - destPoint.x;
drawSource.y = s.top - sourceRect.top - destPoint.y;
drawDest.x = s.left;
drawDest.y = s.top;
drawWidth = s.width();
drawHeight = s.height();
return true;
}
};
#include "common/pack-start.h" // START STRUCT PACKING
struct PalEntry {
byte red;
byte green;
byte blue;
} PACKED_STRUCT;
#include "common/pack-end.h" // END STRUCT PACKING
struct Color {
int red;
int green;
int blue;
int alpha;
};
struct Surface : Graphics::Surface {
void transitionDissolve(const byte *sourceBuffer, const Common::Rect &sourceRect, int flags, double percent);
void drawPalette();
void drawPolyLine(const Point *points, int count, int color);
void blit(const Common::Rect &destRect, const byte *sourceBuffer);
void getRect(Common::Rect &rect) {
rect.left = rect.top = 0;
rect.right = w;
rect.bottom = h;
}
void drawRect(const Common::Rect &destRect, int color) {
Common::Rect rect(w , h);
rect.clip(destRect);
if (rect.isValidRect()) {
fillRect(rect, color);
}
}
void clearRect2x(Common::Rect r) {
// This clears a 2x scaled rect (used for Japanese font removal).
// The pixels buffer only gets allocated for game versions that actually require it.
if (!pixels)
return;
fillRect(Common::Rect(r.left << 1, r.top << 1, r.right << 1, r.bottom << 1), 0);
}
};
#define PAL_ENTRIES 256
#define CURSOR_W 7
#define CURSOR_H 7
#define CURSOR_PC98_W 16
#define CURSOR_PC98_H 16
#define CURSOR_ORIGIN_X 4
#define CURSOR_ORIGIN_Y 4
bool hitTestPoly(const Point *points, unsigned int npoints, const Point& test_point);
class SagaEngine;
class Gfx {
public:
Gfx(SagaEngine *vm, OSystem *system, int width, int height);
~Gfx();
void initPalette();
void setPalette(const PalEntry *pal, bool full = false);
void loadECSExtraPalettes();
void setPaletteColor(int n, int r, int g, int b);
void getCurrentPal(PalEntry *src_pal);
void savePalette() { getCurrentPal(_savedPalette); }
void restorePalette() { setPalette(_savedPalette, true); }
void palToBlack(PalEntry *src_pal, double percent);
void blackToPal(PalEntry *src_pal, double percent);
void palFade(PalEntry *srcPal, int16 from, int16 to, int16 start, int16 numColors, double percent);
void showCursor(bool state);
void setCursor(CursorType cursorType = kCursorNormal);
// Back buffer access methods. These all take care of adding the necessary dirty rectangles
// APART FROM setPixelColor() and getBackBufferPixels()
// This method adds a dirty rectangle automatically
void drawFrame(const Common::Point &p1, const Common::Point &p2, int color);
// This method adds a dirty rectangle automatically
void drawRect(const Common::Rect &destRect, int color);
// This method adds a dirty rectangle automatically
void fillRect(const Common::Rect &destRect, uint32 color);
// This method adds a dirty rectangle automatically
void drawRegion(const Common::Rect &destRect, const byte *sourceBuffer);
// This method does not add a dirty rectangle automatically
void drawBgRegion(const Common::Rect &destRect, const byte *sourceBuffer);
// Used for testing
void drawPalette() {
_backBuffer.drawPalette();
}
// WARNING: This method does not add a dirty rectangle automatically.
// Whenever it gets called, the corresponding caller must take care
// to add the corresponding dirty rectangle itself
void hLine(int x, int y, int x2, uint32 color) {
_backBuffer.hLine(x, y, x2, color);
// Clear corresponding area of the sjis text layer (if the pixels buffer was actually created)
_sjisBackBuffer.clearRect2x(Common::Rect(x, y, x2, y + 1));
}
// WARNING: This method does not add a dirty rectangle automatically.
// Whenever it gets called, the corresponding caller must take care
// to add the corresponding dirty rectangle itself
void vLine(int x, int y, int y2, uint32 color) {
_backBuffer.vLine(x, y, y2, color);
// Clear corresponding area of the sjis text layer (if the pixels buffer was actually created)
_sjisBackBuffer.clearRect2x(Common::Rect(x, y, x + 1, y2));
}
// WARNING: This method does not add a dirty rectangle automatically.
// Whenever it gets called, the corresponding caller must take care
// to add the corresponding dirty rectangle itself
void setPixelColor(int x, int y, byte color) {
((byte *)_backBuffer.getBasePtr(x, y))[0] = color;
// Clear corresponding area of the sjis text layer (if the pixels buffer was actually created)
if (_sjisBackBuffer.getPixels()) {
*((uint16 *)_sjisBackBuffer.getBasePtr(x << 1, y << 1)) = 0;
*((uint16 *)_sjisBackBuffer.getBasePtr(x << 1, (y << 1) + 1)) = 0;
}
}
// WARNING: This method does not add a dirty rectangle automatically.
// Whenever it gets called, the corresponding caller must take care
// to add the corresponding dirty rectangle itself
void drawPolyLine(const Common::Point *points, int count, int color) {
_backBuffer.drawPolyLine(points, count, color);
}
// WARNING: This method allows direct modification of the back buffer
// Whenever it gets called, the corresponding caller must take care
// to add the corresponding dirty rectangle itself
byte *getBackBufferPixels() {
return (byte *)_backBuffer.getPixels();
}
// Same as getBackBufferPixels(), but for the hires sjis buffer
byte *getSJISBackBufferPixels() {
return (byte *)_sjisBackBuffer.getPixels();
}
// Expose the sjis buffer directly. One of the two implementations of Graphics::FontSJIS::drawChar()
// allows a Common::Surface as a parameter which makes the rendering a bit nicer compared to using
// the raw pixel buffer.
Surface &getSJISBackBuffer() {
return _sjisBackBuffer;
}
uint16 getBackBufferWidth() {
return _backBuffer.w;
}
uint16 getSJISBackBufferWidth() {
return _sjisBackBuffer.w;
}
uint16 getBackBufferHeight() {
return _backBuffer.h;
}
uint16 getSJISBackBufferHeight() {
return _sjisBackBuffer.h;
}
uint16 getBackBufferPitch() {
return _backBuffer.pitch;
}
uint16 getSJISBackBufferPitch() {
return _sjisBackBuffer.pitch;
}
void getBackBufferRect(Common::Rect &rect) {
_backBuffer.getRect(rect);
}
private:
Surface _backBuffer;
Surface _sjisBackBuffer;
byte _currentPal[PAL_ENTRIES * 3];
OSystem *_system;
SagaEngine *_vm;
PalEntry _globalPalette[PAL_ENTRIES];
PalEntry _savedPalette[PAL_ENTRIES];
};
} // End of namespace Saga
#endif

505
engines/saga/image.cpp Normal file
View File

@@ -0,0 +1,505 @@
/* 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/>.
*
*/
// SAGA Image resource management routines
#include "saga/saga.h"
#include "common/compression/powerpacker.h"
namespace Saga {
static int granulate(int value, int granularity) {
int remainder;
if (value == 0)
return 0;
if (granularity == 0)
return 0;
remainder = value % granularity;
if (remainder == 0) {
return value;
} else {
return (granularity - remainder + value);
}
}
static bool unbankAmiga(ByteArray& outputBuffer, const byte *banked, uint len, uint16 height, uint16 width, uint bitnum) {
uint planePitch = (width + 15) & ~15;
uint linePitch = bitnum == 8 ? planePitch : (planePitch * 5 / 8);
if (len != linePitch * height)
return false;
outputBuffer.resize(height * width);
memset(outputBuffer.getBuffer(), 0, width * height);
for (uint y = 0; y < height; y++)
for (uint x = 0; x < width; x++)
for (unsigned bit = 0; bit < bitnum; bit++) {
int inXbit = x + bit * planePitch;
outputBuffer[y * width + x] |= ((banked[y * linePitch + inXbit / 8] >> (7 - inXbit % 8)) & 1) << bit;
}
return true;
}
bool SagaEngine::decodeBGImageMask(const ByteArray &imageData, ByteArray &outputBuffer, int *w, int *h, bool flip) {
if (isAGA() || isECS()) {
if (imageData.size() < 160 * 137 + 64)
return false;
*w = 320;
*h = 137;
outputBuffer.resize(320*137);
// First read types
for (int i = 0; i < 160*137; i++) {
outputBuffer[2 * i] = (imageData[i] << 4) | 0xf;
outputBuffer[2 * i + 1] = (imageData[i] << 4) | 0xf;
}
// Now instead of storing depth amiga variant stores precomputed mask for every
// depth. Obviously not every set of precomputed masks is valid but we assume
// that it is. So far it has always been the case. If ever it isn't then we'll
// get a minor graphical glitch
for (int depth = 15; depth > 0; depth--) {
uint32 off = READ_BE_UINT32(&imageData[160 * 137 + 4 * (15 - depth)]);
if (off == 0)
continue;
off += 160 * 137;
if (imageData.size() < off + 137 * 40)
return false;
for (int y = 0; y < 137; y++)
for (int x = 0; x < 320; x++)
if ((imageData[y * 40 + (x / 8) + off] << (x % 8)) & 0x80)
outputBuffer[y * 320 + x] = (outputBuffer[y * 320 + x] & 0xf0) | (depth - 1);
}
return true;
}
return decodeBGImage(imageData, outputBuffer, w, h, flip);
}
bool SagaEngine::decodeBGImage(const ByteArray &imageData, ByteArray &outputBuffer, int *w, int *h, bool flip) {
ImageHeader hdr;
ByteArray decodeBuffer;
if (imageData.size() <= SAGA_IMAGE_DATA_OFFSET) {
error("decodeBGImage() Image size is way too small (%d)", (int)imageData.size());
}
ByteArrayReadStreamEndian readS(imageData, isBigEndian());
hdr.width = readS.readUint16();
hdr.height = readS.readUint16();
// The next four bytes of the image header aren't used.
readS.readUint16();
readS.readUint16();
if (isAGA() || isECS()) {
unsigned bitnum = isAGA() ? 8 : 5;
int headerSize = 8 + (3 << bitnum);
const byte *RLE_data_ptr = &imageData.front() + headerSize;
size_t RLE_data_len = imageData.size() - headerSize;
if (getFeatures() & GF_POWERPACK_GFX) {
int aligned_width = (hdr.width + 15) & ~15;
int pitch = isAGA() ? aligned_width : (aligned_width * 5 / 8);
if (RLE_data_len < 12) {
warning("Compressed size too short");
return false;
}
if (READ_BE_UINT32 (RLE_data_ptr) != RLE_data_len - 4) {
warning("Compressed size mismatch: %d vs %d", READ_BE_UINT32 (RLE_data_ptr), (int)RLE_data_len - 4);
return false;
}
if (READ_BE_UINT32 (RLE_data_ptr + 4) != MKTAG('P', 'A', 'C', 'K')) {
warning("Compressed magic mismatch: 0x%08x vs %08x", READ_BE_UINT32 (RLE_data_ptr + 4), MKTAG('P', 'A', 'C', 'K'));
return false;
}
uint32 uncompressed_len = 0;
byte *uncompressed = Common::PowerPackerStream::unpackBuffer(RLE_data_ptr + 4, RLE_data_len - 4, uncompressed_len);
if (uncompressed == nullptr || (int) uncompressed_len != pitch * hdr.height) {
warning("Uncompressed size mismatch: %d vs %d", uncompressed_len, pitch * hdr.height);
delete[] uncompressed;
return false;
}
if (isAGA() && pitch == hdr.width) {
// TODO: Use some kind of move semantics
outputBuffer = ByteArray(uncompressed, uncompressed_len);
} else if (isAGA()) {
outputBuffer.resize(hdr.height * hdr.width);
for (int y = 0; y < hdr.height; y++)
memcpy(outputBuffer.getBuffer() + y * hdr.width, uncompressed + y * pitch, hdr.width);
} else {
if (!unbankAmiga(outputBuffer, uncompressed, uncompressed_len, hdr.height, hdr.width, bitnum)) {
delete[] uncompressed;
return false;
}
}
delete[] uncompressed;
} else {
if (!unbankAmiga(outputBuffer, RLE_data_ptr, RLE_data_len, hdr.height, hdr.width, bitnum))
return false;
}
} else {
int modex_height = granulate(hdr.height, 4);
const byte *RLE_data_ptr;
size_t RLE_data_len;
decodeBuffer.resize(hdr.width * modex_height);
RLE_data_ptr = &imageData.front() + SAGA_IMAGE_DATA_OFFSET;
RLE_data_len = imageData.size() - SAGA_IMAGE_DATA_OFFSET;
if (!decodeBGImageRLE(RLE_data_ptr, RLE_data_len, decodeBuffer)) {
return false;
}
outputBuffer.resize(hdr.width * hdr.height);
unbankBGImage(outputBuffer.getBuffer(), decodeBuffer.getBuffer(), hdr.width, hdr.height);
}
// For some reason bg images in IHNM are upside down
if (getGameId() == GID_IHNM && !flip) {
flipImage(outputBuffer.getBuffer(), hdr.width, hdr.height);
}
*w = hdr.width;
*h = hdr.height;
return true;
}
bool SagaEngine::decodeBGImageRLE(const byte *inbuf, size_t inbuf_len, ByteArray &outbuf) {
const byte *inbuf_ptr;
byte *outbuf_ptr;
byte *outbuf_start;
uint32 inbuf_remain;
const byte *inbuf_end;
byte *outbuf_end;
uint32 outbuf_remain;
byte mark_byte;
int test_byte;
uint32 runcount;
byte bitfield;
byte bitfield_byte1;
byte bitfield_byte2;
byte *backtrack_ptr;
int backtrack_amount;
uint16 c, b;
int decode_err = 0;
inbuf_ptr = inbuf;
inbuf_remain = inbuf_len;
outbuf_start = outbuf_ptr = outbuf.getBuffer();
outbuf_remain = outbuf.size();
outbuf_end = (outbuf_start + outbuf_remain) - 1;
memset(outbuf_start, 0, outbuf_remain);
inbuf_end = (inbuf + inbuf_len) - 1;
while ((inbuf_remain > 1) && (outbuf_remain > 0) && !decode_err) {
if ((inbuf_ptr > inbuf_end) || (outbuf_ptr > outbuf_end)) {
return false;
}
mark_byte = *inbuf_ptr++;
inbuf_remain--;
test_byte = mark_byte & 0xC0; // Mask all but two high order bits
switch (test_byte) {
case 0xC0: // 1100 0000
// Uncompressed run follows: Max runlength 63
runcount = mark_byte & 0x3f;
if ((inbuf_remain < runcount) || (outbuf_remain < runcount)) {
return false;
}
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *inbuf_ptr++;
}
inbuf_remain -= runcount;
outbuf_remain -= runcount;
continue;
break;
case 0x80: // 1000 0000
// Compressed run follows: Max runlength 63
runcount = (mark_byte & 0x3f) + 3;
if (!inbuf_remain || (outbuf_remain < runcount)) {
return false;
}
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *inbuf_ptr;
}
inbuf_ptr++;
inbuf_remain--;
outbuf_remain -= runcount;
continue;
break;
case 0x40: // 0100 0000
// Repeat decoded sequence from output stream:
// Max runlength 10
runcount = ((mark_byte >> 3) & 0x07U) + 3;
backtrack_amount = *inbuf_ptr;
if (!inbuf_remain || (backtrack_amount > (outbuf_ptr - outbuf_start)) || (runcount > outbuf_remain)) {
return false;
}
inbuf_ptr++;
inbuf_remain--;
backtrack_ptr = outbuf_ptr - backtrack_amount;
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *backtrack_ptr++;
}
outbuf_remain -= runcount;
continue;
break;
default: // 0000 0000
break;
}
// Mask all but the third and fourth highest order bits
test_byte = mark_byte & 0x30;
switch (test_byte) {
case 0x30: // 0011 0000
// Bitfield compression
runcount = (mark_byte & 0x0F) + 1;
if ((inbuf_remain < (runcount + 2)) || (outbuf_remain < (runcount * 8))) {
return false;
}
bitfield_byte1 = *inbuf_ptr++;
bitfield_byte2 = *inbuf_ptr++;
for (c = 0; c < runcount; c++) {
bitfield = *inbuf_ptr;
for (b = 0; b < 8; b++) {
if (bitfield & 0x80) {
*outbuf_ptr = bitfield_byte2;
} else {
*outbuf_ptr = bitfield_byte1;
}
bitfield <<= 1;
outbuf_ptr++;
}
inbuf_ptr++;
}
inbuf_remain -= (runcount + 2);
outbuf_remain -= (runcount * 8);
continue;
break;
case 0x20: // 0010 0000
// Uncompressed run follows
runcount = ((mark_byte & 0x0F) << 8) + *inbuf_ptr;
if ((inbuf_remain < (runcount + 1)) || (outbuf_remain < runcount)) {
return false;
}
inbuf_ptr++;
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *inbuf_ptr++;
}
inbuf_remain -= (runcount + 1);
outbuf_remain -= runcount;
continue;
break;
case 0x10: // 0001 0000
// Repeat decoded sequence from output stream
backtrack_amount = ((mark_byte & 0x0F) << 8) + *inbuf_ptr;
if (inbuf_remain < 2) {
return false;
}
inbuf_ptr++;
runcount = *inbuf_ptr++;
if ((backtrack_amount > (outbuf_ptr - outbuf_start)) || (outbuf_remain < runcount)) {
return false;
}
backtrack_ptr = outbuf_ptr - backtrack_amount;
for (c = 0; c < runcount; c++) {
*outbuf_ptr++ = *backtrack_ptr++;
}
inbuf_remain -= 2;
outbuf_remain -= runcount;
continue;
break;
default:
return false;
}
}
return true;
}
void SagaEngine::flipImage(byte *imageBuffer, int columns, int scanlines) {
int line;
ByteArray tmp_scan;
byte *flip_p1;
byte *flip_p2;
byte *flip_tmp;
int flipcount = scanlines / 2;
tmp_scan.resize(columns);
flip_tmp = tmp_scan.getBuffer();
if (flip_tmp == nullptr) {
return;
}
flip_p1 = imageBuffer;
flip_p2 = imageBuffer + (columns * (scanlines - 1));
for (line = 0; line < flipcount; line++) {
memcpy(flip_tmp, flip_p1, columns);
memcpy(flip_p1, flip_p2, columns);
memcpy(flip_p2, flip_tmp, columns);
flip_p1 += columns;
flip_p2 -= columns;
}
}
void SagaEngine::unbankBGImage(byte *dst_buf, const byte *src_buf, int columns, int scanlines) {
int x, y;
int temp;
int quadruple_rows;
int remain_rows;
int rowjump_src;
int rowjump_dest;
const byte *src_p;
const byte *srcptr1, *srcptr2, *srcptr3, *srcptr4;
byte *dstptr1, *dstptr2, *dstptr3, *dstptr4;
quadruple_rows = scanlines - (scanlines % 4);
remain_rows = scanlines - quadruple_rows;
assert(scanlines > 0);
src_p = src_buf;
srcptr1 = src_p;
srcptr2 = src_p + 1;
srcptr3 = src_p + 2;
srcptr4 = src_p + 3;
dstptr1 = dst_buf;
dstptr2 = dst_buf + columns;
dstptr3 = dst_buf + columns * 2;
dstptr4 = dst_buf + columns * 3;
rowjump_src = columns * 4;
rowjump_dest = columns * 4;
// Unbank groups of 4 first
for (y = 0; y < quadruple_rows; y += 4) {
for (x = 0; x < columns; x++) {
temp = x * 4;
dstptr1[x] = srcptr1[temp];
dstptr2[x] = srcptr2[temp];
dstptr3[x] = srcptr3[temp];
dstptr4[x] = srcptr4[temp];
}
// This is to avoid generating invalid pointers -
// usually innocuous, but undefined
if (y < quadruple_rows - 4) {
dstptr1 += rowjump_dest;
dstptr2 += rowjump_dest;
dstptr3 += rowjump_dest;
dstptr4 += rowjump_dest;
srcptr1 += rowjump_src;
srcptr2 += rowjump_src;
srcptr3 += rowjump_src;
srcptr4 += rowjump_src;
}
}
// Unbank rows remaining
switch (remain_rows) {
case 1:
dstptr1 += rowjump_dest;
srcptr1 += rowjump_src;
for (x = 0; x < columns; x++) {
temp = x * 4;
dstptr1[x] = srcptr1[temp];
}
break;
case 2:
dstptr1 += rowjump_dest;
dstptr2 += rowjump_dest;
srcptr1 += rowjump_src;
srcptr2 += rowjump_src;
for (x = 0; x < columns; x++) {
temp = x * 4;
dstptr1[x] = srcptr1[temp];
dstptr2[x] = srcptr2[temp];
}
break;
case 3:
dstptr1 += rowjump_dest;
dstptr2 += rowjump_dest;
dstptr3 += rowjump_dest;
srcptr1 += rowjump_src;
srcptr2 += rowjump_src;
srcptr3 += rowjump_src;
for (x = 0; x < columns; x++) {
temp = x * 4;
dstptr1[x] = srcptr1[temp];
dstptr2[x] = srcptr2[temp];
dstptr3[x] = srcptr3[temp];
}
break;
default:
break;
}
}
} // End of namespace Saga

177
engines/saga/input.cpp Normal file
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 "saga/saga.h"
#include "saga/gfx.h"
#include "saga/actor.h"
#include "saga/console.h"
#include "saga/interface.h"
#include "saga/render.h"
#include "saga/scene.h"
#include "saga/script.h"
#include "saga/isomap.h"
#include "common/events.h"
#include "common/system.h"
namespace Saga {
int SagaEngine::processInput() {
Common::Event event;
while (_eventMan->pollEvent(event)) {
// Scale down mouse coordinates for the Japanese version which runs in double resolution internally.
if ((event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_RBUTTONDOWN ||
event.type == Common::EVENT_WHEELUP || event.type == Common::EVENT_WHEELDOWN) && getLanguage() == Common::JA_JPN) {
event.mouse.x >>= 1;
event.mouse.y >>= 1;
}
switch (event.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
switch (event.customType) {
case kActionPause:
_render->toggleFlag(RF_RENDERPAUSE);
break;
case kActionAbortSpeech:
_actor->abortSpeech();
break;
case kActionBossKey:
_interface->keyBoss();
break;
case kActionShowDialogue:
_interface->draw();
break;
case kActionOptions:
if (_interface->getSaveReminderState() > 0)
_interface->setMode(kPanelOption);
break;
default:
_interface->processAscii(event.kbd, event.customType);
break;
};
break;
case Common::EVENT_KEYDOWN:
if (_interface->_textInput || _interface->_statusTextInput) {
_interface->processAscii(event.kbd, event.customType);
return SUCCESS;
}
#ifdef SAGA_DEBUG
switch (event.kbd.keycode) {
#if 0
case Common::KEYCODE_KP_MINUS:
case Common::KEYCODE_KP_PLUS:
case Common::KEYCODE_UP:
case Common::KEYCODE_DOWN:
case Common::KEYCODE_RIGHT:
case Common::KEYCODE_LEFT:
if (_vm->_scene->getFlags() & kSceneFlagISO) {
_vm->_isoMap->_viewDiff += (event.kbd.keycode == Common::KEYCODE_KP_PLUS) - (event.kbd.keycode == Common::KEYCODE_KP_MINUS);
_vm->_isoMap->_viewScroll.y += (_vm->_isoMap->_viewDiff * (event.kbd.keycode == Common::KEYCODE_DOWN) - _vm->_isoMap->_viewDiff * (event.kbd.keycode == Common::KEYCODE_UP));
_vm->_isoMap->_viewScroll.x += (_vm->_isoMap->_viewDiff * (event.kbd.keycode == Common::KEYCODE_RIGHT) - _vm->_isoMap->_viewDiff * (event.kbd.keycode == Common::KEYCODE_LEFT));
}
break;
#endif
case Common::KEYCODE_F1:
_render->toggleFlag(RF_SHOW_FPS);
_actor->_handleActionDiv = (_actor->_handleActionDiv == 15) ? 50 : 15;
break;
case Common::KEYCODE_F2:
_render->toggleFlag(RF_PALETTE_TEST);
break;
case Common::KEYCODE_F3:
_render->toggleFlag(RF_TEXT_TEST);
break;
case Common::KEYCODE_F4:
_render->toggleFlag(RF_OBJECTMAP_TEST);
break;
case Common::KEYCODE_F6:
_render->toggleFlag(RF_ACTOR_PATH_TEST);
break;
case Common::KEYCODE_F7:
//_actor->frameTest();
break;
case Common::KEYCODE_F8:
break;
default:
_interface->processAscii(event.kbd, event.customType);
break;
}
#else
_interface->processAscii(event.kbd, event.customType);
#endif
break;
case Common::EVENT_LBUTTONUP:
_leftMouseButtonPressed = false;
break;
case Common::EVENT_RBUTTONUP:
_rightMouseButtonPressed = false;
break;
case Common::EVENT_LBUTTONDOWN:
_leftMouseButtonPressed = true;
_interface->update(event.mouse, UPDATE_LEFTBUTTONCLICK);
break;
case Common::EVENT_RBUTTONDOWN:
_rightMouseButtonPressed = true;
_interface->update(event.mouse, UPDATE_RIGHTBUTTONCLICK);
break;
case Common::EVENT_WHEELUP:
_interface->update(event.mouse, UPDATE_WHEELUP);
break;
case Common::EVENT_WHEELDOWN:
_interface->update(event.mouse, UPDATE_WHEELDOWN);
break;
case Common::EVENT_MOUSEMOVE:
break;
default:
break;
}
Common::Keymapper *keymapper = SagaEngine::getEventManager()->getKeymapper();
if (_interface->_textInput || _interface->_statusTextInput) {
keymapper->getKeymap("game-shortcuts")->setEnabled(false);
keymapper->getKeymap("save-panel")->setEnabled(false);
} else {
keymapper->getKeymap("game-shortcuts")->setEnabled(true);
keymapper->getKeymap("save-panel")->setEnabled(true);
}
enableKeyMap(_interface->getMode());
}
return SUCCESS;
}
Point SagaEngine::mousePos() const {
Common::Point pos = _eventMan->getMousePos();
// Scale down mouse coordinates for the Japanese version which runs in double resolution internally.
if (getLanguage() == Common::JA_JPN) {
pos.x >>= 1;
pos.y >>= 1;
}
return pos;
}
} // End of namespace Saga

2944
engines/saga/interface.cpp Normal file

File diff suppressed because it is too large Load Diff

465
engines/saga/interface.h Normal file
View File

@@ -0,0 +1,465 @@
/* 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/>.
*
*/
// Game interface module private header file
#ifndef SAGA_INTERFACE_H
#define SAGA_INTERFACE_H
#include "common/keyboard.h"
#include "common/savefile.h"
#include "saga/displayinfo.h"
#include "saga/sprite.h"
#include "saga/script.h"
namespace Saga {
enum InterfaceUpdateFlags {
UPDATE_MOUSEMOVE = 1,
UPDATE_LEFTBUTTONCLICK = 2,
UPDATE_RIGHTBUTTONCLICK = 4,
UPDATE_MOUSECLICK = UPDATE_LEFTBUTTONCLICK | UPDATE_RIGHTBUTTONCLICK,
UPDATE_WHEELUP = 8,
UPDATE_WHEELDOWN = 16
};
#define CONVERSE_MAX_TEXTS 64
#define CONVERSE_MAX_WORK_STRING 256
#define ITE_INVENTORY_SIZE 24
#define VERB_STRLIMIT 32
#define STATUS_TEXT_LEN 128
#define STATUS_TEXT_INPUT_MAX 256
#define RID_IHNM_BOSS_SCREEN 19 // not in demo
#define RID_ITE_TYCHO_MAP 1686
#define RID_ITE_SPR_CROSSHAIR (73 + 9)
#define TIMETOSAVE (1000000 * 60 * 30) // 30 minutes
#define TIMETOBLINK_ITE (1000000 * 1)
#define TIMETOBLINK_IHNM (1000000 / 10)
// Converse-specific stuff
enum PanelModes {
kPanelNull,
kPanelMain,
kPanelOption,
kPanelSave, //ex- kPanelTextBox,
kPanelQuit,
kPanelError,
kPanelLoad,
kPanelConverse,
kPanelProtect,
kPanelPlacard,
kPanelMap,
kPanelSceneSubstitute,
kPanelChapterSelection,
kPanelCutaway,
kPanelVideo,
kPanelBoss
// kPanelInventory
};
enum FadeModes {
kNoFade = 0,
kFadeIn,
kFadeOut
};
struct InterfacePanel {
int x;
int y;
ByteArray image;
int imageWidth;
int imageHeight;
PanelButton *currentButton;
int buttonsCount;
PanelButton *buttons;
SpriteList sprites;
InterfacePanel() {
x = y = 0;
imageWidth = imageHeight = 0;
currentButton = NULL;
buttonsCount = 0;
buttons = NULL;
}
PanelButton *getButton(int index) {
if ((index >= 0) && (index < buttonsCount)) {
return &buttons[index];
}
return NULL;
}
void getRect(Rect &rect) {
rect.left = x;
rect.top = y;
rect.setWidth(imageWidth);
rect.setHeight(imageHeight);
}
void calcPanelButtonRect(const PanelButton* panelButton, Rect &rect) {
rect.left = x + panelButton->xOffset;
rect.right = rect.left + panelButton->width;
rect.top = y + panelButton->yOffset;
rect.bottom = rect.top + panelButton->height;
}
PanelButton *hitTest(const Point& mousePoint, int buttonType) {
PanelButton *panelButton;
Rect rect;
int i;
for (i = 0; i < buttonsCount; i++) {
panelButton = &buttons[i];
if (panelButton != NULL) {
if ((panelButton->type & buttonType) > 0) {
calcPanelButtonRect(panelButton, rect);
if (rect.contains(mousePoint)) {
return panelButton;
}
}
}
}
return NULL;
}
void zeroAllButtonState() {
int i;
for (i = 0; i < buttonsCount; i++) {
buttons[i].state = 0;
}
}
};
struct Converse {
Common::Array<char> text;
int strId;
int stringNum;
int textNum;
int replyId;
int replyFlags;
int replyBit;
};
enum StatusTextInputState {
kStatusTextInputFirstRun,
kStatusTextInputEntered,
kStatusTextInputAborted
};
class Interface {
public:
Interface(SagaEngine *vm);
~Interface();
int activate();
int deactivate();
void setSaveReminderState(int state) {
_saveReminderState = state;
draw();
}
int getSaveReminderState() {
return _saveReminderState;
}
bool isActive() { return _active; }
void setMode(int mode);
int getMode() const { return _panelMode; }
void setFadeMode(int fadeMode) {
_fadeMode = fadeMode;
draw();
}
int getFadeMode() const {
return _fadeMode;
}
void rememberMode();
void restoreMode(bool draw_ = true);
bool isInMainMode() { return _inMainMode; }
void setStatusText(const char *text, int statusColor = -1);
void loadScenePortraits(int resourceId);
void setLeftPortrait(int portrait) {
_leftPortrait = portrait;
draw();
}
void setRightPortrait(int portrait) {
_rightPortrait = portrait;
draw();
}
void setPortraitBgColor(int red, int green, int blue) {
_portraitBgColor.red = red;
_portraitBgColor.green = green;
_portraitBgColor.blue = blue;
}
void draw();
void drawOption();
void drawQuit();
void drawLoad();
void drawSave();
void drawProtect();
void update(const Point& mousePoint, int updateFlag);
void drawStatusBar();
void setVerbState(int verb, int state);
bool processAscii(Common::KeyState keystate, Common::CustomEventType customType);
void keyBoss();
void keyBossExit();
void disableAbortSpeeches(bool d) { _disableAbortSpeeches = d; }
static void saveReminderCallback(void *refCon);
void updateSaveReminder();
bool _textInput;
bool _statusTextInput;
StatusTextInputState _statusTextInputState;
char _statusTextInputString[STATUS_TEXT_INPUT_MAX];
void enterStatusString() {
_statusTextInput = true;
_statusTextInputPos = 0;
_statusTextInputString[0] = 0;
setStatusText(_statusTextInputString);
}
private:
void drawInventory();
void updateInventory(int pos);
void inventoryChangePos(int chg);
void inventorySetPos(int key);
public:
void refreshInventory() {
updateInventory(_inventoryCount);
draw();
}
void addToInventory(int objectId);
void removeFromInventory(int objectId);
void clearInventory();
int inventoryItemPosition(int objectId);
int getInventoryContentByPanelButton(PanelButton * panelButton) {
int cell = _inventoryStart + panelButton->id;
if (cell >= _inventoryCount) {
return 0;
}
return _inventory[cell];
}
PanelButton *inventoryHitTest(const Point& mousePoint) {
return _mainPanel.hitTest(mousePoint, kPanelButtonInventory);
}
PanelButton *verbHitTest(const Point& mousePoint){
return _mainPanel.hitTest(mousePoint, kPanelButtonVerb);
}
void saveState(Common::OutSaveFile *out);
void loadState(Common::InSaveFile *in);
void mapPanelDrawCrossHair();
int32 getProtectHash() { return _protectHash; }
void resetSaveReminder();
private:
void handleMainUpdate(const Point& mousePoint); // main panel update
void handleMainClick(const Point& mousePoint); // main panel click
PanelButton *converseHitTest(const Point& mousePoint) {
return _conversePanel.hitTest(mousePoint, kPanelAllButtons);
}
void handleConverseUpdate(const Point& mousePoint); // converse panel update
void handleConverseClick(const Point& mousePoint); // converse panel click
PanelButton *optionHitTest(const Point& mousePoint) {
return _optionPanel.hitTest(mousePoint, kPanelButtonOptionSaveFiles | kPanelButtonOption | kPanelButtonOptionSlider);
}
void handleOptionUpdate(const Point& mousePoint); // option panel update
void handleOptionClick(const Point& mousePoint); // option panel click
PanelButton *quitHitTest(const Point& mousePoint) {
return _quitPanel.hitTest(mousePoint, kPanelAllButtons);
}
void handleQuitUpdate(const Point& mousePoint); // quit panel update
void handleQuitClick(const Point& mousePoint); // quit panel click
PanelButton *loadHitTest(const Point& mousePoint) {
return _loadPanel.hitTest(mousePoint, kPanelAllButtons);
}
void handleLoadUpdate(const Point& mousePoint); // load panel update
void handleLoadClick(const Point& mousePoint); // load panel click
PanelButton *saveHitTest(const Point& mousePoint) {
return _savePanel.hitTest(mousePoint, kPanelAllButtons);
}
void handleSaveUpdate(const Point& mousePoint); // save panel update
void handleSaveClick(const Point& mousePoint); // save panel click
void handleChapterSelectionUpdate(const Point& mousePoint);
void handleChapterSelectionClick(const Point& mousePoint);
void mapPanelShow();
void mapPanelClean();
void lockMode() { _lockedMode = _panelMode; }
void unlockMode() { _panelMode = _lockedMode; }
void setOption(PanelButton *panelButton);
void setQuit(PanelButton *panelButton);
void setLoad(PanelButton *panelButton);
void setSave(PanelButton *panelButton);
void drawTextInput(InterfacePanel *panel, PanelButton *panelButton);
void drawPanelText(InterfacePanel *panel, PanelButton *panelButton);
void drawPanelButtonText(InterfacePanel *panel, PanelButton *panelButton, int spritenum = 0);
enum ButtonKind {
kButton,
kSlider,
kEdit
};
void drawButtonBox(const Rect &rect, ButtonKind kind, bool down);
void drawPanelButtonArrow(InterfacePanel *panel, PanelButton *panelButton);
void drawVerbPanelText(PanelButton *panelButton, KnownColor textKnownColor, KnownColor textShadowKnownColor);
void drawVerbPanel(PanelButton* panelButton);
void calcOptionSaveSlider();
bool processTextInput(Common::KeyState keystate);
void processStatusTextInput(Common::KeyState keystate);
public:
void converseClear();
bool converseAddText(const char *text, int strId, int replyId, byte replyFlags, int replyBit);
void converseDisplayText();
void converseSetTextLines(int row);
void converseChangePos(int chg);
void converseSetPos(int key);
private:
void converseDisplayTextLines();
PanelButton *getPanelButtonByVerbType(int verb) {
if ((verb < 0) || (verb >= kVerbTypeIdsMax)) {
error("Interface::getPanelButtonByVerbType wrong verb");
}
return _verbTypeToPanelButton[verb];
}
void validateOptionButtons() {
if (!_vm->isSaveListFull() && (_optionSaveFileTitleNumber == 0) && (_optionPanel.currentButton != NULL)) {
if (_optionPanel.currentButton->id == kTextLoad) {
_optionPanel.currentButton = NULL;
}
}
}
void validateSaveButtons() {
if ((_textInputStringLength == 0) && (_savePanel.currentButton != NULL)) {
if (_savePanel.currentButton->id == kTextSave) {
_savePanel.currentButton = NULL;
}
}
}
public:
SpriteList _defPortraits;
PalEntry _portraitBgColor;
private:
SagaEngine *_vm;
ResourceContext *_interfaceContext;
InterfacePanel _mainPanel;
PanelButton *_inventoryUpButton;
PanelButton *_inventoryDownButton;
InterfacePanel _conversePanel;
PanelButton *_converseUpButton;
PanelButton *_converseDownButton;
SpriteList _scenePortraits;
PanelButton *_verbTypeToPanelButton[kVerbTypeIdsMax];
InterfacePanel _optionPanel;
PanelButton * _optionSaveFileSlider;
PanelButton * _optionSaveFilePanel;
InterfacePanel _quitPanel;
InterfacePanel _loadPanel;
InterfacePanel _savePanel;
PanelButton * _saveEdit;
InterfacePanel _protectPanel;
PanelButton * _protectEdit;
bool _disableAbortSpeeches;
int _saveReminderState;
bool _active;
int _fadeMode;
int _panelMode;
int _savedMode;
int _lockedMode;
int _bossMode;
bool _inMainMode;
char _statusText[STATUS_TEXT_LEN];
int _statusOnceColor;
int _leftPortrait;
int _rightPortrait;
Point _lastMousePoint;
Common::Array<uint16> _inventory;
int _inventoryStart;
int _inventoryEnd;
int _inventoryPos;
int _inventoryBox;
int _inventoryCount;
char _converseWorkString[CONVERSE_MAX_WORK_STRING];
Converse _converseText[CONVERSE_MAX_TEXTS];
int _converseTextCount;
int _converseStrCount;
int _converseStartPos;
int _converseEndPos;
int _conversePos;
uint _optionSaveFileTop;
uint _optionSaveFileTitleNumber;
int16 _optionSaveFileMouseOff;
Rect _optionSaveRectTop;
Rect _optionSaveRectSlider;
Rect _optionSaveRectBottom;
char _textInputString[SAVE_TITLE_SIZE];
uint _textInputStringLength;
uint _textInputPos;
uint _textInputMaxWidth;
uint _statusTextInputPos;
PalEntry _mapSavedPal[PAL_ENTRIES];
bool _mapPanelCrossHairState;
int32 _protectHash;
};
} // End of namespace Saga
#endif

View File

@@ -0,0 +1,307 @@
/* 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/>.
*
*/
#ifdef ENABLE_IHNM
// "I Have No Mouth" Intro sequence scene procedures
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/animation.h"
#include "saga/events.h"
#include "saga/interface.h"
#include "saga/render.h"
#include "saga/resource.h"
#include "saga/sndres.h"
#include "saga/music.h"
#include "saga/scene.h"
#include "common/events.h"
#include "common/system.h"
namespace Saga {
// IHNM cutaway intro resource IDs
#define RID_IHNM_INTRO_CUTAWAYS 39
#define RID_IHNMDEMO_INTRO_CUTAWAYS 25
int Scene::IHNMStartProc() {
LoadSceneParams firstScene;
IHNMLoadCutaways();
if (!_vm->isIHNMDemo()) {
int logoLength = -168;
if (_vm->getLanguage() == Common::DE_DEU || _vm->getLanguage() == Common::ES_ESP)
logoLength = -128;
// Play Cyberdreams logo for 168 frames
if (!playTitle(0, logoLength, true)) {
if (_vm->shouldQuit())
return !SUCCESS;
// Play Dreamers Guild logo for 10 seconds
if (!playLoopingTitle(1, 10)) {
if (_vm->shouldQuit())
return !SUCCESS;
// Play the title music
_vm->_music->play(1, MUSIC_NORMAL);
// Play title screen
playTitle(2, 20);
}
}
} else {
_vm->_music->play(1, MUSIC_NORMAL);
playTitle(0, 10);
if (_vm->shouldQuit())
return !SUCCESS;
playTitle(2, 12);
}
fadeMusic();
if (_vm->shouldQuit())
return !SUCCESS;
_vm->_anim->clearCutawayList();
// Queue first scene
firstScene.loadFlag = kLoadBySceneNumber;
firstScene.sceneDescriptor = -1;
firstScene.sceneSkipTarget = false;
firstScene.sceneProc = NULL;
firstScene.transitionType = kTransitionFade;
firstScene.actorsEntrance = 0;
firstScene.chapter = -1;
_vm->_scene->queueScene(firstScene);
return SUCCESS;
}
int Scene::IHNMCreditsProc() {
IHNMLoadCutaways();
_vm->_music->play(0, MUSIC_NORMAL);
if (!_vm->isIHNMDemo()) {
// Display the credits for 400 frames
playTitle(4, -400, true);
} else {
// Display sales info for 60 seconds
playTitle(3, 60, true);
}
fadeMusic();
_vm->_anim->clearCutawayList();
return SUCCESS;
}
void Scene::IHNMLoadCutaways() {
ResourceContext *resourceContext;
//ResourceContext *soundContext;
ByteArray resourceData;
resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
if (resourceContext == NULL) {
error("Scene::IHNMStartProc() resource context not found");
}
if (!_vm->isIHNMDemo())
_vm->_resource->loadResource(resourceContext, RID_IHNM_INTRO_CUTAWAYS, resourceData);
else
_vm->_resource->loadResource(resourceContext, RID_IHNMDEMO_INTRO_CUTAWAYS, resourceData);
if (resourceData.empty()) {
error("Scene::IHNMStartProc() Can't load cutaway list");
}
// Load the cutaways for the title screens
_vm->_anim->loadCutawayList(resourceData);
}
bool Scene::checkKey() {
Common::Event event;
bool res = false;
while (_vm->_eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_RETURN_TO_LAUNCHER:
case Common::EVENT_QUIT:
res = true;
break;
case Common::EVENT_KEYDOWN:
// Don't react to modifier keys alone. The original did
// not, and the user may want to change scaler without
// terminating the intro.
if (event.kbd.ascii)
res = true;
break;
default:
break;
}
}
return res;
}
void Scene::fadeMusic() {
if (!_vm->_music->isPlaying())
return;
_vm->_music->setVolume(0, 1000);
while (!_vm->shouldQuit() && _vm->_music->isFading()) {
_vm->_system->delayMillis(10);
if (checkKey()) {
_vm->_music->setVolume(0);
}
}
}
bool Scene::playTitle(int title, int time, int mode) {
bool interrupted = false;
int startTime = _vm->_system->getMillis();
int frameTime = 0;
int curTime;
int assignedId;
int phase = 0;
bool done = false;
bool playParameter = true;
static PalEntry cur_pal[PAL_ENTRIES];
static PalEntry pal_cut[PAL_ENTRIES];
Surface *backBufferSurface = _vm->_render->getBackGroundSurface();
// Load the cutaway
_vm->_anim->setCutAwayMode(mode);
_vm->_frameCount = 0;
_vm->_gfx->getCurrentPal(cur_pal);
assignedId = _vm->_anim->playCutaway(title, false);
_vm->_gfx->getCurrentPal(pal_cut);
while (!done && !_vm->shouldQuit()) {
curTime = _vm->_system->getMillis();
switch (phase) {
case 0: // fadeout
case 1: // fadeout 100%
case 7: // fadeout
case 8: // fadeout 100%
_vm->_gfx->palToBlack(cur_pal, (double)(curTime - startTime) / kNormalFadeDuration);
// fall through
case 3: // fadein
case 4: // fadein 100%
if (phase == 3 || phase == 4)
_vm->_gfx->blackToPal(pal_cut, (double)(curTime - startTime) / kNormalFadeDuration);
if (curTime - startTime > kNormalFadeDuration) {
phase++;
if (phase == 2 || phase == 5 || phase == 9)
startTime = curTime;
break;
}
break;
case 2: // display background
_vm->_system->copyRectToScreen(backBufferSurface->getPixels(), backBufferSurface->w, 0, 0,
backBufferSurface->w, backBufferSurface->h);
phase++;
startTime = curTime;
break;
case 5: // playback
if (time < 0) {
if (_vm->_frameCount >= -time) {
phase++;
break;
}
} else {
if (curTime - startTime >= time * 1000) {
phase++;
break;
}
}
if (checkKey()) {
_vm->_scene->cutawaySkip();
interrupted = true;
phase = 6; // end playback and fade out
break;
}
if (_vm->_anim->getCycles(assignedId)) { // IHNM demo has 0 frames logo
if (curTime - frameTime > _vm->_anim->getFrameTime(assignedId)) {
_vm->_anim->play(assignedId, 0, playParameter);
if (playParameter == true) // Do not loop animations
playParameter = false;
frameTime = curTime;
_vm->_system->copyRectToScreen(backBufferSurface->getPixels(), backBufferSurface->w, 0, 0,
backBufferSurface->w, backBufferSurface->h);
}
}
break;
case 6: // playback end
startTime = curTime;
_vm->_gfx->getCurrentPal(cur_pal);
phase++;
break;
case 9: // end
done = true;
break;
default:
break;
}
_vm->_system->updateScreen();
_vm->_system->delayMillis(10);
}
// Clean up
_vm->_anim->endVideo();
memset((byte *)backBufferSurface->getPixels(), 0, backBufferSurface->w * backBufferSurface->h);
_vm->_system->copyRectToScreen(backBufferSurface->getPixels(), backBufferSurface->w, 0, 0,
backBufferSurface->w, backBufferSurface->h);
return interrupted;
}
bool Scene::playLoopingTitle(int title, int seconds) {
return playTitle(title, seconds, kPanelCutaway);
}
} // End of namespace Saga
#endif

View File

@@ -0,0 +1,834 @@
/* 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/>.
*
*/
// Intro sequence scene procedures
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/animation.h"
#include "saga/events.h"
#include "saga/font.h"
#include "saga/itedata.h"
#include "saga/sndres.h"
#include "saga/palanim.h"
#include "saga/music.h"
#include "saga/scene.h"
#include "saga/resource.h"
namespace Saga {
#define INTRO_FRAMETIME 90
#define INTRO_CAPTION_Y 170
#define INTRO_DE_CAPTION_Y 160
#define INTRO_IT_CAPTION_Y 160
#define INTRO_FR_CAPTION_Y 160
#define INTRO_RU_CAPTION_Y 160
#define INTRO_VOICE_PAD 50
#define INTRO_VOICE_LETTERLEN 90
#define DISSOLVE_DURATION 3000
#define LOGO_DISSOLVE_DURATION 1000
// Intro scenes
#define RID_ITE_INTRO_ANIM_SCENE 1538
#define RID_ITE_CAVE_SCENE_1 1542
#define RID_ITE_CAVE_SCENE_2 1545
#define RID_ITE_CAVE_SCENE_3 1548
#define RID_ITE_CAVE_SCENE_4 1551
#define RID_ITE_VALLEY_SCENE 1556
#define RID_ITE_TREEHOUSE_SCENE 1560
#define RID_ITE_FAIREPATH_SCENE 1564
#define RID_ITE_FAIRETENT_SCENE 1567
// Intro scenes - DOS demo
#define RID_ITE_INTRO_ANIM_SCENE_DOS_DEMO 298
#define RID_ITE_CAVE_SCENE_DOS_DEMO 302
#define RID_ITE_VALLEY_SCENE_DOS_DEMO 310
// ITE intro music
#define MUSIC_INTRO 9
#define MUSIC_TITLE_THEME 10
static LoadSceneParams ITE_IntroListDefault[] = {
{RID_ITE_INTRO_ANIM_SCENE, kLoadByResourceId, Scene::SC_ITEIntroAnimProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_CAVE_SCENE_1, kLoadByResourceId, Scene::SC_ITEIntroCave1Proc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_CAVE_SCENE_2, kLoadByResourceId, Scene::SC_ITEIntroCave2Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_CAVE_SCENE_3, kLoadByResourceId, Scene::SC_ITEIntroCave3Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_CAVE_SCENE_4, kLoadByResourceId, Scene::SC_ITEIntroCave4Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_VALLEY_SCENE, (Saga::SceneLoadFlags) (kLoadByResourceId | kLoadBgMaskIsImage), Scene::SC_ITEIntroValleyProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_TREEHOUSE_SCENE, kLoadByResourceId, Scene::SC_ITEIntroTreeHouseProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_FAIREPATH_SCENE, kLoadByResourceId, Scene::SC_ITEIntroFairePathProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_FAIRETENT_SCENE, kLoadByResourceId, Scene::SC_ITEIntroFaireTentProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{0, kLoadByResourceId, nullptr, false, kTransitionNoFade, 0, 0}
};
static const LoadSceneParams ITE_AmigaEnglishECSCD_IntroList[] = {
{1544, kLoadByResourceId, Scene::SC_ITEIntroAnimProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1548, kLoadByResourceId, Scene::SC_ITEIntroCave1Proc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{1551, kLoadByResourceId, Scene::SC_ITEIntroCave2Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1554, kLoadByResourceId, Scene::SC_ITEIntroCave3Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1557, kLoadByResourceId, Scene::SC_ITEIntroCave4Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1562, (Saga::SceneLoadFlags) (kLoadByResourceId | kLoadBgMaskIsImage), Scene::SC_ITEIntroValleyProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{1566, kLoadByResourceId, Scene::SC_ITEIntroTreeHouseProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1570, kLoadByResourceId, Scene::SC_ITEIntroFairePathProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1573, kLoadByResourceId, Scene::SC_ITEIntroFaireTentProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{0, kLoadByResourceId, nullptr, false, kTransitionNoFade, 0, 0}
};
static const LoadSceneParams ITE_AmigaGermanAGA_IntroList[] = {
{1538, kLoadByResourceId, Scene::SC_ITEIntroAnimProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1543, kLoadByResourceId, Scene::SC_ITEIntroCave1Proc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{1547, kLoadByResourceId, Scene::SC_ITEIntroCave2Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1551, kLoadByResourceId, Scene::SC_ITEIntroCave3Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1555, kLoadByResourceId, Scene::SC_ITEIntroCave4Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1560, (Saga::SceneLoadFlags) (kLoadByResourceId | kLoadBgMaskIsImage), Scene::SC_ITEIntroValleyProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{1564, kLoadByResourceId, Scene::SC_ITEIntroTreeHouseProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1568, kLoadByResourceId, Scene::SC_ITEIntroFairePathProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1571, kLoadByResourceId, Scene::SC_ITEIntroFaireTentProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{0, kLoadByResourceId, nullptr, false, kTransitionNoFade, 0, 0}
};
static const LoadSceneParams ITE_AmigaGermanECS_IntroList[] = {
{1544, kLoadByResourceId, Scene::SC_ITEIntroAnimProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1549, kLoadByResourceId, Scene::SC_ITEIntroCave1Proc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{1553, kLoadByResourceId, Scene::SC_ITEIntroCave2Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1557, kLoadByResourceId, Scene::SC_ITEIntroCave3Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1561, kLoadByResourceId, Scene::SC_ITEIntroCave4Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1566, (Saga::SceneLoadFlags) (kLoadByResourceId | kLoadBgMaskIsImage), Scene::SC_ITEIntroValleyProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{1570, kLoadByResourceId, Scene::SC_ITEIntroTreeHouseProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1574, kLoadByResourceId, Scene::SC_ITEIntroFairePathProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{1577, kLoadByResourceId, Scene::SC_ITEIntroFaireTentProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{0, kLoadByResourceId, nullptr, false, kTransitionNoFade, 0, 0}
};
static const LoadSceneParams ITE_DOS_Demo_IntroList[] = {
{RID_ITE_INTRO_ANIM_SCENE_DOS_DEMO, kLoadByResourceId, Scene::SC_ITEIntroAnimProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_CAVE_SCENE_DOS_DEMO, kLoadByResourceId, Scene::SC_ITEIntroCaveDemoProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{RID_ITE_VALLEY_SCENE_DOS_DEMO, kLoadByResourceId, Scene::SC_ITEIntroValleyProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE},
{0, kLoadByResourceId, nullptr, false, kTransitionNoFade, 0, 0}
};
static const LoadSceneParams *ITE_IntroLists[INTROLIST_MAX] = {
/* INTROLIST_ITE_NONE */ nullptr,
/* INTROLIST_ITE_DEFAULT */ ITE_IntroListDefault,
/* INTROLIST_ITE_AMIGA_ENGLISH_ECS_CD */ ITE_AmigaEnglishECSCD_IntroList,
/* INTROLIST_ITE_AMIGA_GERMAN_AGA */ ITE_AmigaGermanAGA_IntroList,
/* INTROLIST_ITE_AMIGA_GERMAN_ECS */ ITE_AmigaGermanECS_IntroList,
/* INTROLIST_ITE_DOS_DEMO */ ITE_DOS_Demo_IntroList
};
int Scene::ITEStartProc() {
LoadSceneParams firstScene;
LoadSceneParams tempScene;
const LoadSceneParams *scenes = nullptr;
GameIntroList index = _vm->getIntroList();
if (index < INTROLIST_MAX && index > INTROLIST_NONE)
scenes = ITE_IntroLists[index];
if (scenes) {
for (int i = 0; scenes[i].sceneDescriptor; i++) {
tempScene = scenes[i];
tempScene.sceneDescriptor = _vm->_resource->convertResourceId(tempScene.sceneDescriptor);
_vm->_scene->queueScene(tempScene);
}
} else {
warning("Missing intro list");
}
firstScene.loadFlag = kLoadBySceneNumber;
firstScene.sceneDescriptor = _vm->getStartSceneNumber();
firstScene.sceneSkipTarget = true;
firstScene.sceneProc = nullptr;
firstScene.transitionType = kTransitionFade;
firstScene.actorsEntrance = 0;
firstScene.chapter = -1;
_vm->_scene->queueScene(firstScene);
return SUCCESS;
}
EventColumns *Scene::queueIntroDialogue(EventColumns *eventColumns, int n_dialogues, const IntroDialogue dialogue[]) {
TextListEntry textEntry;
TextListEntry *entry;
Event event;
int voiceLength;
int i;
// Queue narrator dialogue list
textEntry.knownColor = kKnownColorSubtitleTextColor;
textEntry.effectKnownColor = (_vm->getPlatform() == Common::kPlatformPC98) ? kKnownColorSubtitleEffectColorPC98 : kKnownColorTransparent;
textEntry.useRect = true;
textEntry.rect.left = (_vm->getPlatform() == Common::kPlatformPC98) ? 10 : 0;
textEntry.rect.right = _vm->getDisplayInfo().width - (_vm->getPlatform() == Common::kPlatformPC98 ? 10 : 0);
if (_vm->getLanguage() == Common::DE_DEU) {
textEntry.rect.top = INTRO_DE_CAPTION_Y;
} else if (_vm->getLanguage() == Common::IT_ITA) {
textEntry.rect.top = INTRO_IT_CAPTION_Y;
} else if (_vm->getLanguage() == Common::FR_FRA) {
textEntry.rect.top = INTRO_FR_CAPTION_Y;
} else if (_vm->getLanguage() == Common::RU_RUS) {
textEntry.rect.top = INTRO_RU_CAPTION_Y;
} else {
textEntry.rect.top = INTRO_CAPTION_Y;
}
textEntry.rect.bottom = _vm->getDisplayInfo().height;
textEntry.font = kKnownFontMedium;
textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered);
for (i = 0; i < n_dialogues; i++) {
textEntry.text = dialogue[i].i_str;
// For the Japanese version align each string to the bottom of the screen
if (_vm->getLanguage() == Common::JA_JPN)
textEntry.rect.top = textEntry.rect.bottom - _vm->_font->getHeight(textEntry.font, textEntry.text, textEntry.rect.width(), textEntry.flags);
entry = _vm->_scene->_textList.addEntry(textEntry);
if (_vm->_subtitlesEnabled) {
// Display text
event.type = kEvTOneshot;
event.code = kTextEvent;
event.op = kEventDisplay;
event.data = entry;
event.time = (i == 0) ? 0 : INTRO_VOICE_PAD;
eventColumns = _vm->_events->chain(eventColumns, event);
}
if (_vm->_voicesEnabled) {
// Play voice
event.type = kEvTOneshot;
event.code = kVoiceEvent;
event.op = kEventPlay;
event.param = dialogue[i].i_voice_rn;
event.time = 0;
_vm->_events->chain(eventColumns, event);
}
voiceLength = _vm->_sndRes->getVoiceLength(dialogue[i].i_voice_rn);
if (voiceLength < 0) {
// Set a default length if no speech file is present
voiceLength = strlen(dialogue[i].i_str) * INTRO_VOICE_LETTERLEN;
}
// Remove text
event.type = kEvTOneshot;
event.code = kTextEvent;
event.op = kEventRemove;
event.data = entry;
event.time = voiceLength;
_vm->_events->chain(eventColumns, event);
}
return eventColumns;
}
// Queue a page of credits text. The original interpreter did word-wrapping
// automatically. We currently don't.
EventColumns *Scene::queueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]) {
int game;
Common::Language lang;
bool hasWyrmkeepCredits = (Common::File::exists("credit3n.dlt") || // PC
Common::File::exists("credit3m.dlt")); // Mac
// The assumption here is that all WyrmKeep versions have the same
// credits, regardless of which operating system they're for.
lang = _vm->getLanguage();
if (hasWyrmkeepCredits)
game = kITECreditsWyrmKeep;
else if (_vm->getPlatform() == Common::kPlatformMacintosh)
game = kITECreditsMac;
else if (_vm->getPlatform() == Common::kPlatformPC98)
game = kITECreditsPC98;
else if (_vm->getFeatures() & GF_EXTRA_ITE_CREDITS)
game = kITECreditsPCCD;
else
game = kITECreditsPC;
int lineHeight = 0;
int paragraph_spacing;
KnownFont font = kKnownFontSmall;
int i;
int n_paragraphs = 0;
int credits_height = 0;
for (i = 0; i < n_credits; i++) {
if (credits[i].lang != lang && credits[i].lang != Common::UNK_LANG) {
continue;
}
if (!(credits[i].game & game)) {
continue;
}
switch (credits[i].type) {
case kITECreditsHeader:
font = kKnownFontSmall;
// First glance at disasm might suggest that the 12 here is a typo (instead of 11). But it isn't.
// I take into account the extra pixel per paragraph here which the original code will consider
// elsewhere. In tbe second (queueing) loop below I have to be a bit more elaborate to get this
// right (using extra variable yOffs2), but here it works fine like this...
lineHeight = (_vm->getPlatform() == Common::kPlatformPC98) ? 12 : _vm->_font->getHeight(font) + 4;
n_paragraphs++;
break;
case kITECreditsText:
font = kKnownFontMedium;
lineHeight = (_vm->getPlatform() == Common::kPlatformPC98) ? (_vm->_font->getHeight(font) << 1) : _vm->_font->getHeight(font) + 2;
break;
default:
error("Unknown credit type");
}
credits_height += lineHeight;
}
paragraph_spacing = (200 - credits_height) / (n_paragraphs + 3);
int y = (_vm->getPlatform() == Common::kPlatformPC98) ? paragraph_spacing + 80 : paragraph_spacing;
TextListEntry textEntry;
TextListEntry *entry;
Event event;
EventColumns *eventColumns = nullptr;
textEntry.knownColor = (_vm->getPlatform() == Common::kPlatformPC98) ? kKnownColorBrightWhite : kKnownColorSubtitleTextColor;
textEntry.effectKnownColor = (_vm->getPlatform() == Common::kPlatformPC98) ? kKnownColorVerbTextShadow : kKnownColorTransparent;
textEntry.flags = (FontEffectFlags)(((_vm->getPlatform() == Common::kPlatformPC98) ? kFontShadow : kFontOutline) | kFontCentered);
textEntry.point.x = 160;
int yOffs = 0;
int yOffs2 = 0;
for (i = 0; i < n_credits; i++) {
if (credits[i].lang != lang && credits[i].lang != Common::UNK_LANG) {
continue;
}
if (!(credits[i].game & game)) {
continue;
}
switch (credits[i].type) {
case kITECreditsHeader:
font = kKnownFontSmall;
lineHeight = (_vm->getPlatform() == Common::kPlatformPC98) ? 11 : _vm->_font->getHeight(font) + 4;
yOffs = (_vm->getPlatform() == Common::kPlatformPC98) ? -3 : 0;
y = y + paragraph_spacing + yOffs2;
break;
case kITECreditsText:
font = kKnownFontMedium;
lineHeight = (_vm->getPlatform() == Common::kPlatformPC98) ? (_vm->_font->getHeight(font) << 1) : _vm->_font->getHeight(font) + 2;
yOffs = 0;
break;
default:
break;
}
textEntry.text = credits[i].string;
textEntry.font = font;
textEntry.point.y = y + yOffs;
if (_vm->getPlatform() == Common::kPlatformPC98) {
textEntry.point.y >>= 1;
yOffs2 = 1;
}
entry = _vm->_scene->_textList.addEntry(textEntry);
// Display text
event.type = kEvTOneshot;
event.code = kTextEvent;
event.op = kEventDisplay;
event.data = entry;
event.time = delta_time;
eventColumns = _vm->_events->queue(event);
// Remove text
event.type = kEvTOneshot;
event.code = kTextEvent;
event.op = kEventRemove;
event.data = entry;
event.time = duration;
_vm->_events->chain(eventColumns, event);
y += lineHeight;
}
return eventColumns;
}
int Scene::SC_ITEIntroAnimProc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroAnimProc(param);
}
// Handles the introductory Dreamer's Guild / NWC logo animation scene.
int Scene::ITEIntroAnimProc(int param) {
Event event;
EventColumns *eventColumns;
bool isMac = _vm->getPlatform() == Common::kPlatformMacintosh;
bool isMultiCD = _vm->getPlatform() == Common::kPlatformUnknown;
bool hasWyrmkeepCredits = (Common::File::exists("credit3n.dlt") || // PC
Common::File::exists("credit3m.dlt")); // Mac
bool isDemo = Common::File::exists("scriptsd.rsc");
switch (param) {
case SCENE_BEGIN:{
// Background for intro scene is the first frame of the
// intro animation; display it and set the palette
event.type = kEvTOneshot;
event.code = kBgEvent;
event.op = kEventDisplay;
event.param = kEvPSetPalette;
event.time = 0;
eventColumns = _vm->_events->queue(event);
debug(3, "Intro animation procedure started.");
debug(3, "Linking animation resources...");
// Some demos lack animations
if (_vm->_anim->hasAnimation(0)) {
_vm->_anim->setFrameTime(0, INTRO_FRAMETIME);
// Link this scene's animation resources for continuous
// playback
int lastAnim;
if (hasWyrmkeepCredits || isMultiCD || isDemo)
lastAnim = isMac ? 3 : 2;
else
lastAnim = isMac ? 4 : 5;
for (int i = 0; i < lastAnim; i++) {
if (!_vm->_anim->hasAnimation(i+1)) {
lastAnim = i;
break;
}
_vm->_anim->link(i, i+1);
}
_vm->_anim->setFlag(lastAnim, ANIM_FLAG_ENDSCENE);
debug(3, "Beginning animation playback.");
// Begin the animation
event.type = kEvTOneshot;
event.code = kAnimEvent;
event.op = kEventPlay;
event.param = 0;
event.time = 0;
_vm->_events->chain(eventColumns, event);
} else {
event.type = kEvTOneshot;
event.code = kSceneEvent;
event.op = kEventEnd;
event.time = 1000;
_vm->_events->chain(eventColumns, event);
}
// Queue intro music playback
_vm->_events->chainMusic(eventColumns, MUSIC_INTRO, true);
}
break;
case SCENE_END:
break;
default:
warning("Illegal scene procedure parameter");
break;
}
return 0;
}
int Scene::ITEIntroCaveCommonProc(int param, int caveScene) {
Event event;
EventColumns *eventColumns = nullptr;
const IntroDialogue *dialogue;
int lang = _vm->getLanguageIndex();
int n_dialogues = 0;
switch (caveScene) {
case 1:
n_dialogues = ARRAYSIZE(introDialogueCave1[lang]);
dialogue = introDialogueCave1[lang];
break;
case 2:
n_dialogues = ARRAYSIZE(introDialogueCave2[lang]);
dialogue = introDialogueCave2[lang];
break;
case 3:
n_dialogues = ARRAYSIZE(introDialogueCave3[lang]);
dialogue = introDialogueCave3[lang];
break;
case 4:
n_dialogues = ARRAYSIZE(introDialogueCave4[lang]);
dialogue = introDialogueCave4[lang];
break;
default:
error("Invalid cave scene");
}
switch (param) {
case SCENE_BEGIN:
if (caveScene > 1) {
// Start 'dissolve' transition to new scene background
event.type = kEvTContinuous;
event.code = kTransitionEvent;
event.op = kEventDissolve;
event.time = 0;
event.duration = DISSOLVE_DURATION;
eventColumns = _vm->_events->queue(event);
}
// Begin palette cycling animation for candles
event.type = kEvTOneshot;
event.code = kPalAnimEvent;
event.op = kEventCycleStart;
event.time = 0;
eventColumns = _vm->_events->chain(eventColumns, event);
// Queue narrator dialogue list
queueIntroDialogue(eventColumns, n_dialogues, dialogue);
// End scene after last dialogue over
event.type = kEvTOneshot;
event.code = kSceneEvent;
event.op = kEventEnd;
event.time = INTRO_VOICE_PAD;
_vm->_events->chain(eventColumns, event);
break;
case SCENE_END:
break;
default:
warning("Illegal scene procedure parameter");
break;
}
return 0;
}
int Scene::ITEIntroCaveDemoProc(int param) {
Event event;
EventColumns *eventColumns = nullptr;
switch (param) {
case SCENE_BEGIN:
// Begin palette cycling animation for candles
event.type = kEvTOneshot;
event.code = kPalAnimEvent;
event.op = kEventCycleStart;
event.time = 0;
eventColumns = _vm->_events->chain(eventColumns, event);
// Queue narrator dialogue list
for (int i = 0; i < 11; i++) {
// Play voice
event.type = kEvTOneshot;
event.code = kVoiceEvent;
event.op = kEventPlay;
event.param = i;
event.time = _vm->_sndRes->getVoiceLength(i);
_vm->_events->chain(eventColumns, event);
}
// End scene after last dialogue over
event.type = kEvTOneshot;
event.code = kSceneEvent;
event.op = kEventEnd;
event.time = INTRO_VOICE_PAD;
_vm->_events->chain(eventColumns, event);
break;
case SCENE_END:
break;
default:
warning("Illegal scene procedure parameter");
break;
}
return 0;
}
int Scene::SC_ITEIntroCaveDemoProc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroCaveDemoProc(param);
}
// Handles first introductory cave painting scene
int Scene::SC_ITEIntroCave1Proc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroCaveCommonProc(param, 1);
}
// Handles second introductory cave painting scene
int Scene::SC_ITEIntroCave2Proc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroCaveCommonProc(param, 2);
}
// Handles third introductory cave painting scene
int Scene::SC_ITEIntroCave3Proc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroCaveCommonProc(param, 3);
}
// Handles fourth introductory cave painting scene
int Scene::SC_ITEIntroCave4Proc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroCaveCommonProc(param, 4);
}
int Scene::SC_ITEIntroValleyProc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroValleyProc(param);
}
// Handles intro title scene (valley overlook)
int Scene::ITEIntroValleyProc(int param) {
Event event;
EventColumns *eventColumns;
int n_credits = ARRAYSIZE(creditsValley);
switch (param) {
case SCENE_BEGIN:
// Begin title screen background animation
_vm->_anim->setCycles(0, -1);
event.type = kEvTOneshot;
event.code = kAnimEvent;
event.op = kEventPlay;
event.param = 0;
event.time = 0;
eventColumns = _vm->_events->queue(event);
// Begin ITE title theme music
_vm->_music->stop();
_vm->_events->chainMusic(eventColumns, MUSIC_TITLE_THEME);
// Pause animation before logo
event.type = kEvTOneshot;
event.code = kAnimEvent;
event.op = kEventStop;
event.param = 0;
event.time = 3000;
_vm->_events->chain(eventColumns, event);
// Display logo
event.type = kEvTContinuous;
event.code = kTransitionEvent;
event.op = kEventDissolveBGMask;
event.time = 0;
event.duration = LOGO_DISSOLVE_DURATION;
_vm->_events->chain(eventColumns, event);
// Remove logo
event.type = kEvTContinuous;
event.code = kTransitionEvent;
event.op = kEventDissolve;
event.time = 3000;
event.duration = LOGO_DISSOLVE_DURATION;
_vm->_events->chain(eventColumns, event);
// Unpause animation before logo
event.type = kEvTOneshot;
event.code = kAnimEvent;
event.op = kEventPlay;
event.time = 0;
event.param = 0;
_vm->_events->chain(eventColumns, event);
// Queue game credits list
eventColumns = queueCredits(9000, CREDIT_DURATION1, n_credits, creditsValley);
// End scene after credit display
event.type = kEvTOneshot;
event.code = kSceneEvent;
event.op = kEventEnd;
event.time = 1000;
_vm->_events->chain(eventColumns, event);
break;
case SCENE_END:
break;
default:
warning("Illegal scene procedure parameter");
break;
}
return 0;
}
int Scene::SC_ITEIntroTreeHouseProc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroTreeHouseProc(param);
}
// Handles second intro credit screen (treehouse view)
int Scene::ITEIntroTreeHouseProc(int param) {
Event event;
EventColumns *eventColumns;
int n_credits1 = ARRAYSIZE(creditsTreeHouse1);
int n_credits2 = ARRAYSIZE(creditsTreeHouse2);
switch (param) {
case SCENE_BEGIN:
// Start 'dissolve' transition to new scene background
event.type = kEvTContinuous;
event.code = kTransitionEvent;
event.op = kEventDissolve;
event.time = 0;
event.duration = DISSOLVE_DURATION;
eventColumns = _vm->_events->queue(event);
if (_vm->_anim->hasAnimation(0)) {
// Begin title screen background animation
_vm->_anim->setFrameTime(0, 100);
event.type = kEvTOneshot;
event.code = kAnimEvent;
event.op = kEventPlay;
event.param = 0;
event.time = 0;
_vm->_events->chain(eventColumns, event);
}
// Queue game credits list
queueCredits(DISSOLVE_DURATION + 2000, CREDIT_DURATION1, n_credits1, creditsTreeHouse1);
eventColumns = queueCredits(DISSOLVE_DURATION + 7000, CREDIT_DURATION1, n_credits2, creditsTreeHouse2);
// End scene after credit display
event.type = kEvTOneshot;
event.code = kSceneEvent;
event.op = kEventEnd;
event.time = 1000;
_vm->_events->chain(eventColumns, event);
break;
case SCENE_END:
break;
default:
warning("Illegal scene procedure parameter");
break;
}
return 0;
}
int Scene::SC_ITEIntroFairePathProc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroFairePathProc(param);
}
// Handles third intro credit screen (path to puzzle tent)
int Scene::ITEIntroFairePathProc(int param) {
Event event;
EventColumns *eventColumns;
int n_credits1 = ARRAYSIZE(creditsFairePath1);
int n_credits2 = ARRAYSIZE(creditsFairePath2);
switch (param) {
case SCENE_BEGIN:
// Start 'dissolve' transition to new scene background
event.type = kEvTContinuous;
event.code = kTransitionEvent;
event.op = kEventDissolve;
event.time = 0;
event.duration = DISSOLVE_DURATION;
eventColumns = _vm->_events->queue(event);
// Begin title screen background animation
_vm->_anim->setCycles(0, -1);
event.type = kEvTOneshot;
event.code = kAnimEvent;
event.op = kEventPlay;
event.param = 0;
event.time = 0;
_vm->_events->chain(eventColumns, event);
// Queue game credits list
queueCredits(DISSOLVE_DURATION + 2000, CREDIT_DURATION1, n_credits1, creditsFairePath1);
eventColumns = queueCredits(DISSOLVE_DURATION + 7000, CREDIT_DURATION1, n_credits2, creditsFairePath2);
// End scene after credit display
event.type = kEvTOneshot;
event.code = kSceneEvent;
event.op = kEventEnd;
event.time = 1000;
_vm->_events->chain(eventColumns, event);
break;
case SCENE_END:
break;
default:
warning("Illegal scene procedure parameter");
break;
}
return 0;
}
int Scene::SC_ITEIntroFaireTentProc(int param, void *refCon) {
return ((Scene *)refCon)->ITEIntroFaireTentProc(param);
}
// Handles fourth intro credit screen (treehouse view)
int Scene::ITEIntroFaireTentProc(int param) {
Event event;
EventColumns *eventColumns;
switch (param) {
case SCENE_BEGIN:
// Start 'dissolve' transition to new scene background
event.type = kEvTContinuous;
event.code = kTransitionEvent;
event.op = kEventDissolve;
event.time = 0;
event.duration = DISSOLVE_DURATION;
eventColumns = _vm->_events->queue(event);
_vm->_events->chain(eventColumns, event);
// Queue PC98 extra credits
eventColumns = queueCredits(DISSOLVE_DURATION, CREDIT_DURATION1, ARRAYSIZE(creditsTent), creditsTent);
// End scene after momentary pause
event.type = kEvTOneshot;
event.code = kSceneEvent;
event.op = kEventEnd;
event.time = (_vm->getPlatform() == Common::kPlatformPC98) ? 5000 - CREDIT_DURATION1 : 5000;
_vm->_events->chain(eventColumns, event);
break;
case SCENE_END:
break;
default:
warning("Illegal scene procedure parameter");
break;
}
return 0;
}
} // End of namespace Saga

1708
engines/saga/isomap.cpp Normal file

File diff suppressed because it is too large Load Diff

286
engines/saga/isomap.h Normal file
View File

@@ -0,0 +1,286 @@
/* 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/>.
*
*/
// Isometric level module - private header
#ifndef SAGA_ISOMAP_H
#define SAGA_ISOMAP_H
#include "saga/actor.h"
namespace Saga {
#define ITE_OBJ_MAP 14
#define SAGA_ISOTILE_WIDTH 32
#define SAGA_ISOTILE_BASEHEIGHT 15
#define SAGA_TILE_NOMINAL_H 16
#define SAGA_MAX_TILE_H 64
#define SAGA_TILEPLATFORMDATA_LEN 136
#define SAGA_PLATFORM_W 8
#define SAGA_MAX_PLATFORM_H 16
#define SAGA_TILEMAP_LEN 514
#define SAGA_TILEMAP_W 16
#define SAGA_TILEMAP_H 16
#define SAGA_METATILEDATA_LEN 36
#define SAGA_MULTI_TILE (1 << 15)
#define SAGA_SCROLL_LIMIT_X1 32
#define SAGA_SCROLL_LIMIT_X2 64
#define SAGA_SCROLL_LIMIT_Y1 8
#define SAGA_SCROLL_LIMIT_Y2 32
#define SAGA_DRAGON_SEARCH_CENTER 24
#define SAGA_DRAGON_SEARCH_DIAMETER (SAGA_DRAGON_SEARCH_CENTER * 2)
#define SAGA_SEARCH_CENTER 15
#define SAGA_SEARCH_DIAMETER (SAGA_SEARCH_CENTER * 2)
#define SAGA_SEARCH_QUEUE_SIZE 128
#define SAGA_IMPASSABLE ((1 << kTerrBlock) | (1 << kTerrWater))
#define SAGA_STRAIGHT_NORMAL_COST 4
#define SAGA_DIAG_NORMAL_COST 6
#define SAGA_STRAIGHT_EASY_COST 2
#define SAGA_DIAG_EASY_COST 3
#define SAGA_STRAIGHT_HARD_COST 9
#define SAGA_DIAG_HARD_COST 10
#define SAGA_MAX_PATH_DIRECTIONS 256
enum TerrainTypes {
kTerrNone = 0,
kTerrPath = 1,
kTerrRough = 2,
kTerrBlock = 3,
kTerrWater = 4,
kTerrLast = 5
};
enum TileMapEdgeType {
kEdgeTypeBlack = 0,
kEdgeTypeFill0 = 1,
kEdgeTypeFill1 = 2,
kEdgeTypeRpt = 3,
kEdgeTypeWrap = 4
};
struct IsoTileData {
byte height;
size_t tileSize;
int8 attributes;
byte *tilePointer;
uint16 terrainMask;
byte FGDBGDAttr;
int8 getMaskRule() const {
return attributes & 0x0F;
}
byte getFGDAttr() const {
return FGDBGDAttr >> 4;
}
byte getBGDAttr() const {
return FGDBGDAttr & 0x0F;
}
uint16 getFGDMask() const {
return 1 << getFGDAttr();
}
uint16 getBGDMask() const {
return 1 << getBGDAttr();
}
};
struct TilePlatformData {
int16 metaTile;
int16 height;
int16 highestPixel;
byte vBits;
byte uBits;
int16 tiles[SAGA_PLATFORM_W][SAGA_PLATFORM_W];
};
struct TileMapData {
byte edgeType;
int16 tilePlatforms[SAGA_TILEMAP_W][SAGA_TILEMAP_H];
};
struct MetaTileData {
uint16 highestPlatform;
uint16 highestPixel;
int16 stack[SAGA_MAX_PLATFORM_H];
};
struct MultiTileEntryData {
int16 offset;
byte u;
byte v;
byte h;
byte uSize;
byte vSize;
byte numStates;
byte currentState;
};
class IsoMap {
public:
IsoMap(SagaEngine *vm);
~IsoMap() {
}
void loadImages(const ByteArray &resourceData);
void loadMap(const ByteArray &resourceData);
void loadPlatforms(const ByteArray &resourceData);
void loadMetaTiles(const ByteArray &resourceData);
void loadMulti(const ByteArray &resourceData);
void clear();
void draw();
void drawSprite(SpriteList &spriteList, int spriteNumber, const Location &location, const Point &screenPosition, int scale);
void adjustScroll(bool jump);
void tileCoordsToScreenPoint(const Location &location, Point &position) {
position.x = location.u() - location.v() + (128 * SAGA_TILEMAP_W) - _viewScroll.x + 16;
position.y = -(location.uv() >> 1) + (128 * SAGA_TILEMAP_W) - _viewScroll.y - location.z;
}
void screenPointToTileCoords(const Point &position, Location &location);
void placeOnTileMap(const Location &start, Location &result, int16 distance, uint16 direction);
void findDragonTilePath(ActorData* actor, const Location &start, const Location &end, uint16 initialDirection);
bool findNearestChasm(int16 &u0, int16 &v0, uint16 &direction);
void findTilePath(ActorData* actor, const Location &start, const Location &end);
bool nextTileTarget(ActorData* actor);
void setTileDoorState(int doorNumber, int doorState);
Point getMapPosition() { return _mapPosition; }
void setMapPosition(int x, int y);
int16 getTileIndex(int16 u, int16 v, int16 z);
private:
void drawTiles(const Location *location);
void drawMetaTile(uint16 metaTileIndex, const Point &point, int16 absU, int16 absV);
void drawSpriteMetaTile(uint16 metaTileIndex, const Point &point, Location &location, int16 absU, int16 absV);
void drawPlatform(uint16 platformIndex, const Point &point, int16 absU, int16 absV, int16 absH);
void drawSpritePlatform(uint16 platformIndex, const Point &point, const Location &location, int16 absU, int16 absV, int16 absH);
void drawTile(uint16 tileIndex, const Point &point, const Location *location);
int16 smoothSlide(int16 value, int16 min, int16 max) {
if (value < min) {
if (value < min - 100 || value > min - 4) {
value = min;
} else {
value += 4;
}
} else {
if (value > max) {
if (value > max + 100 || value < max + 4) {
value = max;
} else {
value -= 4;
}
}
}
return value;
}
int16 findMulti(int16 tileIndex, int16 absU, int16 absV, int16 absH);
void pushPoint(int16 u, int16 v, uint16 cost, uint16 direction);
void pushDragonPoint(int16 u, int16 v, uint16 direction);
bool checkDragonPoint(int16 u, int16 v, uint16 direction);
void testPossibleDirections(int16 u, int16 v, uint16 terraComp[8], int skipCenter);
IsoTileData *getTile(int16 u, int16 v, int16 z);
ByteArray _tileData;
Common::Array<IsoTileData> _tilesTable;
Common::Array<TilePlatformData> _tilePlatformList;
Common::Array<MetaTileData> _metaTileList;
Common::Array<MultiTileEntryData> _multiTable;
Common::Array<int16> _multiTableData;
TileMapData _tileMap;
Point _mapPosition;
// path finding stuff
uint16 _platformHeight;
struct DragonPathCell {
uint8 visited:1,direction:3;
};
struct DragonTilePoint {
int8 u, v;
uint8 direction:4;
};
struct PathCell {
uint16 visited:1,direction:3,cost:12;
};
public:
struct TilePoint {
int8 u, v;
uint16 direction:4,cost:12;
};
private:
struct DragonSearchArray {
DragonPathCell cell[SAGA_DRAGON_SEARCH_DIAMETER][SAGA_DRAGON_SEARCH_DIAMETER];
DragonTilePoint queue[SAGA_SEARCH_QUEUE_SIZE];
DragonTilePoint *getQueue(uint16 i) {
assert(i < SAGA_SEARCH_QUEUE_SIZE);
return &queue[i];
}
DragonPathCell *getPathCell(uint16 u, uint16 v) {
assert((u < SAGA_DRAGON_SEARCH_DIAMETER) && (v < SAGA_DRAGON_SEARCH_DIAMETER));
return &cell[u][v];
}
};
struct SearchArray {
PathCell cell[SAGA_SEARCH_DIAMETER][SAGA_SEARCH_DIAMETER];
TilePoint queue[SAGA_SEARCH_QUEUE_SIZE];
TilePoint *getQueue(uint16 i) {
assert(i < SAGA_SEARCH_QUEUE_SIZE);
return &queue[i];
}
PathCell *getPathCell(uint16 u, uint16 v) {
assert((u < SAGA_SEARCH_DIAMETER) && (v < SAGA_SEARCH_DIAMETER));
return &cell[u][v];
}
};
int16 _queueCount;
int16 _readCount;
SearchArray _searchArray;
DragonSearchArray _dragonSearchArray;
byte _pathDirections[SAGA_MAX_PATH_DIRECTIONS];
int _viewDiff;
Point _viewScroll;
Rect _tileClip;
SagaEngine *_vm;
};
} // End of namespace Saga
#endif

351
engines/saga/ite8.h Normal file
View File

@@ -0,0 +1,351 @@
static byte font_ite8[] = {
0x00, 0x08, 0x00, 0x07, 0x00, 0xbc, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03,
0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07,
0x00, 0x08, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b,
0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x0f,
0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13,
0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17,
0x00, 0x18, 0x00, 0x19, 0x00, 0x1a, 0x00, 0x1b,
0x00, 0x1c, 0x00, 0x1d, 0x00, 0x1e, 0x00, 0x1f,
0x00, 0x20, 0x00, 0x22, 0x00, 0x23, 0x00, 0x24,
0x00, 0x26, 0x00, 0x27, 0x00, 0x28, 0x00, 0x29,
0x00, 0x2a, 0x00, 0x2b, 0x00, 0x2c, 0x00, 0x2d,
0x00, 0x2f, 0x00, 0x31, 0x00, 0x33, 0x00, 0x34,
0x00, 0x35, 0x00, 0x36, 0x00, 0x38, 0x00, 0x39,
0x00, 0x3a, 0x00, 0x3b, 0x00, 0x3d, 0x00, 0x3f,
0x00, 0x41, 0x00, 0x43, 0x00, 0x44, 0x00, 0x45,
0x00, 0x46, 0x00, 0x47, 0x00, 0x48, 0x00, 0x49,
0x00, 0x4a, 0x00, 0x4b, 0x00, 0x4c, 0x00, 0x4d,
0x00, 0x4e, 0x00, 0x4f, 0x00, 0x50, 0x00, 0x51,
0x00, 0x52, 0x00, 0x53, 0x00, 0x54, 0x00, 0x55,
0x00, 0x56, 0x00, 0x58, 0x00, 0x59, 0x00, 0x5a,
0x00, 0x5b, 0x00, 0x5c, 0x00, 0x5d, 0x00, 0x5e,
0x00, 0x5f, 0x00, 0x60, 0x00, 0x61, 0x00, 0x63,
0x00, 0x64, 0x00, 0x65, 0x00, 0x66, 0x00, 0x67,
0x00, 0x68, 0x00, 0x69, 0x00, 0x6a, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70,
0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x72,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x00, 0x74,
0x00, 0x76, 0x00, 0x78, 0x00, 0x7a, 0x00, 0x7c,
0x00, 0x7e, 0x00, 0x80, 0x00, 0x82, 0x00, 0x83,
0x00, 0x84, 0x00, 0x85, 0x00, 0x86, 0x00, 0x87,
0x00, 0x88, 0x00, 0x89, 0x00, 0x8a, 0x00, 0x8b,
0x00, 0x8d, 0x00, 0x8f, 0x00, 0x90, 0x00, 0x91,
0x00, 0x92, 0x00, 0x93, 0x00, 0x94, 0x00, 0x96,
0x00, 0x97, 0x00, 0x98, 0x00, 0x99, 0x00, 0x9a,
0x00, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c,
0x00, 0x9d, 0x00, 0x9e, 0x00, 0x9f, 0x00, 0xa0,
0x00, 0xa1, 0x00, 0xa2, 0x00, 0xa4, 0x00, 0xa5,
0x00, 0xa6, 0x00, 0xa7, 0x00, 0xa8, 0x00, 0xa9,
0x00, 0xaa, 0x00, 0xab, 0x00, 0xac, 0x00, 0xad,
0x00, 0xae, 0x00, 0xaf, 0x00, 0xb0, 0x00, 0xb1,
0x00, 0xb2, 0x00, 0xb3, 0x00, 0x00, 0x00, 0xb4,
0x00, 0xb5, 0x00, 0xb6, 0x00, 0xb7, 0x00, 0xb8,
0x00, 0xb9, 0x00, 0x00, 0x00, 0xba, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x07, 0x07, 0x07, 0x06, 0x07, 0x03, 0x05, 0x04,
0x07, 0x06, 0x03, 0x06, 0x03, 0x06, 0x07, 0x05,
0x07, 0x07, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07,
0x02, 0x03, 0x04, 0x04, 0x04, 0x07, 0x07, 0x09,
0x08, 0x07, 0x09, 0x08, 0x08, 0x07, 0x08, 0x06,
0x07, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x08, 0x08,
0x09, 0x07, 0x08, 0x08, 0x09, 0x0b, 0x0a, 0x09,
0x08, 0x04, 0x06, 0x04, 0x06, 0x06, 0x04, 0x08,
0x08, 0x06, 0x08, 0x07, 0x06, 0x07, 0x08, 0x03,
0x05, 0x08, 0x03, 0x0a, 0x07, 0x07, 0x07, 0x08,
0x07, 0x06, 0x06, 0x08, 0x07, 0x0a, 0x08, 0x08,
0x07, 0x04, 0x02, 0x04, 0x06, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x04, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x09, 0x09,
0x09, 0x09, 0x09, 0x0a, 0x0c, 0x07, 0x08, 0x08,
0x08, 0x08, 0x06, 0x06, 0x06, 0x06, 0x09, 0x09,
0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x08, 0x08,
0x08, 0x08, 0x08, 0x08, 0x0b, 0x06, 0x07, 0x07,
0x07, 0x07, 0x03, 0x03, 0x03, 0x03, 0x08, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x00, 0x07, 0x08,
0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04,
0x04, 0x07, 0x08, 0x07, 0x08, 0x04, 0x05, 0x05,
0x08, 0x07, 0x04, 0x07, 0x05, 0x07, 0x08, 0x05,
0x08, 0x08, 0x09, 0x08, 0x08, 0x08, 0x08, 0x08,
0x03, 0x04, 0x05, 0x05, 0x05, 0x08, 0x08, 0x0a,
0x09, 0x08, 0x0a, 0x09, 0x09, 0x08, 0x09, 0x07,
0x08, 0x09, 0x09, 0x0b, 0x0a, 0x09, 0x09, 0x09,
0x09, 0x08, 0x09, 0x09, 0x09, 0x0c, 0x0b, 0x0a,
0x09, 0x05, 0x06, 0x05, 0x06, 0x07, 0x05, 0x09,
0x09, 0x07, 0x09, 0x08, 0x07, 0x08, 0x09, 0x04,
0x06, 0x09, 0x04, 0x0b, 0x08, 0x08, 0x08, 0x09,
0x08, 0x07, 0x07, 0x09, 0x08, 0x0b, 0x09, 0x09,
0x07, 0x05, 0x03, 0x05, 0x07, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x05, 0x00, 0x00, 0x05, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0a, 0x0a,
0x0a, 0x0a, 0x0a, 0x0a, 0x0d, 0x08, 0x09, 0x09,
0x09, 0x09, 0x07, 0x07, 0x07, 0x07, 0x0a, 0x09,
0x09, 0x09, 0x09, 0x09, 0x09, 0x00, 0x09, 0x09,
0x09, 0x09, 0x09, 0x09, 0x00, 0x00, 0x09, 0x09,
0x09, 0x09, 0x09, 0x09, 0x0b, 0x07, 0x08, 0x08,
0x08, 0x08, 0x04, 0x04, 0x04, 0x04, 0x09, 0x08,
0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x08,
0x08, 0x08, 0x08, 0x09, 0x00, 0x09, 0xc0, 0xa0,
0x00, 0x78, 0xc4, 0x38, 0x60, 0x30, 0xc0, 0x6c,
0x00, 0x00, 0x00, 0x00, 0x04, 0x7c, 0x20, 0x7c,
0x7c, 0x0e, 0xfe, 0x3c, 0xfe, 0x7c, 0x7c, 0x00,
0x00, 0x00, 0x00, 0x00, 0x7c, 0x7c, 0x0f, 0x80,
0xfe, 0x7c, 0xfe, 0x00, 0xff, 0xff, 0x7c, 0xe3,
0x78, 0x06, 0xe3, 0xe0, 0x00, 0xe1, 0xc0, 0xe3,
0x00, 0x7e, 0xfe, 0x7e, 0xfe, 0x00, 0x7c, 0x7f,
0xc6, 0xe7, 0x00, 0xc0, 0xc0, 0xe3, 0x80, 0xe3,
0x80, 0x7f, 0xf0, 0xc0, 0xf0, 0x20, 0x00, 0xe0,
0x00, 0xe0, 0x00, 0x06, 0x00, 0x3c, 0x00, 0xe0,
0xc0, 0x18, 0xe0, 0xc0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x30, 0xc0, 0xc0, 0x6c,
0xa0, 0xc0, 0x3e, 0x00, 0xfd, 0x8c, 0x00, 0x00,
0x00, 0x18, 0x0c, 0x00, 0x01, 0x80, 0x02, 0x00,
0x06, 0x80, 0x0d, 0x80, 0x06, 0x00, 0x0f, 0xf0,
0x7c, 0x30, 0x0c, 0x08, 0x36, 0x60, 0x18, 0x20,
0xcc, 0xfe, 0x00, 0x39, 0x00, 0x30, 0x0c, 0x08,
0x32, 0x66, 0x00, 0x00, 0x7d, 0x30, 0x0c, 0x08,
0x36, 0x0c, 0x60, 0x0c, 0x10, 0x32, 0x6c, 0x38,
0x00, 0x00, 0x00, 0x30, 0x18, 0x10, 0x6c, 0x80,
0x40, 0x40, 0xa0, 0x06, 0x32, 0x60, 0x0c, 0x10,
0x32, 0x6c, 0x00, 0x30, 0x0c, 0x10, 0x66, 0x0c,
0x66, 0x00, 0xc0, 0xa0, 0x48, 0xc6, 0xcc, 0x68,
0x60, 0x60, 0x60, 0x38, 0x30, 0x00, 0x00, 0x00,
0x0c, 0xc6, 0xe0, 0xc6, 0xc6, 0x1e, 0xc0, 0x60,
0x06, 0xc6, 0xc6, 0xc0, 0x60, 0x30, 0x00, 0xc0,
0xc6, 0xc6, 0x1b, 0x00, 0x63, 0xc6, 0x63, 0x00,
0x63, 0x63, 0xc6, 0x63, 0xb0, 0x06, 0x66, 0x60,
0x00, 0x73, 0xc0, 0x71, 0x80, 0xc3, 0x63, 0xc3,
0x63, 0x00, 0xc6, 0x98, 0x63, 0x63, 0x00, 0x60,
0x60, 0x33, 0x00, 0x33, 0x00, 0xc6, 0xc0, 0xc0,
0x30, 0x70, 0x00, 0x60, 0x00, 0x60, 0x00, 0x06,
0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x60, 0xc0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x60, 0xc0, 0x60, 0xd8, 0x50, 0x00, 0x41, 0x00,
0x31, 0xdc, 0x00, 0x60, 0x00, 0x00, 0x02, 0x00,
0x02, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00,
0x09, 0x00, 0x1b, 0x30, 0xc6, 0x08, 0x10, 0x14,
0x00, 0x10, 0x20, 0x50, 0x00, 0x63, 0x00, 0x4e,
0x00, 0x08, 0x10, 0x14, 0x4c, 0x00, 0x00, 0x00,
0xc6, 0x18, 0x18, 0x14, 0x00, 0x10, 0x10, 0x10,
0x28, 0x4c, 0x00, 0x44, 0x00, 0x00, 0x00, 0x08,
0x20, 0x28, 0x00, 0x40, 0x80, 0xa0, 0x00, 0x1f,
0x4c, 0x10, 0x10, 0x28, 0x4c, 0x00, 0x00, 0x08,
0x10, 0x28, 0x00, 0x18, 0x00, 0x00, 0xc0, 0x00,
0xfc, 0xc6, 0x18, 0x68, 0xc0, 0xc0, 0x30, 0xfe,
0x30, 0x00, 0x00, 0x00, 0x18, 0xc6, 0x60, 0x06,
0x06, 0x36, 0xfc, 0xc0, 0x06, 0xc6, 0xc6, 0xc0,
0x60, 0x60, 0xf0, 0x60, 0x06, 0xde, 0x33, 0x00,
0x7e, 0xc0, 0x61, 0x80, 0x60, 0x60, 0xc0, 0x63,
0x30, 0x06, 0x6c, 0x60, 0x00, 0x6e, 0xc0, 0x79,
0x80, 0xc3, 0x7e, 0xc3, 0x7e, 0x00, 0x60, 0x18,
0x63, 0x62, 0x00, 0x60, 0x60, 0x1e, 0x00, 0x1e,
0x00, 0x0c, 0xc0, 0x60, 0x30, 0x98, 0x00, 0x30,
0x7c, 0x6e, 0x78, 0x76, 0x7c, 0xf8, 0x7a, 0x7e,
0xc0, 0x18, 0x66, 0xc0, 0x77, 0x80, 0xbc, 0x7c,
0xbc, 0x7a, 0xbc, 0x7c, 0x7c, 0x66, 0xe6, 0xc1,
0x80, 0xe3, 0xc7, 0xfc, 0x60, 0xc0, 0x60, 0x00,
0xa0, 0xc0, 0x9c, 0x80, 0x31, 0xac, 0x00, 0xf0,
0x00, 0x18, 0x0f, 0x80, 0x0f, 0x80, 0x0f, 0x80,
0x0f, 0x80, 0x0f, 0x80, 0x0e, 0x00, 0x33, 0x00,
0xc0, 0xff, 0xff, 0xff, 0xff, 0x78, 0x78, 0x78,
0x78, 0x61, 0x80, 0xf3, 0x00, 0x7e, 0x7e, 0x7e,
0x7e, 0x7e, 0x00, 0x00, 0xc7, 0xc3, 0xc3, 0xc3,
0xc3, 0xe7, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c,
0x7f, 0x80, 0x78, 0x7c, 0x7c, 0x7c, 0x7c, 0xc0,
0xc0, 0x00, 0xc0, 0x06, 0x00, 0x7c, 0x7c, 0x7c,
0x7c, 0x7c, 0x7a, 0x46, 0x66, 0x66, 0x66, 0xc3,
0xc7, 0x00, 0xc0, 0x00, 0x48, 0xf8, 0x30, 0x36,
0x00, 0xc0, 0x30, 0x38, 0xfc, 0x00, 0xfc, 0x00,
0x30, 0xc6, 0x60, 0x1c, 0x1c, 0x66, 0x06, 0xfc,
0x0c, 0x7c, 0x7e, 0x00, 0x00, 0xc0, 0x00, 0x30,
0x1c, 0xd6, 0x3f, 0x00, 0x63, 0xc0, 0x61, 0x80,
0x7c, 0x78, 0xce, 0x7f, 0x30, 0x06, 0x78, 0x60,
0x00, 0x64, 0xc0, 0x6d, 0x80, 0xc3, 0x60, 0xc3,
0x6c, 0x00, 0x18, 0x18, 0x63, 0x36, 0x00, 0x64,
0x60, 0x0c, 0x00, 0x0c, 0x00, 0x18, 0xc0, 0x60,
0x30, 0x00, 0x00, 0x00, 0xc6, 0x73, 0xc4, 0xce,
0xcc, 0x60, 0xc6, 0x63, 0xc0, 0x18, 0x68, 0xc0,
0xcc, 0xc0, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc0,
0xb0, 0xc6, 0xc6, 0xc0, 0xc0, 0x36, 0x63, 0x18,
0xc0, 0xc0, 0x30, 0x00, 0x50, 0xc0, 0xb0, 0x80,
0x31, 0x8c, 0x10, 0xf0, 0x00, 0x70, 0x13, 0x00,
0x13, 0x00, 0x13, 0x00, 0x13, 0x00, 0x13, 0x00,
0x13, 0x00, 0x3f, 0xe0, 0xc0, 0x63, 0x63, 0x63,
0x63, 0xb0, 0xb0, 0xb0, 0xb0, 0xf9, 0x80, 0x7b,
0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x00, 0x00,
0xcb, 0x63, 0x63, 0x63, 0x63, 0x3c, 0xc6, 0xc6,
0xc6, 0xc6, 0xc6, 0xc6, 0xcc, 0xc0, 0xc4, 0xcc,
0xcc, 0xcc, 0xcc, 0xc0, 0xc0, 0xc0, 0xc0, 0x76,
0xbc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xce, 0xc6,
0xc6, 0xc6, 0xc6, 0x63, 0x63, 0x00, 0xc0, 0x00,
0xfc, 0xc6, 0x60, 0xdc, 0x00, 0xc0, 0x30, 0x6c,
0x30, 0x00, 0x00, 0x00, 0x60, 0xc6, 0x60, 0x60,
0x06, 0xff, 0x06, 0xc6, 0x18, 0xc6, 0x06, 0xc0,
0x60, 0x60, 0xf0, 0x60, 0x30, 0xdc, 0x63, 0x00,
0x63, 0xc0, 0x61, 0x80, 0x60, 0x60, 0xc6, 0x63,
0x30, 0x06, 0x78, 0x60, 0x00, 0x60, 0xc0, 0x67,
0x80, 0xc3, 0x60, 0xdb, 0x66, 0x00, 0x06, 0x18,
0x63, 0x34, 0x00, 0x64, 0x60, 0x1e, 0x00, 0x0c,
0x00, 0x30, 0xc0, 0x30, 0x30, 0x00, 0x00, 0x00,
0xc6, 0x63, 0xc0, 0xc6, 0xd8, 0x60, 0xc6, 0x63,
0xc0, 0x18, 0x78, 0xc0, 0xcc, 0xc0, 0xc6, 0xc6,
0xe6, 0xce, 0xc0, 0x78, 0x30, 0xc6, 0x66, 0xcc,
0xc0, 0x18, 0x36, 0x30, 0x60, 0xc0, 0x60, 0x00,
0xa0, 0xc0, 0xb0, 0x80, 0x00, 0x00, 0x30, 0x60,
0x00, 0xc0, 0x3f, 0x00, 0x3f, 0x00, 0x3f, 0x00,
0x3f, 0x00, 0x3f, 0x00, 0x3f, 0x00, 0x63, 0x00,
0xc0, 0x78, 0x78, 0x78, 0x78, 0x30, 0x30, 0x30,
0x30, 0x61, 0x80, 0x6f, 0x00, 0xc3, 0xc3, 0xc3,
0xc3, 0xc3, 0x00, 0x00, 0xd3, 0x63, 0x63, 0x63,
0x63, 0x18, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
0xcf, 0xc0, 0xc0, 0xd8, 0xd8, 0xd8, 0xd8, 0xc0,
0xc0, 0xc0, 0xc0, 0xce, 0xc6, 0xc6, 0xc6, 0xc6,
0xc6, 0xc6, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0x36,
0x36, 0x00, 0x00, 0x00, 0x48, 0xc6, 0xcc, 0xcc,
0x00, 0x60, 0x60, 0x00, 0x30, 0x60, 0x00, 0x60,
0xc0, 0xc6, 0x60, 0xc6, 0xc6, 0x06, 0xc6, 0xc6,
0x30, 0xc6, 0x0c, 0xc0, 0x60, 0x30, 0x00, 0xc0,
0x00, 0xc0, 0x63, 0x00, 0x63, 0xc0, 0x63, 0x00,
0x63, 0x60, 0xc6, 0x63, 0x34, 0xc6, 0x6c, 0x63,
0x00, 0x60, 0xc0, 0x63, 0x80, 0xc3, 0x60, 0xcf,
0x63, 0x00, 0xc6, 0x18, 0x63, 0x1c, 0x00, 0x66,
0x60, 0x33, 0x00, 0x0c, 0x00, 0x63, 0xc0, 0x30,
0x30, 0x00, 0x00, 0x00, 0xc6, 0x63, 0xc0, 0xc6,
0xe2, 0x60, 0x7c, 0x63, 0xc0, 0x18, 0x6c, 0xc0,
0xc0, 0xc0, 0xc6, 0xc6, 0xdc, 0x76, 0xc0, 0x0c,
0x30, 0xc6, 0x36, 0xcc, 0xc0, 0x6c, 0x1c, 0x60,
0x60, 0xc0, 0x60, 0x00, 0x50, 0xc0, 0x9c, 0x80,
0x00, 0x00, 0x60, 0x00, 0x00, 0xc6, 0x63, 0x00,
0x63, 0x00, 0x63, 0x00, 0x63, 0x00, 0x63, 0x00,
0x63, 0x00, 0x63, 0x30, 0xc0, 0x63, 0x63, 0x63,
0x63, 0x34, 0x34, 0x34, 0x34, 0x63, 0x00, 0x67,
0x00, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0x00, 0x00,
0x63, 0x63, 0x63, 0x63, 0x63, 0x18, 0xc6, 0xc6,
0xc6, 0xc6, 0xc6, 0xc6, 0xdc, 0x00, 0xc0, 0xe2,
0xe2, 0xe2, 0xe2, 0xc0, 0xc0, 0xc0, 0xc0, 0xc6,
0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xe6, 0xc6,
0xc6, 0xc6, 0xc6, 0x1c, 0x1c, 0x00, 0xc0, 0x00,
0x00, 0xcc, 0x8c, 0x76, 0x00, 0x30, 0xc0, 0x00,
0x00, 0x60, 0x00, 0x60, 0x80, 0x7c, 0xf0, 0xfe,
0x7c, 0x06, 0x7c, 0x7c, 0x60, 0x7c, 0x78, 0x00,
0xc0, 0x00, 0x00, 0x00, 0x30, 0x7c, 0xf1, 0x80,
0x7e, 0x7e, 0x6e, 0x00, 0x7f, 0x60, 0x7e, 0x63,
0x78, 0x7c, 0x67, 0xfe, 0x00, 0x61, 0x80, 0x61,
0x80, 0x7e, 0xc0, 0x7e, 0xc1, 0x80, 0x7c, 0x38,
0x3f, 0x1c, 0x00, 0x3b, 0xc0, 0x71, 0xc0, 0x1c,
0x00, 0xff, 0xf0, 0x18, 0xf0, 0x00, 0xfc, 0x00,
0x7b, 0x6e, 0x7c, 0x7b, 0x7c, 0x60, 0x06, 0x66,
0x60, 0x18, 0x67, 0x60, 0xc1, 0x80, 0xcc, 0x7c,
0xc0, 0x06, 0xc0, 0xf8, 0x18, 0x7b, 0x1c, 0x7b,
0x80, 0xc7, 0x18, 0xfc, 0x30, 0xc0, 0xc0, 0x00,
0xa0, 0xc0, 0x41, 0x00, 0x00, 0x00, 0xf0, 0x00,
0x40, 0x7c, 0xf1, 0x80, 0xf1, 0x80, 0xf1, 0x80,
0xf1, 0x80, 0xf1, 0x80, 0xf1, 0x80, 0xf3, 0xf0,
0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x78, 0x78, 0x78,
0x78, 0x6e, 0x00, 0x63, 0x00, 0x7e, 0x7e, 0x7e,
0x7e, 0x7e, 0x00, 0x00, 0xbe, 0x3f, 0x3f, 0x3f,
0x3f, 0x38, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,
0x67, 0x80, 0x7c, 0x7c, 0x7c, 0x7c, 0x7c, 0x60,
0x60, 0x60, 0x60, 0x7b, 0xcc, 0x7c, 0x7c, 0x7c,
0x7c, 0x7c, 0xbc, 0x7b, 0x7b, 0x7b, 0x7b, 0x18,
0x18, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x02, 0x06, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7c, 0x00, 0x00, 0xf0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xf0, 0xf0, 0x00,
};

2231
engines/saga/itedata.cpp Normal file

File diff suppressed because it is too large Load Diff

150
engines/saga/itedata.h Normal file
View File

@@ -0,0 +1,150 @@
/* 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/>.
*
*/
// Actor data table header file
#ifndef SAGA_ITEDATA_H
#define SAGA_ITEDATA_H
namespace Saga {
enum ActorFlags {
kProtagonist = 0x01, // (1<<0) Actor is protagonist
kFollower = 0x02, // (1<<1) Actor is follower
kCycle = 0x04, // (1<<2) Actor stand has a cycle
kFaster = 0x08, // (1<<3) Actor is fast
kFastest = 0x10, // (1<<4) Actor is faster
kExtended = 0x20, // (1<<5) Actor uses extended sprites
kUsable = 0x40, // (1<<6) Actor can be used
kNoScale = 0x80 // (1<<7) Actor is not scaled
};
struct ActorTableData {
byte flags;
byte nameIndex;
int32 sceneIndex;
int16 x;
int16 y;
int16 z;
int32 spriteListResourceId;
int32 frameListResourceId;
byte scriptEntrypointNumber;
byte speechColor;
byte currentAction;
byte facingDirection;
byte actionDirection;
};
#define ITE_ACTORCOUNT 181
extern ActorTableData ITE_ActorTable[ITE_ACTORCOUNT];
extern byte ITE_ActorECSSpeechColor[ITE_ACTORCOUNT];
enum {
kObjUseWith = 0x01,
kObjNotFlat = 0x02
};
struct ObjectTableData {
byte nameIndex;
int32 sceneIndex;
int16 x;
int16 y;
int16 z;
int32 spriteListResourceId;
byte scriptEntrypointNumber;
uint16 interactBits;
};
struct IteFxTable {
byte res;
byte vol;
};
struct IntroDialogue {
uint32 i_voice_rn;
const char *i_str;
};
struct IntroCredit {
Common::Language lang;
int game;
int type;
const char *string;
};
enum {
kITECreditsHeader,
kITECreditsText
};
enum {
kITECreditsPC = (1 << 0),
kITECreditsPCCD = (1 << 1),
kITECreditsMac = (1 << 2),
kITECreditsWyrmKeep = (1 << 3),
kITECreditsPC98 = (1 << 4),
kITECreditsAny = 0xffff,
kITECreditsNotWyrmKeep = kITECreditsAny & ~kITECreditsWyrmKeep
};
#define ITE_OBJECTCOUNT 39
#define ITE_SFXCOUNT 63
extern ObjectTableData ITE_ObjectTable[ITE_OBJECTCOUNT];
extern IteFxTable ITE_SfxTable[ITE_SFXCOUNT];
extern const char *ITEinterfaceTextStrings[][53];
#define PUZZLE_PIECES 15
struct RawPoint { int x, y; };
extern const RawPoint pieceOrigins[PUZZLE_PIECES];
extern const char *pieceNames[][PUZZLE_PIECES];
#define NUM_SOLICIT_REPLIES 5
extern const char *solicitStr[][NUM_SOLICIT_REPLIES];
#define NUM_SAKKA 3
extern const char *sakkaStr[][NUM_SAKKA];
#define NUM_WHINES 5
extern const char *whineStr[][NUM_WHINES];
extern const char *hintStr[][4];
extern const char portraitList[];
extern const char *optionsStr[][4];
extern const IntroDialogue introDialogueCave1[][4];
extern const IntroDialogue introDialogueCave2[][3];
extern const IntroDialogue introDialogueCave3[][3];
extern const IntroDialogue introDialogueCave4[][4];
extern const IntroCredit creditsValley[49];
extern const IntroCredit creditsTreeHouse1[42];
extern const IntroCredit creditsTreeHouse2[53];
extern const IntroCredit creditsFairePath1[49];
extern const IntroCredit creditsFairePath2[23];
extern const IntroCredit creditsTent[6];
} // End of namespace Saga
#endif

574
engines/saga/metaengine.cpp Normal file
View File

@@ -0,0 +1,574 @@
/* 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/>.
*
*/
// Game detection, general game parameters
#include "saga/saga.h"
#include "base/plugins.h"
#include "common/config-manager.h"
#include "engines/advancedDetector.h"
#include "common/system.h"
#include "common/translation.h"
#include "graphics/thumbnail.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "backends/keymapper/standard-actions.h"
#include "saga/animation.h"
#include "saga/displayinfo.h"
#include "saga/events.h"
#include "saga/resource.h"
#include "saga/interface.h"
#include "saga/scene.h"
#include "saga/detection.h"
namespace Saga {
static const ADExtraGuiOptionsMap optionsList[] = {
{
GAMEOPTION_COPY_PROTECTION,
{
_s("Enable copy protection"),
_s("Enable any copy protection that would otherwise be bypassed by default."),
"copy_protection",
false,
0,
0
},
},
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
bool SagaEngine::isBigEndian() const { return (isMacResources() || (getPlatform() == Common::kPlatformAmiga)) && getGameId() == GID_ITE; }
bool SagaEngine::isMacResources() const { return (getPlatform() == Common::kPlatformMacintosh); }
GameResourceList SagaEngine::getResourceList() const { return _gameDescription->resourceList; }
GameFontList SagaEngine::getFontList() const { return _gameDescription->fontList; }
GamePatchList SagaEngine::getPatchList() const { return _gameDescription->patchList; }
int SagaEngine::getGameId() const { return _gameDescription->gameId; }
uint32 SagaEngine::getFeatures() const {
uint32 result = _gameDescription->features;
return result;
}
Common::Language SagaEngine::getLanguage() const { return _gameDescription->desc.language; }
Common::Platform SagaEngine::getPlatform() const { return _gameDescription->desc.platform; }
int SagaEngine::getGameNumber() const { return _gameNumber; }
int SagaEngine::getStartSceneNumber() const { return _gameDescription->startSceneNumber; }
const ADGameFileDescription *SagaEngine::getFilesDescriptions() const {
return getFeatures() & GF_INSTALLER ? _gameDescription->filesInArchive : _gameDescription->desc.filesDescriptions;
}
const ADGameFileDescription *SagaEngine::getArchivesDescriptions() const {
return getFeatures() & GF_INSTALLER ? _gameDescription->desc.filesDescriptions : nullptr;
}
} // End of namespace Saga
class SagaMetaEngine : public AdvancedMetaEngine<Saga::SAGAGameDescription> {
public:
const char *getName() const override {
return "saga";
}
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
return Saga::optionsList;
}
bool hasFeature(MetaEngineFeature f) const override;
Common::Error createInstance(OSystem *syst, Engine **engine, const Saga::SAGAGameDescription *desc) const override;
SaveStateList listSaves(const char *target) const override;
int getMaximumSaveSlot() const override;
bool removeSaveState(const char *target, int slot) const override;
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
Common::KeymapArray initKeymaps(const char *target) const override;
};
bool SagaMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
(f == kSavesSupportPlayTime);
}
bool Saga::SagaEngine::hasFeature(EngineFeature f) const {
return
(f == kSupportsReturnToLauncher) ||
(f == kSupportsLoadingDuringRuntime) ||
(f == kSupportsSavingDuringRuntime);
}
Common::Error SagaMetaEngine::createInstance(OSystem *syst, Engine **engine, const Saga::SAGAGameDescription *gd) const {
switch (gd->gameId) {
case Saga::GID_IHNM:
#ifndef ENABLE_IHNM
return Common::Error(Common::kUnsupportedGameidError, _s("I Have No Mouth support not compiled in"));
#endif
break;
default:
break;
}
*engine = new Saga::SagaEngine(syst, gd);
return Common::kNoError;
}
SaveStateList SagaMetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
Common::StringArray filenames;
char saveDesc[SAVE_TITLE_SIZE];
Common::String pattern = target;
pattern += ".s##";
filenames = saveFileMan->listSavefiles(pattern);
SaveStateList saveList;
int slotNum = 0;
for (const auto &file : filenames) {
// Obtain the last 2 digits of the filename, since they correspond to the save slot
slotNum = atoi(file.c_str() + file.size() - 2);
if (slotNum >= 0 && slotNum < MAX_SAVES) {
Common::InSaveFile *in = saveFileMan->openForLoading(file);
if (in) {
for (int i = 0; i < 3; i++)
in->readUint32BE();
in->read(saveDesc, SAVE_TITLE_SIZE);
saveList.push_back(SaveStateDescriptor(this, slotNum, saveDesc));
delete in;
}
}
}
// Sort saves based on slot number.
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
int SagaMetaEngine::getMaximumSaveSlot() const { return MAX_SAVES - 1; }
bool SagaMetaEngine::removeSaveState(const char *target, int slot) const {
Common::String filename = target;
filename += Common::String::format(".s%02d", slot);
return g_system->getSavefileManager()->removeSavefile(filename);
}
SaveStateDescriptor SagaMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
static char fileName[MAX_FILE_NAME];
Common::sprintf_s(fileName, "%s.s%02d", target, slot);
char title[TITLESIZE];
Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fileName);
if (in) {
uint32 type = in->readUint32BE();
in->readUint32LE(); // size
uint32 version = in->readUint32LE();
char name[SAVE_TITLE_SIZE];
in->read(name, sizeof(name));
SaveStateDescriptor desc(this, slot, name);
// Some older saves were not written in an endian safe fashion.
// We try to detect this here by checking for extremely high version values.
// If found, we retry with the data swapped.
if (version > 0xFFFFFF) {
warning("This savegame is not endian safe, retrying with the data swapped");
version = SWAP_BYTES_32(version);
}
debug(2, "Save version: 0x%X", version);
if (version < 4)
warning("This savegame is not endian-safe. There may be problems");
if (type != MKTAG('S','A','G','A')) {
error("SagaEngine::load wrong save game format");
}
if (version > 4) {
in->read(title, TITLESIZE);
debug(0, "Save is for: %s", title);
}
if (version >= 6) {
Graphics::Surface *thumbnail;
if (!Graphics::loadThumbnail(*in, thumbnail)) {
delete in;
return SaveStateDescriptor();
}
desc.setThumbnail(thumbnail);
uint32 saveDate = in->readUint32BE();
uint16 saveTime = in->readUint16BE();
int day = (saveDate >> 24) & 0xFF;
int month = (saveDate >> 16) & 0xFF;
int year = saveDate & 0xFFFF;
desc.setSaveDate(year, month, day);
int hour = (saveTime >> 8) & 0xFF;
int minutes = saveTime & 0xFF;
desc.setSaveTime(hour, minutes);
if (version >= 8) {
uint32 playTime = in->readUint32BE();
desc.setPlayTime(playTime * 1000);
}
}
delete in;
return desc;
}
return SaveStateDescriptor();
}
Common::KeymapArray SagaMetaEngine::initKeymaps(const char *target) const {
using namespace Common;
using namespace Saga;
Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, engineKeyMapId, _("Default game keymap"));
Keymap *gameKeyMap = new Keymap(Keymap::kKeymapTypeGame, gameKeyMapId, _("Game keymapping"));
Keymap *optionKeyMap = new Keymap(Keymap::kKeymapTypeGame, optionKeyMapId, _("Option panel keymapping"));
Keymap *saveKeyMap = new Keymap(Keymap::kKeymapTypeGame, saveKeyMapId, _("Save panel keymapping"));
Keymap *loadKeyMap = new Keymap(Keymap::kKeymapTypeGame, loadKeyMapId, _("Load panel keymapping"));
Keymap *quitKeyMap = new Keymap(Keymap::kKeymapTypeGame, quitKeyMapId, _("Quit panel keymapping"));
Keymap *converseKeyMap = new Keymap(Keymap::kKeymapTypeGame, converseKeyMapId, _("Converse panel keymapping"));
Action *act;
{
act = new Action(kStandardActionLeftClick, _("Left click"));
act->setLeftClickEvent();
act->addDefaultInputMapping("MOUSE_LEFT");
act->addDefaultInputMapping("JOY_A");
engineKeyMap->addAction(act);
act = new Action(kStandardActionMiddleClick, _("Middle click"));
act->addDefaultInputMapping("MOUSE_MIDDLE");
act->setMiddleClickEvent();
engineKeyMap->addAction(act);
act = new Action(kStandardActionRightClick, _("Right click"));
act->setRightClickEvent();
act->addDefaultInputMapping("MOUSE_RIGHT");
act->addDefaultInputMapping("JOY_B");
engineKeyMap->addAction(act);
// I18N: the boss key is a feature,
// that allows players to quickly switch to a fake screen that looks like a business application,
// typically to avoid detection if someone, like a boss, walks by while they are playing.
act = new Action("BOSSKEY", _("Boss key"));
act->setCustomEngineActionEvent(kActionBossKey);
act->addDefaultInputMapping("F9");
engineKeyMap->addAction(act);
act = new Action("SHOWOPTION", _("Show options"));
act->setCustomEngineActionEvent(kActionOptions);
act->addDefaultInputMapping("F5");
act->addDefaultInputMapping("C+o");
engineKeyMap->addAction(act);
}
{
act = new Action("EXITCONVO", _("Exit conversation"));
act->setCustomEngineActionEvent(kActionConverseExit);
act->addDefaultInputMapping("x");
converseKeyMap->addAction(act);
act = new Action("UPCONVO", _("Conversation position - Up"));
act->setCustomEngineActionEvent(kActionConversePosUp);
act->addDefaultInputMapping("u");
converseKeyMap->addAction(act);
act = new Action("DOWNCONVO", _("Conversation position - Down"));
act->setCustomEngineActionEvent(kActionConversePosDown);
act->addDefaultInputMapping("d");
converseKeyMap->addAction(act);
}
{
act = new Action("ESCAPE", _("Escape"));
act->setCustomEngineActionEvent(kActionEscape);
act->addDefaultInputMapping("ESCAPE");
gameKeyMap->addAction(act);
act = new Action("PAUSE", _("Pause game"));
act->setCustomEngineActionEvent(kActionPause);
act->addDefaultInputMapping("z");
act->addDefaultInputMapping("PAUSE");
gameKeyMap->addAction(act);
act = new Action("ABRTSPEECH", _("Abort speech"));
act->setCustomEngineActionEvent(kActionAbortSpeech);
act->addDefaultInputMapping("SPACE");
gameKeyMap->addAction(act);
act = new Action("SHOWDILOG", _("Show dialog"));
act->setCustomEngineActionEvent(kActionShowDialogue);
act->addDefaultInputMapping("r");
gameKeyMap->addAction(act);
act = new Action("WALK", _("Walk to"));
act->setCustomEngineActionEvent(kActionWalkTo);
act->addDefaultInputMapping("w");
gameKeyMap->addAction(act);
act = new Action("LOOK", _("Look at"));
act->setCustomEngineActionEvent(kActionLookAt);
act->addDefaultInputMapping("l");
gameKeyMap->addAction(act);
act = new Action("PICKUP", _("Pick up"));
act->setCustomEngineActionEvent(kActionPickUp);
act->addDefaultInputMapping("p");
gameKeyMap->addAction(act);
act = new Action("TALK", _("Talk to"));
act->setCustomEngineActionEvent(kActionTalkTo);
act->addDefaultInputMapping("t");
gameKeyMap->addAction(act);
act = new Action("OPEN", _("Open"));
act->setCustomEngineActionEvent(kActionOpen);
act->addDefaultInputMapping("o");
gameKeyMap->addAction(act);
act = new Action("CLOSE", _("Close"));
act->setCustomEngineActionEvent(kActionClose);
act->addDefaultInputMapping("c");
gameKeyMap->addAction(act);
act = new Action("USE", _("Use"));
act->setCustomEngineActionEvent(kActionUse);
act->addDefaultInputMapping("u");
gameKeyMap->addAction(act);
act = new Action("GIVE", _("Give"));
act->setCustomEngineActionEvent(kActionGive);
act->addDefaultInputMapping("g");
gameKeyMap->addAction(act);
act = new Action("PUSH", _("Push"));
act->setCustomEngineActionEvent(kActionPush);
act->addDefaultInputMapping("p");
gameKeyMap->addAction(act);
act = new Action("TAKE", _("Take"));
act->setCustomEngineActionEvent(kActionTake);
act->addDefaultInputMapping("k");
gameKeyMap->addAction(act);
// I18N: It is a verb for gulping, not a bird
act = new Action("SWALLOW", _("Swallow"));
act->setCustomEngineActionEvent(kActionSwallow);
act->addDefaultInputMapping("s");
gameKeyMap->addAction(act);
}
{
act = new Action("READSPEED", _("Reading speed"));
act->setCustomEngineActionEvent(kActionOptionReadingSpeed);
act->addDefaultInputMapping("r");
optionKeyMap->addAction(act);
act = new Action("MUSIC", _("Change music"));
act->setCustomEngineActionEvent(kActionOptionMusic);
act->addDefaultInputMapping("m");
optionKeyMap->addAction(act);
act = new Action("SOUND", _("Change sound"));
act->setCustomEngineActionEvent(kActionOptionSound);
act->addDefaultInputMapping("n");
optionKeyMap->addAction(act);
act = new Action("VOICES", _("Change voices"));
act->setCustomEngineActionEvent(kActionOptionVoices);
act->addDefaultInputMapping("v");
optionKeyMap->addAction(act);
act = new Action("CONTGAME", _("Continue game"));
act->setCustomEngineActionEvent(kActionOptionContinue);
act->addDefaultInputMapping("c");
optionKeyMap->addAction(act);
act = new Action("LOAD", _("Load game"));
act->setCustomEngineActionEvent(kActionOptionLoad);
act->addDefaultInputMapping("l");
optionKeyMap->addAction(act);
act = new Action("QUITGAME", _("Quit game"));
act->setCustomEngineActionEvent(kActionOptionQuitGame);
act->addDefaultInputMapping("q");
optionKeyMap->addAction(act);
act = new Action("SAVEGAME", _("Save game"));
act->setCustomEngineActionEvent(kActionOptionSaveGame);
act->addDefaultInputMapping("s");
optionKeyMap->addAction(act);
}
{
act = new Action("QUIT", _("Quit"));
act->setCustomEngineActionEvent(kActionOptionQuit);
act->addDefaultInputMapping("q");
quitKeyMap->addAction(act);
act = new Action("CNCLQUIT", _("Cancel quit"));
act->setCustomEngineActionEvent(kActionOptionCancel);
act->addDefaultInputMapping("c");
quitKeyMap->addAction(act);
act = new Action("OKAY", _("Okay"));
act->setCustomEngineActionEvent(kActionOptionOkay);
act->addDefaultInputMapping("o");
loadKeyMap->addAction(act);
act = new Action("CNCLLOAD", _("Cancel load"));
act->setCustomEngineActionEvent(kActionOptionCancel);
act->addDefaultInputMapping("c");
loadKeyMap->addAction(act);
act = new Action("SAVE", _("Save"));
act->setCustomEngineActionEvent(kActionOptionSave);
act->addDefaultInputMapping("s");
saveKeyMap->addAction(act);
act = new Action("CNCLSAVE", _("Cancel save"));
act->setCustomEngineActionEvent(kActionOptionCancel);
act->addDefaultInputMapping("c");
saveKeyMap->addAction(act);
}
KeymapArray keymaps(7);
keymaps[0] = engineKeyMap;
keymaps[1] = gameKeyMap;
keymaps[2] = optionKeyMap;
keymaps[3] = saveKeyMap;
keymaps[4] = loadKeyMap;
keymaps[5] = quitKeyMap;
keymaps[6] = converseKeyMap;
gameKeyMap->setEnabled(false);
optionKeyMap->setEnabled(false);
saveKeyMap->setEnabled(false);
loadKeyMap->setEnabled(false);
quitKeyMap->setEnabled(false);
converseKeyMap->setEnabled(false);
return keymaps;
}
#if PLUGIN_ENABLED_DYNAMIC(SAGA)
REGISTER_PLUGIN_DYNAMIC(SAGA, PLUGIN_TYPE_ENGINE, SagaMetaEngine);
#else
REGISTER_PLUGIN_STATIC(SAGA, PLUGIN_TYPE_ENGINE, SagaMetaEngine);
#endif
namespace Saga {
bool SagaEngine::initGame() {
_displayClip.right = getDisplayInfo().width;
_displayClip.bottom = getDisplayInfo().height;
return _resource->createContexts();
}
const GameDisplayInfo &SagaEngine::getDisplayInfo() {
switch (_gameDescription->gameId) {
case GID_ITE:
if (getLanguage() == Common::ZH_TWN)
return ITE_DisplayInfo_ZH;
if (isECS()) {
static GameDisplayInfo ITE_DisplayInfo_ECS;
if (!ITE_DisplayInfo_ECS.width) {
ITE_DisplayInfo_ECS = ITE_DisplayInfo;
ITE_DisplayInfo_ECS.statusTextColor = kITEECSBottomColorGreen;
ITE_DisplayInfo_ECS.statusBGColor = kITEECSColorBlack;
}
return ITE_DisplayInfo_ECS;
}
return ITE_DisplayInfo;
#ifdef ENABLE_IHNM
case GID_IHNM:
return IHNM_DisplayInfo;
#endif
default:
error("getDisplayInfo: Unknown game ID");
return ITE_DisplayInfo; // for compilers that don't support NORETURN
}
}
Common::Error SagaEngine::loadGameState(int slot) {
// Init the current chapter to 8 (character selection) for IHNM
if (getGameId() == GID_IHNM)
_scene->changeScene(-2, 0, kTransitionFade, 8);
// First scene sets up palette
_scene->changeScene(getStartSceneNumber(), 0, kTransitionNoFade);
_events->handleEvents(0); // Process immediate events
if (getGameId() == GID_ITE)
_interface->setMode(kPanelMain);
else
_interface->setMode(kPanelChapterSelection);
load(calcSaveFileName((uint)slot));
syncSoundSettings();
return Common::kNoError; // TODO: return success/failure
}
Common::Error SagaEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
save(calcSaveFileName((uint)slot), desc.c_str());
return Common::kNoError; // TODO: return success/failure
}
bool SagaEngine::canLoadGameStateCurrently(Common::U32String *msg) {
return !_scene->isInIntro() &&
(_interface->getMode() == kPanelMain || _interface->getMode() == kPanelChapterSelection);
}
bool SagaEngine::canSaveGameStateCurrently(Common::U32String *msg) {
return !_scene->isInIntro() &&
(_interface->getMode() == kPanelMain || _interface->getMode() == kPanelChapterSelection);
}
} // End of namespace Saga

53
engines/saga/module.mk Normal file
View File

@@ -0,0 +1,53 @@
MODULE := engines/saga
MODULE_OBJS := \
actor.o \
actor_path.o \
actor_walk.o \
animation.o \
console.o \
events.o \
font.o \
font_map.o \
gfx.o \
image.o \
input.o \
interface.o \
introproc_ite.o \
isomap.o \
itedata.o \
metaengine.o \
music.o \
objectmap.o \
palanim.o \
puzzle.o \
render.o \
resource.o \
resource_rsc.o \
saga.o \
saveload.o \
scene.o \
script.o \
sfuncs.o \
sndres.o \
sound.o \
sprite.o \
sthread.o
ifdef ENABLE_IHNM
MODULE_OBJS += \
introproc_ihnm.o \
resource_res.o \
sfuncs_ihnm.o
endif
# This module can be built as a plugin
ifeq ($(ENABLE_SAGA), DYNAMIC_PLUGIN)
PLUGIN := 1
endif
# Include common rules
include $(srcdir)/rules.mk
# Detection objects
DETECT_OBJS += $(MODULE)/detection.o

548
engines/saga/music.cpp Normal file
View File

@@ -0,0 +1,548 @@
/* 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/>.
*
*/
// MIDI and digital music class
#include "saga/saga.h"
#include "saga/resource.h"
#include "saga/music.h"
#include "audio/adlib_ms.h"
#include "audio/audiostream.h"
#include "audio/mididrv.h"
#include "audio/midiparser.h"
#include "audio/midiparser_qt.h"
#include "audio/miles.h"
#include "audio/decoders/flac.h"
#include "audio/decoders/mp3.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/vorbis.h"
#include "audio/mods/mod_xm_s3m.h"
#include "audio/softsynth/fmtowns_pc98/towns_pc98_driver.h"
#include "common/config-manager.h"
#include "common/file.h"
#include "common/substream.h"
#include "common/translation.h"
#include "gui/message.h"
namespace Saga {
const uint8 Music::MT32_GOODBYE_MSG[] = { 0x47, 0x6F, 0x6F, 0x64, 0x62, 0x79, 0x65, 0x21, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 };
Music::Music(SagaEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer), _parser(nullptr), _driver(nullptr), _driverPC98(nullptr), _musicContext(nullptr) {
_currentVolume = 0;
_currentMusicBuffer = nullptr;
if (_vm->getPlatform() == Common::kPlatformAmiga) {
_musicType = _driverType = MT_AMIGA;
} else if (_vm->getPlatform() == Common::kPlatformPC98) {
_musicType = _driverType = MT_PC98;
_driverPC98 = new TownsPC98_AudioDriver(mixer, PC98AudioPluginDriver::kType86);
_driverPC98->init();
} else {
_musicType = (_vm->getGameId() == GID_ITE && _vm->getPlatform() == Common::kPlatformDOS ? MT_MT32 : MT_GM);
MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | (_musicType == MT_MT32 ? MDT_PREFER_MT32 : MDT_PREFER_GM));
_driverType = MidiDriver::getMusicType(dev);
if (_driverType == MT_GM && ConfMan.getBool("native_mt32"))
_driverType = MT_MT32;
switch (_driverType) {
case MT_ADLIB:
if (_vm->getPlatform() == Common::kPlatformDOS) {
const char *opl2InstDefFilename;
const char *opl3InstDefFilename;
if (_vm->getGameId() == GID_ITE) {
opl2InstDefFilename = "INSTR.AD";
opl3InstDefFilename = "INSTR.OPL";
} else {
// IHNM
opl2InstDefFilename = "SAMPLE.AD";
opl3InstDefFilename = "SAMPLE.OPL";
}
if (Common::File::exists(opl2InstDefFilename) && Common::File::exists(opl3InstDefFilename)) {
_driver = (MidiDriver_Multisource *)Audio::MidiDriver_Miles_AdLib_create(opl2InstDefFilename, opl3InstDefFilename);
_driver->property(MidiDriver::PROP_MILES_VERSION, _vm->getGameId() == GID_ITE ?
Audio::MILES_VERSION_2 : Audio::MILES_VERSION_3);
} else {
// WORKAROUND The GOG version of IHNM is missing the AdLib
// instrument definition files. In this case we fall back
// to the regular AdLib driver, which has a built-in set of
// instrument definitions.
// We cannot distinguish between this GOG version and the
// case where the user has a physical version of the game,
// but has forgotten to copy the instrument definition
// files. So we show a warning that these files are missing.
GUI::MessageDialog dialog(
Common::U32String::format(
_("Could not find AdLib instrument definition files\n"
"%s and %s. Without these files,\n"
"the music will not sound the same as the original game."),
opl2InstDefFilename, opl3InstDefFilename));
dialog.runModal();
OPL::Config::OplType oplType =
MidiDriver_ADLIB_Multisource::detectOplType(OPL::Config::kOpl3) ? OPL::Config::kOpl3 : OPL::Config::kOpl2;
_driver = new MidiDriver_ADLIB_Multisource(oplType);
}
} else {
OPL::Config::OplType oplType =
MidiDriver_ADLIB_Multisource::detectOplType(OPL::Config::kOpl3) ? OPL::Config::kOpl3 : OPL::Config::kOpl2;
_driver = new MidiDriver_ADLIB_Multisource(oplType);
}
break;
case MT_MT32:
case MT_GM:
if (_vm->getPlatform() == Common::kPlatformDOS) {
_driver = Audio::MidiDriver_Miles_MIDI_create(_musicType, "");
_driver->property(MidiDriver::PROP_MILES_VERSION, _vm->getGameId() == GID_ITE ?
Audio::MILES_VERSION_2 : Audio::MILES_VERSION_3);
} else {
_driver = new MidiDriver_MT32GM(_musicType);
}
break;
default:
_driver = new MidiDriver_NULL_Multisource();
break;
}
if (_driver) {
_driver->property(MidiDriver::PROP_USER_VOLUME_SCALING, true);
if (_driver->open() != 0)
error("Failed to open MIDI driver.");
_driver->setTimerCallback(this, &timerCallback);
_driver->setSourceNeutralVolume(255);
}
}
_digitalMusicContext = _vm->_resource->getContext(GAME_DIGITALMUSICFILE);
if (_driverType != MT_ADLIB)
_musicContext = _vm->_resource->getContext(GAME_MUSICFILE_GM);
if (!_musicContext)
_musicContext = _vm->_resource->getContext(GAME_MUSICFILE_FM);
if (!_musicContext) {
if (_vm->getGameId() == GID_ITE) {
_musicContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
} else if (_vm->getGameId() == GID_IHNM) {
// TODO If program flow gets here, this getContext call previously
// returned null...
_musicContext = _vm->_resource->getContext(GAME_MUSICFILE_FM);
}
}
_trackNumber = 0;
_userVolume = 0;
_userMute = false;
_targetVolume = 0;
_currentVolumePercent = 100;
_digitalMusic = false;
}
Music::~Music() {
_vm->getTimerManager()->removeTimerProc(&musicVolumeGaugeCallback);
_mixer->stopHandle(_musicHandle);
if (_parser) {
_parser->stopPlaying();
delete _parser;
}
if (_driver) {
_driver->setTimerCallback(nullptr, nullptr);
_driver->close();
delete _driver;
}
if (_driverPC98) {
_driverPC98->reset();
delete _driverPC98;
}
}
void Music::close() {
if (_parser)
_parser->stopPlaying();
if (_vm->getGameId() == GID_ITE && _vm->getPlatform() == Common::kPlatformDOS && _driver) {
MidiDriver_MT32GM *mt32Driver = dynamic_cast<MidiDriver_MT32GM *>(_driver);
if (mt32Driver)
mt32Driver->sysExMT32(MT32_GOODBYE_MSG, MidiDriver_MT32GM::MT32_DISPLAY_NUM_CHARS,
MidiDriver_MT32GM::MT32_DISPLAY_MEMORY_ADDRESS, false, false);
}
}
void Music::musicVolumeGaugeCallback(void *refCon) {
((Music *)refCon)->musicVolumeGauge();
}
void Music::musicVolumeGauge() {
// CHECKME: This is potentially called from a different thread because it is
// called from a timer callback. However, it does not seem to take any
// precautions to avoid race conditions.
int volume;
_currentVolumePercent += 10;
if (_currentVolume - _targetVolume > 0) { // Volume decrease
volume = _targetVolume + (_currentVolume - _targetVolume) * (100 - _currentVolumePercent) / 100;
} else {
volume = _currentVolume + (_targetVolume - _currentVolume) * _currentVolumePercent / 100;
}
if (volume < 0)
volume = 1;
int scaledVolume;
if (_userMute) {
scaledVolume = 0;
} else {
scaledVolume = (volume * _userVolume) >> 8;
}
_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, scaledVolume);
if (_driverPC98)
_driverPC98->setMusicVolume(scaledVolume);
if (_currentVolumePercent == 100) {
_vm->getTimerManager()->removeTimerProc(&musicVolumeGaugeCallback);
_currentVolume = _targetVolume;
}
}
void Music::setVolume(int volume, int time) {
_targetVolume = volume;
if (volume == -1) // Set Full volume
volume = 255;
if (time == 1) {
if (_driver) {
if (_driver->isFading(0))
_driver->abortFade(0, MidiDriver_Multisource::FADE_ABORT_TYPE_CURRENT_VOLUME);
_driver->setSourceVolume(0, volume);
}
_currentVolumePercent = 100;
_vm->getTimerManager()->removeTimerProc(&musicVolumeGaugeCallback);
int scaledVolume;
if (_userMute) {
scaledVolume = 0;
} else {
scaledVolume = (volume * _userVolume) >> 8;
}
_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, scaledVolume);
if (_driverPC98)
_driverPC98->setMusicVolume(scaledVolume);
_currentVolume = volume;
return;
}
if (_driver)
_driver->startFade(0, time * 3, volume);
_currentVolumePercent = 0;
_vm->getTimerManager()->installTimerProc(&musicVolumeGaugeCallback, time * 300L, this, "sagaMusicVolume");
}
void Music::resetVolume() {
// Abort a fade / gauge if active and set volume to max.
setVolume(255);
}
bool Music::isFading() {
bool isFading = false;
if (_driver)
isFading = _driver->isFading(0);
isFading = isFading || (_currentVolumePercent < 100);
return isFading;
}
bool Music::isPlaying() {
return _mixer->isSoundHandleActive(_musicHandle) || (_parser ? _parser->isPlaying() : false) || (_driverPC98 ? _driverPC98->musicPlaying() : false);
}
void Music::play(uint32 resourceId, MusicFlags flags) {
debug(2, "Music::play %d, %d", resourceId, flags);
if (isPlaying() && _trackNumber == resourceId)
return;
if (_vm->getFeatures() & GF_ITE_DOS_DEMO) {
warning("TODO: Music::play %d, %d for ITE DOS demo", resourceId, flags);
return;
}
_trackNumber = resourceId;
_mixer->stopHandle(_musicHandle);
if (_parser) {
_parser->unloadMusic();
delete _parser;
_parser = nullptr;
}
if (_driverPC98)
_driverPC98->reset();
resetVolume();
bool digital = playDigital(resourceId, flags);
if (!digital) {
// Load MIDI/XMI resource data
if (_vm->getGameId() == GID_ITE && _vm->getPlatform() == Common::Platform::kPlatformAmiga) {
playProtracker(resourceId, flags);
} else if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) {
// Load the external music file for Mac IHNM
playQuickTime(resourceId, flags);
} else {
playMidi(resourceId, flags);
}
}
}
bool Music::playDigital(uint32 resourceId, MusicFlags flags) {
Audio::SeekableAudioStream *audioStream = nullptr;
uint32 loopStart = 0;
int realTrackNumber = 0;
if (_vm->getGameId() == GID_ITE) {
if (resourceId != 13 && resourceId != 19)
flags = MUSIC_LOOP;
realTrackNumber = resourceId - 8;
} else if (_vm->getGameId() == GID_IHNM) {
realTrackNumber = resourceId + 1;
}
// Try to open standalone digital track
char trackName[2][16];
Common::sprintf_s(trackName[0], "track%d", realTrackNumber);
Common::sprintf_s(trackName[1], "track%02d", realTrackNumber);
Audio::SeekableAudioStream *stream = nullptr;
for (int i = 0; i < 2; ++i) {
stream = Audio::SeekableAudioStream::openStreamFile(trackName[i]);
if (stream) {
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle,
Audio::makeLoopingAudioStream(stream, (flags == MUSIC_LOOP) ? 0 : 1));
_digitalMusic = true;
return true;
}
}
if (_vm->getGameId() == GID_ITE) {
if (resourceId >= 9 && resourceId <= 34) {
if (_digitalMusicContext != nullptr) {
loopStart = 0;
// Fix ITE sunstatm/sunspot score
if (resourceId == MUSIC_SUNSPOT)
loopStart = 18727;
// Digital music
ResourceData *resData = _digitalMusicContext->getResourceData(resourceId - 9);
Common::SeekableReadStream *musicFile = _digitalMusicContext->getFile(resData);
int offs = (_digitalMusicContext->isCompressed()) ? 9 : 0;
Common::SeekableSubReadStream *musicStream = new Common::SeekableSubReadStream(musicFile,
(uint32)resData->offset + offs, (uint32)resData->offset + resData->size - offs);
if (!_digitalMusicContext->isCompressed()) {
byte musicFlags = Audio::FLAG_STEREO |
Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
if (_vm->isBigEndian() || (_vm->getFeatures() & GF_SOME_MAC_RESOURCES))
musicFlags &= ~Audio::FLAG_LITTLE_ENDIAN;
// The newer ITE Mac demo version contains a music file, but it has mono music.
// This is the only music file that is about 7MB, whereas all the other ones
// are much larger. Thus, we use this simple heuristic to determine if we got
// mono music in the ITE demos or not.
if (!strcmp(_digitalMusicContext->fileName(), "musicd.rsc") &&
_digitalMusicContext->fileSize() < 8000000)
musicFlags &= ~Audio::FLAG_STEREO;
audioStream = Audio::makeRawStream(musicStream, 11025, musicFlags, DisposeAfterUse::YES);
} else {
// Read compressed header to determine compression type
musicFile->seek((uint32)resData->offset, SEEK_SET);
byte identifier = musicFile->readByte();
if (identifier == 0) { // MP3
#ifdef USE_MAD
audioStream = Audio::makeMP3Stream(musicStream, DisposeAfterUse::YES);
#endif
} else if (identifier == 1) { // OGG
#ifdef USE_VORBIS
audioStream = Audio::makeVorbisStream(musicStream, DisposeAfterUse::YES);
#endif
} else if (identifier == 2) { // FLAC
#ifdef USE_FLAC
audioStream = Audio::makeFLACStream(musicStream, DisposeAfterUse::YES);
#endif
}
}
if (!audioStream)
delete musicStream;
}
}
}
if (audioStream) {
debug(2, "Playing digitized music");
if (loopStart) {
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle,
new Audio::SubLoopingAudioStream(audioStream,
(flags == MUSIC_LOOP ? 0 : 1),
Audio::Timestamp(0, loopStart, audioStream->getRate()),
audioStream->getLength()));
} else {
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle,
Audio::makeLoopingAudioStream(audioStream, (flags == MUSIC_LOOP ? 0 : 1)));
}
_digitalMusic = true;
return true;
}
return false;
}
void Music::playQuickTime(uint32 resourceId, MusicFlags flags) {
// IHNM Mac uses QuickTime MIDI
_parser = MidiParser::createParser_QT();
_parser->setMidiDriver(_driver);
_parser->setTimerRate(_driver->getBaseTempo());
_parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
_parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1);
// Handle music looping
_parser->property(MidiParser::mpAutoLoop, flags & MUSIC_LOOP);
Common::Path musicName(Common::String::format("Music/Music%02x", resourceId));
if (!((MidiParser_QT *)_parser)->loadFromContainerFile(musicName))
error("Music::playQuickTime(): Failed to load file '%s'", musicName.toString().c_str());
_parser->setTrack(0);
}
void Music::playProtracker(uint32 resourceId, MusicFlags flags) {
ByteArray ba;
_vm->_resource->loadResource(_musicContext, resourceId, ba);
Common::MemoryReadStream ms(ba.getBuffer(), ba.size());
/* No reference to the 'stream' object is kept, so you can safely delete it after
invoking this factory. */
Audio::RewindableAudioStream *amigaModStream = Audio::makeModXmS3mStream(&ms, DisposeAfterUse::NO);
_mixer->playStream(Audio::Mixer::kMusicSoundType, &_musicHandle,
Audio::makeLoopingAudioStream(amigaModStream, (flags == MUSIC_LOOP ? 0 : 1)));
}
void Music::playMidi(uint32 resourceId, MusicFlags flags) {
if (_currentMusicBuffer == &_musicBuffer[1]) {
_currentMusicBuffer = &_musicBuffer[0];
} else {
_currentMusicBuffer = &_musicBuffer[1];
}
_vm->_resource->loadResource(_musicContext, resourceId, *_currentMusicBuffer);
if (_driverPC98) {
_driverPC98->loadMusicData(_currentMusicBuffer->data() + 4);
} else {
if (_currentMusicBuffer->size() < 4) {
error("Music::playMidi() wrong music resource size");
}
// Check if the game is using XMIDI or SMF music
if (!memcmp(_currentMusicBuffer->getBuffer(), "FORM", 4)) {
_parser = MidiParser::createParser_XMIDI(nullptr, nullptr, 0);
} else {
_parser = MidiParser::createParser_SMF(0);
}
_parser->setMidiDriver(_driver);
_parser->setTimerRate(_driver->getBaseTempo());
if (_vm->getGameId() == GID_IHNM) {
// IHNM XMIDI uses sustain and does not reset pitch bend at the
// start of a new track.
_parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
_parser->property(MidiParser::mpSendSustainOffOnNotesOff, 1);
}
// Handle music looping
_parser->property(MidiParser::mpAutoLoop, flags & MUSIC_LOOP);
if (!_parser->loadMusic(_currentMusicBuffer->getBuffer(), _currentMusicBuffer->size()))
error("Music::play() wrong music resource");
}
}
void Music::pause() {
if (_parser) {
_parser->pausePlaying();
} else if (_driverPC98) {
_driverPC98->pause();
}
}
void Music::resume() {
if (_parser) {
_parser->resumePlaying();
} else if (_driverPC98) {
_driverPC98->cont();
}
}
void Music::stop() {
if (_parser)
_parser->stopPlaying();
else if (_driverPC98)
_driverPC98->reset();
}
void Music::syncSoundSettings() {
if (_driver)
_driver->syncSoundSettings();
_userVolume = ConfMan.getInt("music_volume");
_userMute = ConfMan.hasKey("mute") && ConfMan.getBool("mute");
setVolume(_currentVolume);
}
void Music::onTimer() {
if (_parser)
_parser->onTimer();
}
void Music::timerCallback(void *data) {
((Music *)data)->onTimer();
}
} // End of namespace Saga

109
engines/saga/music.h Normal file
View File

@@ -0,0 +1,109 @@
/* 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/>.
*
*/
// Music class
#ifndef SAGA_MUSIC_H
#define SAGA_MUSIC_H
#include "audio/audiostream.h"
#include "audio/mididrv.h"
#include "audio/mididrv_ms.h"
#include "audio/mt32gm.h"
#include "audio/midiparser.h"
#include "audio/mixer.h"
#include "audio/softsynth/fmtowns_pc98/towns_pc98_driver.h"
class TownsPC98_AudioDriver;
namespace Saga {
enum MusicFlags {
MUSIC_NORMAL = 0,
MUSIC_LOOP = 0x0001
};
class Music {
private:
static const uint8 MUSIC_SUNSPOT = 26;
static const uint8 MT32_GOODBYE_MSG[MidiDriver_MT32GM::MT32_DISPLAY_NUM_CHARS];
public:
Music(SagaEngine *vm, Audio::Mixer *mixer);
~Music();
void close();
bool isPlaying();
bool hasDigitalMusic() { return _digitalMusic; }
void play(uint32 resourceId, MusicFlags flags = MUSIC_NORMAL);
void pause();
void resume();
void stop();
void setVolume(int volume, int time = 1);
int getVolume() { return _currentVolume; }
void resetVolume();
bool isFading();
bool isAdlib() const { return _driverType == MT_ADLIB; }
void syncSoundSettings();
Common::Array<int32> _songTable;
private:
SagaEngine *_vm;
Audio::Mixer *_mixer;
MidiParser *_parser;
MidiDriver_Multisource *_driver;
TownsPC98_AudioDriver *_driverPC98;
Audio::SoundHandle _musicHandle;
uint32 _trackNumber;
int _userVolume;
bool _userMute;
int _targetVolume;
int _currentVolume;
int _currentVolumePercent;
bool _digitalMusic;
MusicType _musicType;
MusicType _driverType;
ResourceContext *_musicContext;
ResourceContext *_digitalMusicContext;
static void musicVolumeGaugeCallback(void *refCon);
static void timerCallback(void *refCon);
void onTimer();
bool playDigital(uint32 resourceId, MusicFlags flags);
void playProtracker(uint32 resourceId, MusicFlags flags);
void playQuickTime(uint32 resourceId, MusicFlags flags);
void playMidi(uint32 resourceId, MusicFlags flags);
void musicVolumeGauge();
ByteArray *_currentMusicBuffer;
ByteArray _musicBuffer[2];
};
} // End of namespace Saga
#endif

236
engines/saga/objectmap.cpp Normal file
View File

@@ -0,0 +1,236 @@
/* 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/>.
*
*/
// Object map / Object click-area module
// Polygon Hit Test code ( HitTestPoly() ) adapted from code (C) Eric Haines
// appearing in Graphics Gems IV, "Point in Polygon Strategies."
// p. 24-46, code: p. 34-45
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/console.h"
#include "saga/font.h"
#include "saga/interface.h"
#include "saga/objectmap.h"
#include "saga/actor.h"
#include "saga/scene.h"
#include "saga/isomap.h"
#ifdef SAGA_DEBUG
#include "saga/render.h"
#endif
namespace Saga {
void HitZone::load(SagaEngine *vm, Common::MemoryReadStreamEndian *readStream, int index, int sceneNumber) {
_index = index;
_flags = readStream->readByte();
_clickAreas.resize(readStream->readByte());
_rightButtonVerb = readStream->readByte();
readStream->readByte(); // pad
_nameIndex = readStream->readUint16();
_scriptNumber = readStream->readUint16();
for (auto &area : _clickAreas) {
area.resize(readStream->readUint16LE());
assert(!area.empty());
for (auto &point : area) {
point.x = readStream->readSint16();
point.y = readStream->readSint16();
// WORKAROUND: bug #2154: "ITE: Riff ignores command in Ferret merchant center"
// Apparently ITE Mac version has bug in game data. Both ObjectMap and ActionMap
// for exit area are little taller (y = 123) and thus Riff goes to exit
// when clicked on barrel of nails.
if (vm->getGameId() == GID_ITE) {
if (sceneNumber == 18 && index == 0 && (&area == _clickAreas.begin()) && (&point == area.begin()) && point.y == 123) {
point.y = 129;
}
}
}
}
}
bool HitZone::getSpecialPoint(Point &specialPoint) const {
for (const auto &area : _clickAreas) {
if (area.size() == 1) {
specialPoint = area[0];
return true;
}
}
return false;
}
bool HitZone::hitTest(const Point &testPoint) {
const Point *points;
uint pointsCount;
if (_flags & kHitZoneEnabled) {
for (const auto &area : _clickAreas) {
pointsCount = area.size();
if (pointsCount < 2) {
continue;
}
points = &area.front();
if (pointsCount == 2) {
// Hit-test a box region
if ((testPoint.x >= points[0].x) &&
(testPoint.x <= points[1].x) &&
(testPoint.y >= points[0].y) &&
(testPoint.y <= points[1].y)) {
return true;
}
} else {
// Hit-test a polygon
if (hitTestPoly(points, pointsCount, testPoint)) {
return true;
}
}
}
}
return false;
}
#ifdef SAGA_DEBUG
void HitZone::draw(SagaEngine *vm, int color) {
int pointsCount, j;
Location location;
HitZone::ClickArea tmpPoints;
const Point *points;
Point specialPoint1;
Point specialPoint2;
for (const auto &area : _clickAreas) {
pointsCount = area.size();
points = &area.front();
if (vm->_scene->getFlags() & kSceneFlagISO) {
tmpPoints.resize(pointsCount);
for (j = 0; j < pointsCount; j++) {
location.u() = points[j].x;
location.v() = points[j].y;
location.z = 0;
vm->_isoMap->tileCoordsToScreenPoint(location, tmpPoints[j]);
}
points = &tmpPoints.front();
}
if (pointsCount == 2) {
// 2 points represent a box
vm->_gfx->drawFrame(points[0], points[1], color);
} else {
if (pointsCount > 2) {
// Otherwise draw a polyline
// Do a full refresh so that the polyline can be shown
vm->_render->setFullRefresh(true);
vm->_gfx->drawPolyLine(points, pointsCount, color);
}
}
}
if (getSpecialPoint(specialPoint1)) {
specialPoint2 = specialPoint1;
specialPoint1.x--;
specialPoint1.y--;
specialPoint2.x++;
specialPoint2.y++;
vm->_gfx->drawFrame(specialPoint1, specialPoint2, color);
}
}
#endif
// Loads an object map resource ( objects ( clickareas ( points ) ) )
void ObjectMap::load(const ByteArray &resourceData) {
if (!_hitZoneList.empty()) {
error("ObjectMap::load _hitZoneList not empty");
}
if (resourceData.empty()) {
return;
}
if (resourceData.size() < 4) {
error("ObjectMap::load wrong resourceLength");
}
ByteArrayReadStreamEndian readS(resourceData, _vm->isBigEndian());
_hitZoneList.resize(readS.readUint16());
int idx = 0;
for (auto &hitZone : _hitZoneList) {
hitZone.load(_vm, &readS, idx++, _vm->_scene->currentSceneNumber());
}
}
void ObjectMap::clear() {
_hitZoneList.clear();
}
#ifdef SAGA_DEBUG
void ObjectMap::draw(const Point& testPoint, int color, int color2) {
int hitZoneIndex;
Common::String txtBuf;
Point pickPoint;
Point textPoint;
Location pickLocation;
pickPoint = testPoint;
if (_vm->_scene->getFlags() & kSceneFlagISO) {
assert(_vm->_actor->_protagonist);
pickPoint.y -= _vm->_actor->_protagonist->_location.z;
_vm->_isoMap->screenPointToTileCoords(pickPoint, pickLocation);
pickLocation.toScreenPointUV(pickPoint);
}
hitZoneIndex = hitTest(pickPoint);
for (auto &i : _hitZoneList) {
i->draw(_vm, (hitZoneIndex == i->getIndex()) ? color2 : color);
}
if (hitZoneIndex != -1) {
txtBuf = Common::String::format("hitZone %d", hitZoneIndex);
textPoint.x = 2;
textPoint.y = 2;
_vm->_font->textDraw(kKnownFontSmall, txtBuf.c_str(), textPoint, kITEColorBrightWhite, kITEColorBlack, kFontOutline);
}
}
#endif
int ObjectMap::hitTest(const Point& testPoint) {
// Loop through all scene objects
for (auto &hitZone : _hitZoneList) {
if (hitZone.hitTest(testPoint)) {
return hitZone.getIndex();
}
}
return -1;
}
void ObjectMap::cmdInfo() {
_vm->_console->debugPrintf("%d zone(s) loaded.\n\n", _hitZoneList.size());
}
} // End of namespace Saga

118
engines/saga/objectmap.h Normal file
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/>.
*
*/
// Object map / Object click-area module header file
#ifndef SAGA_OBJECTMAP_H
#define SAGA_OBJECTMAP_H
namespace Saga {
class HitZone {
private:
typedef Common::Array<Point> ClickArea;
typedef Common::Array<ClickArea> ClickAreas;
public:
void load(SagaEngine *vm, Common::MemoryReadStreamEndian *readStream, int index, int sceneNumber);
int getIndex() const {
return _index;
}
int getNameIndex() const {
return _nameIndex;
}
int getSceneNumber() const {
return _nameIndex;
}
int getActorsEntrance() const {
return _scriptNumber;
}
int getScriptNumber() const {
return _scriptNumber;
}
int getRightButtonVerb() const {
return _rightButtonVerb;
}
int getFlags() const {
return _flags;
}
void setFlag(HitZoneFlags flag) {
_flags |= flag;
}
void clearFlag(HitZoneFlags flag) {
_flags &= ~flag;
}
int getDirection() const {
return ((_flags >> 4) & 0xF);
}
uint16 getHitZoneId() const {
return objectIndexToId(kGameObjectHitZone, _index);
}
uint16 getStepZoneId() const {
return objectIndexToId(kGameObjectStepZone, _index);
}
bool getSpecialPoint(Point &specialPoint) const;
#ifdef SAGA_DEBUG
void draw(SagaEngine *vm, int color); // for debugging
#endif
bool hitTest(const Point &testPoint);
private:
int _flags; // Saga::HitZoneFlags
int _rightButtonVerb;
int _nameIndex;
int _scriptNumber;
int _index;
ClickAreas _clickAreas;
};
typedef Common::Array<HitZone> HitZoneArray;
class ObjectMap {
public:
ObjectMap(SagaEngine *vm) : _vm(vm) {
}
void load(const ByteArray &resourceData);
void clear();
#ifdef SAGA_DEBUG
void draw(const Point& testPoint, int color, int color2); // for debugging
#endif
int hitTest(const Point& testPoint);
HitZone *getHitZone(int16 index) {
if (uint(index) >= _hitZoneList.size()) {
return NULL;
}
return &_hitZoneList[index];
}
void cmdInfo();
private:
SagaEngine *_vm;
HitZoneArray _hitZoneList;
};
} // End of namespace Saga
#endif

145
engines/saga/palanim.cpp Normal file
View File

@@ -0,0 +1,145 @@
/* 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/>.
*
*/
// Palette animation module
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/events.h"
#include "saga/palanim.h"
#include "saga/interface.h"
namespace Saga {
PalAnim::PalAnim(SagaEngine *vm) : _vm(vm) {
}
void PalAnim::loadPalAnim(const ByteArray &resourceData) {
clear();
if (resourceData.empty()) {
return;
}
ByteArrayReadStreamEndian readS(resourceData, _vm->isBigEndian());
if (_vm->getGameId() == GID_IHNM) {
return;
}
_entries.resize(readS.readUint16());
debug(3, "PalAnim::loadPalAnim(): Loading %d PALANIM entries.", _entries.size());
for (auto &palAnimEntry : _entries) {
palAnimEntry.cycle = 0;
palAnimEntry.colors.resize(readS.readUint16());
debug(2, "PalAnim::loadPalAnim(): Loading %d SAGA_COLOR structures.", palAnimEntry.colors.size());
palAnimEntry.palIndex.resize(readS.readUint16());
debug(2, "PalAnim::loadPalAnim(): Loading %d palette indices.\n", palAnimEntry.palIndex.size());
for (uint j = 0; j < palAnimEntry.palIndex.size(); j++) {
palAnimEntry.palIndex[j] = readS.readByte();
}
for (auto &color : palAnimEntry.colors) {
color.red = readS.readByte();
color.green = readS.readByte();
color.blue = readS.readByte();
}
}
}
void PalAnim::cycleStart() {
Event event;
if (_entries.empty()) {
return;
}
event.type = kEvTOneshot;
event.code = kPalAnimEvent;
event.op = kEventCycleStep;
event.time = PALANIM_CYCLETIME;
_vm->_events->queue(event);
}
void PalAnim::cycleStep(int vectortime) {
static PalEntry pal[256];
uint16 palIndex;
uint16 colIndex;
uint16 j;
uint16 cycle;
uint16 cycleLimit;
Event event;
if (_entries.empty()) {
return;
}
_vm->_gfx->getCurrentPal(pal);
for (auto &palAnimEntry : _entries) {
cycle = palAnimEntry.cycle;
cycleLimit = palAnimEntry.colors.size();
for (j = 0; j < palAnimEntry.palIndex.size(); j++) {
palIndex = palAnimEntry.palIndex[j];
colIndex = (cycle + j) % cycleLimit;
pal[palIndex].red = (byte) palAnimEntry.colors[colIndex].red;
pal[palIndex].green = (byte) palAnimEntry.colors[colIndex].green;
pal[palIndex].blue = (byte) palAnimEntry.colors[colIndex].blue;
}
palAnimEntry.cycle++;
if (palAnimEntry.cycle == cycleLimit) {
palAnimEntry.cycle = 0;
}
}
// Don't cycle the palette when the map is open
// Fixes bug #3636 - "ITE: Glitch when looking at the map while at the docks"
if (_vm->_interface->getMode() != kPanelMap)
_vm->_gfx->setPalette(pal);
event.type = kEvTOneshot;
event.code = kPalAnimEvent;
event.op = kEventCycleStep;
event.time = vectortime + PALANIM_CYCLETIME;
_vm->_events->queue(event);
}
void PalAnim::clear() {
debug(3, "PalAnim::clear()");
_entries.clear();
}
} // End of namespace Saga

54
engines/saga/palanim.h Normal file
View File

@@ -0,0 +1,54 @@
/* 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/>.
*
*/
// Palette animation module header file
#ifndef SAGA_PALANIM_H
#define SAGA_PALANIM_H
namespace Saga {
#define PALANIM_CYCLETIME 100
struct PalanimEntry {
uint16 cycle;
ByteArray palIndex;
Common::Array<Color> colors;
};
class PalAnim {
public:
PalAnim(SagaEngine *vm);
void loadPalAnim(const ByteArray &resourceData);
void cycleStart();
void cycleStep(int vectortime);
void clear();
private:
SagaEngine *_vm;
Common::Array<PalanimEntry> _entries;
};
} // End of namespace Saga
#endif

583
engines/saga/puzzle.cpp Normal file
View File

@@ -0,0 +1,583 @@
/* 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 "saga/saga.h"
#include "saga/actor.h"
#include "saga/interface.h"
#include "saga/scene.h"
#include "saga/sprite.h"
#include "saga/puzzle.h"
#include "saga/render.h"
#include "common/system.h"
#include "common/timer.h"
namespace Saga {
#define ITE_ACTOR_PUZZLE 176
#define PUZZLE_X_OFFSET 72
#define PUZZLE_Y_OFFSET 46
#define PUZZLE_FIT 0x01 // 1 when in correct position
#define PUZZLE_MOVED 0x04 // 1 when somewhere in the box
#define PUZZLE_ALL_SET PUZZLE_FIT | PUZZLE_MOVED
// Puzzle portraits
#define RID_ITE_SAKKA_APPRAISING 6
#define RID_ITE_SAKKA_DENIAL 7
#define RID_ITE_SAKKA_EXCITED 8
#define RID_ITE_JFERRET_SERIOUS 9
#define RID_ITE_JFERRET_GOOFY 10
#define RID_ITE_JFERRET_ALOOF 11
const char portraitList[] = {
RID_ITE_JFERRET_SERIOUS,
RID_ITE_JFERRET_GOOFY,
RID_ITE_JFERRET_SERIOUS,
RID_ITE_JFERRET_GOOFY,
RID_ITE_JFERRET_ALOOF
};
enum rifOptions {
kROLater = 0,
kROAccept = 1,
kRODecline = 2,
kROHint = 3
};
Puzzle::Puzzle(SagaEngine *vm) : _vm(vm), _solved(false), _active(false) {
_lang = _vm->getLanguageIndex();
_hintRqState = kRQNoHint;
_hintOffer = 0;
_hintCount = 0;
_helpCount = 0;
_puzzlePiece = -1;
_newPuzzle = true;
_sliding = false;
_hintBox.left = 70;
_hintBox.top = 105;
_hintBox.setWidth(240);
_hintBox.setHeight(30);
_hintNextRqState = kRQNoHint;
_hintGiver = 0;
_hintSpeaker = 0;
_slidePointX = _slidePointY = 0;
initPieceInfo(0, 268, 18, 0, 0, 0 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 3,
Point(0, 1), Point(0, 62), Point(15, 31), Point(0, 0), Point(0, 0), Point(0, 0));
initPieceInfo(1, 270, 52, 0, 0, 0 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 4,
Point(0, 31), Point(0, 47), Point(39, 47), Point(15, 1), Point(0, 0), Point(0, 0));
initPieceInfo(2, 19, 51, 0, 0, 0 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 4,
Point(0, 0), Point(23, 46), Point(39, 15), Point(31, 0), Point(0, 0), Point(0, 0));
initPieceInfo(3, 73, 0, 0, 0, 32 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 6,
Point(0, 0), Point(8, 16), Point(0, 31), Point(31, 31), Point(39, 15), Point(31, 0));
initPieceInfo(4, 0, 35, 0, 0, 64 + PUZZLE_X_OFFSET, 16 + PUZZLE_Y_OFFSET, 0, 4,
Point(0, 15), Point(15, 46), Point(23, 32), Point(7, 1), Point(0, 0), Point(0, 0));
initPieceInfo(5, 215, 0, 0, 0, 24 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 6,
Point(0, 15), Point(8, 31), Point(39, 31), Point(47, 16), Point(39, 0), Point(8, 0));
initPieceInfo(6, 159, 0, 0, 0, 32 + PUZZLE_X_OFFSET, 48 + PUZZLE_Y_OFFSET, 0, 5,
Point(0, 16), Point(8, 31), Point(55, 31), Point(39, 1), Point(32, 15), Point(0, 0));
initPieceInfo(7, 9, 70, 0, 0, 80 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 5,
Point(0, 31), Point(8, 47), Point(23, 47), Point(31, 31), Point(15, 1), Point(0, 0));
initPieceInfo(8, 288, 18, 0, 0, 96 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 4,
Point(0, 31), Point(15, 62), Point(31, 32), Point(15, 1), Point(0, 0), Point(0, 0));
initPieceInfo(9, 112, 0, 0, 0, 112 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 4,
Point(0, 0), Point(16, 31), Point(47, 31), Point(31, 0), Point(0, 0), Point(0, 0));
initPieceInfo(10, 27, 89, 0, 0, 104 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 4,
Point(0, 47), Point(31, 47), Point(31, 0), Point(24, 0), Point(0, 0), Point(0, 0));
initPieceInfo(11, 43, 0, 0, 0, 136 + PUZZLE_X_OFFSET, 32 + PUZZLE_Y_OFFSET, 0, 6,
Point(0, 0), Point(0, 47), Point(15, 47), Point(15, 15), Point(31, 15), Point(23, 0));
initPieceInfo(12, 0, 0, 0, 0, 144 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 4,
Point(0, 0), Point(24, 47), Point(39, 47), Point(39, 0), Point(0, 0), Point(0, 0));
initPieceInfo(13, 262, 0, 0, 0, 64 + PUZZLE_X_OFFSET, 0 + PUZZLE_Y_OFFSET, 0, 3,
Point(0, 0), Point(23, 46), Point(47, 0), Point(0, 0), Point(0, 0), Point(0, 0));
initPieceInfo(14, 271, 103, 0, 0, 152 + PUZZLE_X_OFFSET, 48 + PUZZLE_Y_OFFSET, 0, 4,
Point(0, 0), Point(0, 31), Point(31, 31), Point(31, 0), Point(0, 0), Point(0, 0));
}
void Puzzle::initPieceInfo(int i, int16 curX, int16 curY, byte offX, byte offY, int16 trgX,
int16 trgY, uint8 flag, uint8 count, Point point0, Point point1,
Point point2, Point point3, Point point4, Point point5) {
_pieceInfo[i].curX = curX;
_pieceInfo[i].curY = curY;
_pieceInfo[i].offX = offX;
_pieceInfo[i].offY = offY;
_pieceInfo[i].trgX = trgX;
_pieceInfo[i].trgY = trgY;
_pieceInfo[i].flag = flag;
_pieceInfo[i].count = count;
_pieceInfo[i].point[0] = point0;
_pieceInfo[i].point[1] = point1;
_pieceInfo[i].point[2] = point2;
_pieceInfo[i].point[3] = point3;
_pieceInfo[i].point[4] = point4;
_pieceInfo[i].point[5] = point5;
}
void Puzzle::execute() {
_active = true;
_vm->getTimerManager()->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this, "sagaPuzzleHint");
initPieces();
showPieces();
_vm->_interface->setMode(kPanelConverse);
clearHint();
//_solved = true; // Cheat
//exitPuzzle();
}
void Puzzle::exitPuzzle() {
_active = false;
_vm->getTimerManager()->removeTimerProc(&hintTimerCallback);
_vm->_scene->changeScene(ITE_SCENE_LODGE, 0, kTransitionNoFade);
_vm->_interface->setMode(kPanelMain);
}
void Puzzle::initPieces() {
SpriteInfo *spI;
ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
int frameNumber;
SpriteList *spriteList;
_vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);
for (int i = 0; i < PUZZLE_PIECES; i++) {
spI = &((*spriteList)[i]);
_pieceInfo[i].offX = (byte)(spI->width >> 1);
_pieceInfo[i].offY = (byte)(spI->height >> 1);
if (_newPuzzle) {
_pieceInfo[i].curX = pieceOrigins[i].x;
_pieceInfo[i].curY = pieceOrigins[i].y;
}
_piecePriority[i] = i;
}
_newPuzzle = false;
}
void Puzzle::showPieces() {
ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
int frameNumber;
SpriteList *spriteList;
_vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);
for (int j = PUZZLE_PIECES - 1; j >= 0; j--) {
int num = _piecePriority[j];
if (_puzzlePiece != num) {
_vm->_sprite->draw(*spriteList, num, Point(_pieceInfo[num].curX, _pieceInfo[num].curY), 256);
}
}
}
void Puzzle::drawCurrentPiece() {
ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
int frameNumber;
SpriteList *spriteList;
_vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);
_vm->_sprite->draw(*spriteList, _puzzlePiece,
Point(_pieceInfo[_puzzlePiece].curX, _pieceInfo[_puzzlePiece].curY), 256, true);
}
void Puzzle::movePiece(Point mousePt) {
int newx, newy;
showPieces();
if (_puzzlePiece == -1)
return;
if (_sliding) {
newx = _slidePointX;
newy = _slidePointY;
} else {
if (mousePt.y >= 137)
return;
newx = mousePt.x;
newy = mousePt.y;
}
newx -= _pieceInfo[_puzzlePiece].offX;
newy -= _pieceInfo[_puzzlePiece].offY;
_pieceInfo[_puzzlePiece].curX = newx;
_pieceInfo[_puzzlePiece].curY = newy;
drawCurrentPiece();
}
void Puzzle::handleClick(Point mousePt) {
if (_puzzlePiece != -1) {
dropPiece(mousePt);
if (!_active)
return; // we won
drawCurrentPiece();
_puzzlePiece = -1;
return;
}
for (int j = 0; j < PUZZLE_PIECES; j++) {
int i = _piecePriority[j];
int adjX = mousePt.x - _pieceInfo[i].curX;
int adjY = mousePt.y - _pieceInfo[i].curY;
if (hitTestPoly(&_pieceInfo[i].point[0], _pieceInfo[i].count, Point(adjX, adjY))) {
_puzzlePiece = i;
break;
}
}
if (_puzzlePiece == -1)
return;
alterPiecePriority();
// Display scene background
_vm->_scene->draw();
showPieces();
int newx = mousePt.x - _pieceInfo[_puzzlePiece].offX;
int newy = mousePt.y - _pieceInfo[_puzzlePiece].offY;
if (newx != _pieceInfo[_puzzlePiece].curX
|| newy != _pieceInfo[_puzzlePiece].curY) {
_pieceInfo[_puzzlePiece].curX = newx;
_pieceInfo[_puzzlePiece].curY = newy;
}
_vm->_interface->setStatusText(pieceNames[_lang][_puzzlePiece]);
}
void Puzzle::alterPiecePriority() {
for (int i = 1; i < PUZZLE_PIECES; i++) {
if (_puzzlePiece == _piecePriority[i]) {
for (int j = i - 1; j >= 0; j--)
_piecePriority[j+1] = _piecePriority[j];
_piecePriority[0] = _puzzlePiece;
break;
}
}
}
void Puzzle::slidePiece(int x1, int y1, int x2, int y2) {
int count;
PointList slidePoints;
slidePoints.resize(320);
x1 += _pieceInfo[_puzzlePiece].offX;
y1 += _pieceInfo[_puzzlePiece].offY;
count = pathLine(slidePoints, 0, Point(x1, y1),
Point(x2 + _pieceInfo[_puzzlePiece].offX, y2 + _pieceInfo[_puzzlePiece].offY));
if (count > 1) {
int factor = count / 4;
_sliding = true;
if (!factor)
factor++;
for (int i = 1; i < count; i += factor) {
_slidePointX = slidePoints[i].x;
_slidePointY = slidePoints[i].y;
_vm->_render->drawScene();
_vm->_system->delayMillis(10);
}
_sliding = false;
}
_pieceInfo[_puzzlePiece].curX = x2;
_pieceInfo[_puzzlePiece].curY = y2;
}
void Puzzle::dropPiece(Point mousePt) {
int boxx = PUZZLE_X_OFFSET;
int boxy = PUZZLE_Y_OFFSET;
int boxw = boxx + 184;
int boxh = boxy + 80;
// if the center is within the box quantize within
// else move it back to its original start point
if (mousePt.x >= boxx && mousePt.x < boxw && mousePt.y >= boxy && mousePt.y <= boxh) {
ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE));
SpriteInfo *spI;
int frameNumber;
SpriteList *spriteList;
_vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList);
int newx = mousePt.x - _pieceInfo[_puzzlePiece].offX;
int newy = mousePt.y - _pieceInfo[_puzzlePiece].offY;
if (newx < boxx)
newx = PUZZLE_X_OFFSET;
if (newy < boxy)
newy = PUZZLE_Y_OFFSET;
spI = &((*spriteList)[_puzzlePiece]);
if (newx + spI->width > boxw)
newx = boxw - spI->width;
if (newy + spI->height > boxh)
newy = boxh - spI->height;
int x1 = ((newx - PUZZLE_X_OFFSET) & ~7) + PUZZLE_X_OFFSET;
int y1 = ((newy - PUZZLE_Y_OFFSET) & ~7) + PUZZLE_Y_OFFSET;
int x2 = x1 + 8;
int y2 = y1 + 8;
newx = (x2 - newx < newx - x1) ? x2 : x1;
newy = (y2 - newy < newy - y1) ? y2 : y1;
// if any part of the puzzle piece falls outside the box
// force it back in
// is the piece at the target location
if (newx == _pieceInfo[_puzzlePiece].trgX
&& newy == _pieceInfo[_puzzlePiece].trgY) {
_pieceInfo[_puzzlePiece].flag |= (PUZZLE_MOVED | PUZZLE_FIT);
} else {
_pieceInfo[_puzzlePiece].flag &= ~PUZZLE_FIT;
_pieceInfo[_puzzlePiece].flag |= PUZZLE_MOVED;
}
_pieceInfo[_puzzlePiece].curX = newx;
_pieceInfo[_puzzlePiece].curY = newy;
} else {
int newx = pieceOrigins[_puzzlePiece].x;
int newy = pieceOrigins[_puzzlePiece].y;
_pieceInfo[_puzzlePiece].flag &= ~(PUZZLE_FIT | PUZZLE_MOVED);
// slide piece from current position to new position
slidePiece(_pieceInfo[_puzzlePiece].curX, _pieceInfo[_puzzlePiece].curY,
newx, newy);
}
// is the puzzle completed?
_solved = true;
for (int i = 0; i < PUZZLE_PIECES; i++)
if ((_pieceInfo[i].flag & PUZZLE_FIT) == 0) {
_solved = false;
break;
}
if (_solved)
exitPuzzle();
}
void Puzzle::hintTimerCallback(void *refCon) {
((Puzzle *)refCon)->solicitHint();
}
void Puzzle::solicitHint() {
// CHECKME: This is potentially called from a different thread because it is
// called from a timer callback. However, it does not seem to take any
// precautions to avoid race conditions.
int i;
_vm->_actor->setSpeechColor(1, _vm->iteColorBlack());
_vm->getTimerManager()->removeTimerProc(&hintTimerCallback);
switch (_hintRqState) {
case kRQSpeaking:
if (_vm->_actor->isSpeaking()) {
_vm->getTimerManager()->installTimerProc(&hintTimerCallback, 50 * 1000000, this, "sagaPuzzleHint");
break;
}
_hintRqState = _hintNextRqState;
_vm->getTimerManager()->installTimerProc(&hintTimerCallback, 100*1000000/3, this, "sagaPuzzleHint");
break;
case kRQNoHint:
// Pick a random hint request.
i = _hintOffer++;
if (_hintOffer >= NUM_SOLICIT_REPLIES)
_hintOffer = 0;
// Determine which of the journeymen will offer then
// hint, and then show that character's portrait.
_hintGiver = portraitList[i];
_hintSpeaker = _hintGiver - RID_ITE_JFERRET_SERIOUS;
_vm->_interface->setRightPortrait(_hintGiver);
_vm->_actor->nonActorSpeech(_hintBox, &solicitStr[_lang][i], 1, PUZZLE_SOLICIT_SOUNDS + i * 3 + _hintSpeaker, 0);
// Add Rif's reply to the list.
clearHint();
// Roll to see if Sakka scolds
if (_vm->_rnd.getRandomNumber(1)) {
_hintRqState = kRQSakkaDenies;
_vm->getTimerManager()->installTimerProc(&hintTimerCallback, 200*1000000, this, "sagaPuzzleHint");
} else {
_hintRqState = kRQSpeaking;
_hintNextRqState = kRQHintRequested;
_vm->getTimerManager()->installTimerProc(&hintTimerCallback, 50*1000000, this, "sagaPuzzleHint");
}
break;
case kRQHintRequested:
i = _vm->_rnd.getRandomNumber(NUM_SAKKA - 1);
_vm->_actor->nonActorSpeech(_hintBox, &sakkaStr[_lang][i], 1, PUZZLE_SAKKA_SOUNDS + i, 0);
_vm->_interface->setRightPortrait(RID_ITE_SAKKA_APPRAISING);
_hintRqState = kRQSpeaking;
_hintNextRqState = kRQHintRequestedStage2;
_vm->getTimerManager()->installTimerProc(&hintTimerCallback, 50*1000000, this, "sagaPuzzleHint");
_vm->_interface->converseClear();
_vm->_interface->converseAddText(optionsStr[_lang][kROAccept], 0, 1, 0, 0);
_vm->_interface->converseAddText(optionsStr[_lang][kRODecline], 0, 2, 0, 0);
_vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0, 0);
_vm->_interface->converseDisplayText();
break;
case kRQHintRequestedStage2:
if (_vm->_rnd.getRandomNumber(1)) { // Skip Reply part
i = _vm->_rnd.getRandomNumber(NUM_WHINES - 1);
_vm->_actor->nonActorSpeech(_hintBox, &whineStr[_lang][i], 1, PUZZLE_WHINE_SOUNDS + i * 3 + _hintSpeaker, 0);
}
_vm->_interface->setRightPortrait(_hintGiver);
_hintRqState = kRQSakkaDenies;
break;
case kRQSakkaDenies:
_vm->_interface->converseClear();
_vm->_interface->converseAddText(optionsStr[_lang][kROAccept], 0, 1, 0, 0);
_vm->_interface->converseAddText(optionsStr[_lang][kRODecline], 0, 2, 0, 0);
_vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0, 0);
_vm->_interface->converseDisplayText();
_vm->getTimerManager()->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this, "sagaPuzzleHint");
_hintRqState = kRQSkipEverything;
break;
default:
break;
}
}
void Puzzle::handleReply(int reply) {
switch (reply) {
case 0: // Quit the puzzle
exitPuzzle();
break;
case 1: // Accept the hint
giveHint();
break;
case 2: // Decline the hint
_vm->_actor->abortSpeech();
_hintRqState = kRQNoHint;
_vm->getTimerManager()->removeTimerProc(&hintTimerCallback);
_vm->getTimerManager()->installTimerProc(&hintTimerCallback, kPuzzleHintTime * 2, this, "sagaPuzzleHint");
clearHint();
break;
default:
break;
}
}
void Puzzle::giveHint() {
int i, total = 0;
_vm->_interface->converseClear();
_vm->_actor->abortSpeech();
_vm->_interface->setRightPortrait(_hintGiver);
for (i = 0; i < PUZZLE_PIECES; i++)
total += _pieceInfo[i].flag & PUZZLE_FIT;
if (_hintCount == 0 && (_pieceInfo[1].flag & PUZZLE_FIT
|| _pieceInfo[12].flag & PUZZLE_FIT))
_hintCount++;
if (_hintCount == 1 && _pieceInfo[14].flag & PUZZLE_FIT)
_hintCount++;
if (_hintCount == 2 && total > 3)
_hintCount++;
_vm->_actor->setSpeechColor(1, _vm->iteColorBlack());
if (_hintCount < 3) {
_vm->_actor->nonActorSpeech(_hintBox, &hintStr[_lang][_hintCount], 1, PUZZLE_HINT_SOUNDS + _hintCount * 3 + _hintSpeaker, 0);
} else {
int piece = 0;
for (i = PUZZLE_PIECES - 1; i >= 0; i--) {
piece = _piecePriority[i];
if (_pieceInfo[piece].flag & PUZZLE_MOVED
&& !(_pieceInfo[piece].flag & PUZZLE_FIT)) {
if (_helpCount < 12)
_helpCount++;
break;
}
}
if (i >= 0) {
static char hintBuf[64];
static const char *hintPtr = hintBuf;
Common::sprintf_s(hintBuf, optionsStr[_lang][kROHint], pieceNames[_lang][piece]);
_vm->_actor->nonActorSpeech(_hintBox, &hintPtr, 1, PUZZLE_TOOL_SOUNDS + _hintSpeaker + piece * 3, 0);
} else {
// If no pieces are in the wrong place
_vm->_actor->nonActorSpeech(_hintBox, &hintStr[_lang][3], 1, PUZZLE_HINT_SOUNDS + 3 * 3 + _hintSpeaker, 0);
}
}
_hintCount++;
_hintRqState = kRQNoHint;
_vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0, 0);
_vm->_interface->converseDisplayText();
_vm->getTimerManager()->removeTimerProc(&hintTimerCallback);
_vm->getTimerManager()->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this, "sagaPuzzleHint");
}
void Puzzle::clearHint() {
_vm->_interface->converseClear();
_vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0, 0);
_vm->_interface->converseDisplayText();
_vm->_interface->setStatusText(" ");
}
} // End of namespace Saga

121
engines/saga/puzzle.h Normal file
View File

@@ -0,0 +1,121 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 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/>.
*
*/
// ITE puzzle scene
#ifndef SAGA_PUZZLE_H
#define SAGA_PUZZLE_H
namespace Saga {
#define PUZZLE_SOUNDS 3622
#define PUZZLE_TOOL_SOUNDS (PUZZLE_SOUNDS + 0)
#define PUZZLE_HINT_SOUNDS (PUZZLE_SOUNDS + 45)
#define PUZZLE_SOLICIT_SOUNDS (PUZZLE_SOUNDS + 57)
#define PUZZLE_WHINE_SOUNDS (PUZZLE_SOUNDS + 72)
#define PUZZLE_SAKKA_SOUNDS (PUZZLE_SOUNDS + 87)
class Puzzle {
private:
enum kRQStates {
kRQNoHint = 0,
kRQHintRequested = 1,
kRQHintRequestedStage2 = 2,
kRQSakkaDenies = 3,
kRQSkipEverything = 4,
kRQSpeaking = 5
};
SagaEngine *_vm;
bool _solved;
bool _active;
bool _newPuzzle;
bool _sliding;
kRQStates _hintRqState;
kRQStates _hintNextRqState;
int _hintGiver;
int _hintSpeaker;
int _hintOffer;
int _hintCount;
int _helpCount;
int _puzzlePiece;
int _piecePriority[PUZZLE_PIECES];
int _lang;
public:
Puzzle(SagaEngine *vm);
void execute();
void exitPuzzle();
bool isSolved() { return _solved; }
bool isActive() { return _active; }
void handleReply(int reply);
void handleClick(Point mousePt);
void movePiece(Point mousePt);
private:
void initPieceInfo(int i, int16 curX, int16 curY, byte offX, byte offY, int16 trgX,
int16 trgY, uint8 flag, uint8 count, Point point0, Point point1,
Point point2, Point point3, Point point4, Point point5);
static void hintTimerCallback(void *refCon);
void solicitHint();
void initPieces();
void showPieces();
void slidePiece(int x1, int y1, int x2, int y2);
void dropPiece(Point mousePt);
void alterPiecePriority();
void drawCurrentPiece();
void giveHint();
void clearHint();
private:
struct PieceInfo {
int16 curX;
int16 curY;
byte offX;
byte offY;
int16 trgX;
int16 trgY;
uint8 flag;
uint8 count;
Point point[6];
};
PieceInfo _pieceInfo[PUZZLE_PIECES];
int _slidePointX, _slidePointY;
Rect _hintBox;
};
} // End of namespace Saga
#endif

352
engines/saga/render.cpp Normal file
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/>.
*
*/
// Main rendering loop
#include "saga/saga.h"
#include "saga/actor.h"
#include "saga/font.h"
#include "saga/gfx.h"
#include "saga/interface.h"
#include "saga/objectmap.h"
#include "saga/puzzle.h"
#include "saga/render.h"
#include "saga/scene.h"
#include "common/timer.h"
#include "common/system.h"
namespace Saga {
const char *test_txt = "The quick brown fox jumped over the lazy dog. She sells sea shells down by the sea shore.";
const char *pauseStringITE = "PAWS GAME";
const char *pauseStringIHNM = "Game Paused";
Render::Render(SagaEngine *vm, OSystem *system) {
_vm = vm;
_system = system;
_initialized = false;
_fullRefresh = true;
_splitScreen = false;
_dualSurface = (vm->getLanguage() == Common::JA_JPN);
#ifdef SAGA_DEBUG
// Initialize FPS timer callback
_vm->getTimerManager()->installTimerProc(&fpsTimerCallback, 1000000, this, "sagaFPS");
#endif
_backGroundSurface.create(_vm->getDisplayInfo().width, _vm->getDisplayInfo().height, Graphics::PixelFormat::createFormatCLUT8());
if (_dualSurface)
_mergeSurface.create(_vm->getDisplayInfo().width << 1, _vm->getDisplayInfo().height << 1, Graphics::PixelFormat::createFormatCLUT8());
_flags = 0;
_initialized = true;
}
Render::~Render() {
#ifdef SAGA_DEBUG
_vm->getTimerManager()->removeTimerProc(&fpsTimerCallback);
#endif
_backGroundSurface.free();
_mergeSurface.free();
_initialized = false;
}
bool Render::initialized() {
return _initialized;
}
void Render::drawScene() {
Point mousePoint;
Point textPoint;
int curMode = _vm->_interface->getMode();
assert(_initialized);
#ifdef SAGA_DEBUG
_renderedFrameCount++;
#endif
// Get mouse coordinates
mousePoint = _vm->mousePos();
if (!_fullRefresh)
restoreChangedRects();
else
_dirtyRects.clear();
if (!(_flags & (RF_DEMO_SUBST | RF_MAP) || curMode == kPanelPlacard)) {
if (_vm->_interface->getFadeMode() != kFadeOut) {
// Display scene background
if (!(_flags & RF_DISABLE_ACTORS) || _vm->getGameId() == GID_ITE)
_vm->_scene->draw();
if (_vm->_scene->isITEPuzzleScene()) {
_vm->_puzzle->movePiece(mousePoint);
_vm->_actor->drawSpeech();
} else {
// Draw queued actors
if (!(_flags & RF_DISABLE_ACTORS))
_vm->_actor->drawActors();
}
// WORKAROUND
// Bug #4684: "ITE: Graphic Glitches during Cat Tribe Celebration"
if (_vm->_scene->currentSceneNumber() == 274) {
_vm->_interface->drawStatusBar();
}
#ifdef SAGA_DEBUG
if (getFlags() & RF_OBJECTMAP_TEST) {
if (_vm->_scene->_objectMap)
_vm->_scene->_objectMap->draw(mousePoint, kITEColorBrightWhite, kITEColorBlack);
if (_vm->_scene->_actionMap)
_vm->_scene->_actionMap->draw(mousePoint, kITEColorRed, kITEColorBlack);
}
#endif
#ifdef ACTOR_DEBUG
if (getFlags() & RF_ACTOR_PATH_TEST) {
_vm->_actor->drawPathTest();
}
#endif
}
} else {
_fullRefresh = true;
}
if (_flags & RF_MAP)
_vm->_interface->mapPanelDrawCrossHair();
if ((curMode == kPanelOption) ||
(curMode == kPanelQuit) ||
(curMode == kPanelLoad) ||
(curMode == kPanelSave)) {
_vm->_interface->drawOption();
if (curMode == kPanelQuit) {
_vm->_interface->drawQuit();
}
if (curMode == kPanelLoad) {
_vm->_interface->drawLoad();
}
if (curMode == kPanelSave) {
_vm->_interface->drawSave();
}
}
if (curMode == kPanelProtect) {
_vm->_interface->drawProtect();
}
// Draw queued text strings
_vm->_scene->drawTextList();
// Handle user input
_vm->processInput();
#ifdef SAGA_DEBUG
// Display rendering information
if (_flags & RF_SHOW_FPS) {
char txtBuffer[20];
Common::sprintf_s(txtBuffer, "%d", _fps);
textPoint.x = _vm->_gfx->getBackBufferWidth() - _vm->_font->getStringWidth(kKnownFontSmall, txtBuffer, 0, kFontOutline);
textPoint.y = 2;
_vm->_font->textDraw(kKnownFontSmall, txtBuffer, textPoint, kITEColorBrightWhite, kITEColorBlack, kFontOutline);
}
#endif
// Display "paused game" message, if applicable
if (_flags & RF_RENDERPAUSE) {
const char *pauseString = (_vm->getGameId() == GID_ITE) ? pauseStringITE : pauseStringIHNM;
textPoint.x = (_vm->_gfx->getBackBufferWidth() - _vm->_font->getStringWidth(kKnownFontPause, pauseString, 0, kFontOutline)) / 2;
textPoint.y = 90;
_vm->_font->textDraw(kKnownFontPause, pauseString, textPoint,
_vm->KnownColor2ColorId(kKnownColorBrightWhite), _vm->KnownColor2ColorId(kKnownColorBlack), kFontOutline);
}
// Update user interface
_vm->_interface->update(mousePoint, UPDATE_MOUSEMOVE);
#ifdef SAGA_DEBUG
// Display text formatting test, if applicable
if (_flags & RF_TEXT_TEST) {
Rect rect(mousePoint.x, mousePoint.y, mousePoint.x + 100, mousePoint.y + 50);
_vm->_font->textDrawRect(kKnownFontMedium, test_txt, rect,
kITEColorBrightWhite, kITEColorBlack, (FontEffectFlags)(kFontOutline | kFontCentered));
}
// Display palette test, if applicable
if (_flags & RF_PALETTE_TEST) {
_vm->_gfx->drawPalette();
}
#endif
drawDirtyRects();
_system->updateScreen();
// TODO: Change this to false to use dirty rectangles
// Still quite buggy
_fullRefresh = true;
}
void Render::addDirtyRect(Common::Rect r) {
if (_fullRefresh)
return;
// Clip rectangle
r.clip(_backGroundSurface.w, _backGroundSurface.h);
// If it is empty after clipping, we are done
if (r.isEmpty())
return;
// Check if the new rectangle is contained within another in the list
Common::List<Common::Rect>::iterator it;
for (it = _dirtyRects.begin(); it != _dirtyRects.end(); ) {
// If we find a rectangle which fully contains the new one,
// we can abort the search.
if (it->contains(r))
return;
// Conversely, if we find rectangles which are contained in
// the new one, we can remove them
if (r.contains(*it))
it = _dirtyRects.erase(it);
else
++it;
}
// If we got here, we can safely add r to the list of dirty rects.
if (_vm->_interface->getFadeMode() != kFadeOut)
_dirtyRects.push_back(r);
}
#define mCopyRectToScreen(x, y, w, h) \
if (_dualSurface) { \
scale2xAndMergeOverlay(x, y, w, h); \
_system->copyRectToScreen(_mergeSurface.getPixels(), _mergeSurface.pitch, x << 1, y << 1, w << 1, h << 1); \
} else \
_system->copyRectToScreen(_vm->_gfx->getBackBufferPixels(), _vm->_gfx->getBackBufferWidth(), x, y, w, h)
void Render::maskSplitScreen() {
if (!_vm->isECS())
return;
uint8 *start = _vm->_gfx->getBackBufferPixels() + 137 * _vm->_gfx->getBackBufferWidth();
uint8 *end = _vm->_gfx->getBackBufferPixels() + _vm->_gfx->getBackBufferHeight() * _vm->_gfx->getBackBufferWidth();
if (_splitScreen) {
for (uint8 *ptr = start; ptr < end; ptr++)
if (!(*ptr & 0xc0))
*ptr |= 0x20;
} else {
for (uint8 *ptr = start; ptr < end; ptr++)
if (!(*ptr & 0xc0))
*ptr &= ~0x20;
}
}
void Render::restoreChangedRects() {
maskSplitScreen();
if (!_fullRefresh) {
for (const auto &dirty : _dirtyRects) {
//_backGroundSurface.frameRect(*it, 1); // DEBUG
if (_vm->_interface->getFadeMode() != kFadeOut) {
mCopyRectToScreen(dirty.left, dirty.top, dirty.width(), dirty.height());
}
}
}
_dirtyRects.clear();
}
void Render::drawDirtyRects() {
maskSplitScreen();
if (!_fullRefresh) {
for (const auto &dirty : _dirtyRects) {
//_backGroundSurface.frameRect(*it, 2); // DEBUG
if (_vm->_interface->getFadeMode() != kFadeOut) {
mCopyRectToScreen(dirty.left, dirty.top, dirty.width(), dirty.height());
}
}
} else {
mCopyRectToScreen(0, 0, _backGroundSurface.w, _backGroundSurface.h);
}
_dirtyRects.clear();
}
#undef mCopyRectToScreen
void Render::scale2xAndMergeOverlay(int x, int y, int w, int h) {
int src0Pitch = _vm->_gfx->getBackBufferPitch();
int src1Pitch = _vm->_gfx->getSJISBackBufferPitch();
int dst1Pitch = _mergeSurface.pitch;
const byte *src00 = _vm->_gfx->getBackBufferPixels() + y * src0Pitch + x;
const byte *src10 = _vm->_gfx->getSJISBackBufferPixels() + y * 2 * src1Pitch + x * 2;
const byte *src11 = src10 + src1Pitch;
byte *dst10 = (byte*)_mergeSurface.getBasePtr(x << 1, y << 1);
byte *dst11 = dst10 + dst1Pitch;
src0Pitch -= w;
src1Pitch += (src1Pitch - (w << 1));
dst1Pitch += (dst1Pitch - (w << 1));
while (h--) {
for (int i = 0; i < w; ++i) {
// v0: pixels from "normal" surface that have to be scaled
// v1: pixels from hires text surface that go on top
uint8 v0 = *src00++;
uint8 v1 = *src10++;
*dst10++ = v1 ? v1 : v0;
v1 = *src10++;
*dst10++ = v1 ? v1 : v0;
v1 = *src11++;
*dst11++ = v1 ? v1 : v0;
v1 = *src11++;
*dst11++ = v1 ? v1 : v0;
}
src00 += src0Pitch;
src10 += src1Pitch;
src11 += src1Pitch;
dst10 += dst1Pitch;
dst11 += dst1Pitch;
}
}
#ifdef SAGA_DEBUG
void Render::fpsTimerCallback(void *refCon) {
((Render *)refCon)->fpsTimer();
}
void Render::fpsTimer() {
// CHECKME: This is potentially called from a different thread because it is
// called from a timer callback. However, it does not seem to take any
// precautions to avoid race conditions.
_fps = _renderedFrameCount;
_renderedFrameCount = 0;
}
#endif
} // End of namespace Saga

131
engines/saga/render.h Normal file
View File

@@ -0,0 +1,131 @@
/* 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/>.
*
*/
// Main rendering loop - private header
#ifndef SAGA_RENDER_H
#define SAGA_RENDER_H
#include "saga/sprite.h"
#include "saga/gfx.h"
#include "common/list.h"
namespace Saga {
enum RENDER_FLAGS {
RF_RENDERPAUSE = (1 << 0),
RF_MAP = (1 << 1),
RF_DISABLE_ACTORS = (1 << 2),
RF_DEMO_SUBST = (1 << 3)
};
// Extra render flags used for debugging
#ifdef SAGA_DEBUG
enum RENDER_DEBUG_FLAGS {
RF_SHOW_FPS = (1 << 4),
RF_PALETTE_TEST = (1 << 5),
RF_TEXT_TEST = (1 << 6),
RF_OBJECTMAP_TEST = (1 << 7),
RF_ACTOR_PATH_TEST = (1 << 8)
};
#endif
class Render {
public:
Render(SagaEngine *vm, OSystem *system);
~Render();
bool initialized();
void drawScene();
unsigned int getFlags() const {
return _flags;
}
void setFlag(unsigned int flag) {
_flags |= flag;
}
void clearFlag(unsigned int flag) {
_flags &= ~flag;
}
void toggleFlag(unsigned int flag) {
_flags ^= flag;
}
Surface *getBackGroundSurface() {
return &_backGroundSurface;
}
void addDirtyRect(Common::Rect rect);
void clearDirtyRects() {
_dirtyRects.clear();
}
void setFullRefresh(bool flag) {
_fullRefresh = flag;
}
bool isFullRefresh() {
return _fullRefresh;
}
/**
* ECS has only 32-color palette. ITE switches palette
* after line 137 on most screens but not on intro. Set
* whether to change palette after line 137.
*/
void setSplitScreen(bool flag) {
_splitScreen = flag;
}
void maskSplitScreen();
void drawDirtyRects();
void scale2xAndMergeOverlay(int x, int y, int w, int h);
void restoreChangedRects();
private:
#ifdef SAGA_DEBUG
static void fpsTimerCallback(void *refCon);
void fpsTimer();
unsigned int _fps;
unsigned int _renderedFrameCount;
#endif
SagaEngine *_vm;
OSystem *_system;
bool _initialized;
Common::List<Common::Rect> _dirtyRects;
bool _fullRefresh;
bool _dualSurface;
bool _splitScreen;
// Module data
Surface _backGroundSurface;
Surface _mergeSurface;
uint32 _flags;
};
} // End of namespace Saga
#endif

560
engines/saga/resource.cpp Normal file
View File

@@ -0,0 +1,560 @@
/* 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/>.
*
*/
// RSC Resource file management module
#include "saga/saga.h"
#include "saga/actor.h"
#include "saga/animation.h"
#include "saga/interface.h"
#include "saga/music.h"
#include "saga/resource.h"
#include "saga/scene.h"
#include "saga/sndres.h"
#include "engines/advancedDetector.h"
#include "common/compression/powerpacker.h"
namespace Saga {
// Patch files. Files not found will be ignored
static const GamePatchDescription ITEPatch_Files[] = {
{ "cave.mid", GAME_RESOURCEFILE, 9},
{ "intro.mid", GAME_RESOURCEFILE, 10},
{ "fvillage.mid", GAME_RESOURCEFILE, 11},
{ "elkhall.mid", GAME_RESOURCEFILE, 12},
{ "mouse.mid", GAME_RESOURCEFILE, 13},
{ "darkclaw.mid", GAME_RESOURCEFILE, 14},
{ "birdchrp.mid", GAME_RESOURCEFILE, 15},
{ "orbtempl.mid", GAME_RESOURCEFILE, 16},
{ "spooky.mid", GAME_RESOURCEFILE, 17},
{ "catfest.mid", GAME_RESOURCEFILE, 18},
{ "elkfanfare.mid", GAME_RESOURCEFILE, 19},
{ "bcexpl.mid", GAME_RESOURCEFILE, 20},
{ "boargtnt.mid", GAME_RESOURCEFILE, 21},
{ "boarking.mid", GAME_RESOURCEFILE, 22},
{ "explorea.mid", GAME_RESOURCEFILE, 23},
{ "exploreb.mid", GAME_RESOURCEFILE, 24},
{ "explorec.mid", GAME_RESOURCEFILE, 25},
{ "sunstatm.mid", GAME_RESOURCEFILE, 26},
{ "nitstrlm.mid", GAME_RESOURCEFILE, 27},
{ "humruinm.mid", GAME_RESOURCEFILE, 28},
{ "damexplm.mid", GAME_RESOURCEFILE, 29},
{ "tychom.mid", GAME_RESOURCEFILE, 30},
{ "kitten.mid", GAME_RESOURCEFILE, 31},
{ "sweet.mid", GAME_RESOURCEFILE, 32},
{ "brutalmt.mid", GAME_RESOURCEFILE, 33},
{ "shiala.mid", GAME_RESOURCEFILE, 34},
{ "wyrm.pak", GAME_RESOURCEFILE, 1529},
{ "wyrm1.dlt", GAME_RESOURCEFILE, 1530},
{ "wyrm2.dlt", GAME_RESOURCEFILE, 1531},
{ "wyrm3.dlt", GAME_RESOURCEFILE, 1532},
{ "wyrm4.dlt", GAME_RESOURCEFILE, 1533},
{ "credit3n.dlt", GAME_RESOURCEFILE, 1796}, // PC
{ "credit3m.dlt", GAME_RESOURCEFILE, 1796}, // Macintosh
{ "credit4n.dlt", GAME_RESOURCEFILE, 1797}, // PC
{ "credit4m.dlt", GAME_RESOURCEFILE, 1797}, // Macintosh
{ "p2_a.voc", GAME_VOICEFILE, 4},
{ "p2_a.iaf", GAME_VOICEFILE, 4},
{ NULL, 0, 0}
};
static const GamePatchDescription ITEMacPatch_Files[] = {
{ "wyrm.pak", GAME_RESOURCEFILE, 1529},
{ "wyrm1.dlt", GAME_RESOURCEFILE, 1530},
{ "wyrm2.dlt", GAME_RESOURCEFILE, 1531},
{ "wyrm3.dlt", GAME_RESOURCEFILE, 1532},
{ "wyrm4.dlt", GAME_RESOURCEFILE, 1533},
{ "credit3m.dlt", GAME_RESOURCEFILE, 1796},
{ "credit4m.dlt", GAME_RESOURCEFILE, 1797},
{ "p2_a.iaf", GAME_VOICEFILE, 4},
{ NULL, 0, 0}
};
static const GamePatchDescription *PatchLists[PATCHLIST_MAX] = {
/* PATCHLIST_NONE */ nullptr,
/* PATCHLIST_ITE */ ITEPatch_Files,
/* PATCHLIST_ITE_MAC */ ITEMacPatch_Files
};
struct ITEAmigaIndex {
uint32 fileOffset;
uint32 numEntries;
};
struct ITEAmigaEXEDescriptor {
ITEAmigaIndex voiceIndex;
ITEAmigaIndex soundIndex;
};
bool ResourceContext::loadResIteAmigaSound(SagaEngine *_vm, int type) {
Common::String exeName;
for (const ADGameFileDescription *gameFileDescription = _vm->getFilesDescriptions();
gameFileDescription->fileName; gameFileDescription++) {
if (Common::String(gameFileDescription->fileName).hasSuffix(".exe"))
exeName = gameFileDescription->fileName;
}
if (exeName.empty())
return false;
// Right now German and English ECS version have same offsets to
// offset tables, no need to distinguish them
static const ITEAmigaEXEDescriptor ecsDesc = {
{ 0x56a8, 3730 },
{ 0x90f0, 44 },
};
// Right now German and English ECS version have same offsets to
// offset tables, no need to distinguish them
static const ITEAmigaEXEDescriptor agaDesc = {
{ 0x53a8, 3730 },
{ 0x8df0, 44 },
};
const ITEAmigaEXEDescriptor *exedesc = _vm->isECS() ? &ecsDesc : &agaDesc;
const ITEAmigaIndex& amigaIdx = _fileType & GAME_VOICEFILE ? exedesc->voiceIndex : exedesc->soundIndex;
if (amigaIdx.numEntries <= 1)
return false;
_table.resize(amigaIdx.numEntries - 1);
Common::File f;
if(!f.open(exeName.c_str()))
return false;
f.seek(amigaIdx.fileOffset);
for (uint32 i = 0; i < amigaIdx.numEntries - 1; i++) {
ResourceData *resourceData = &_table[i];
resourceData->offset = f.readUint32BE();
resourceData->diskNum = -1;
}
uint32 lastEntry = f.readUint32BE();
for (uint32 i = 0; i < amigaIdx.numEntries - 2; i++) {
_table[i].size = _table[i + 1].offset - _table[i].offset;
}
_table[amigaIdx.numEntries - 2].size = lastEntry - _table[amigaIdx.numEntries - 2].offset;
return true;
}
bool ResourceContext::loadResIteAmiga(SagaEngine *_vm, int type, bool isFloppy) {
if (_fileType & (GAME_VOICEFILE | GAME_SOUNDFILE))
return loadResIteAmigaSound(_vm, type);
_file->seek(0);
uint16 resourceCount = _file->readUint16BE();
uint16 scriptCount = _file->readUint16BE();
uint32 count = (type & GAME_SCRIPTFILE) ? scriptCount : resourceCount;
uint32 extraOffset = isFloppy ? 1024 : 0;
if (type & GAME_SCRIPTFILE)
_file->seek(resourceCount * 10, SEEK_CUR);
_table.resize(count);
for (uint32 i = 0; i < count; i++) {
ResourceData *resourceData = &_table[i];
resourceData->offset = _file->readUint32BE() + extraOffset;
resourceData->size = _file->readUint32BE();
resourceData->diskNum = _file->readUint16BE();
}
return true;
}
bool ResourceContext::loadResV1() {
size_t i;
bool result;
byte tableInfo[RSC_TABLEINFO_SIZE];
ByteArray tableBuffer;
uint32 count;
uint32 resourceTableOffset;
ResourceData *resourceData;
if (_fileSize < RSC_MIN_FILESIZE) {
warning("ResourceContext::loadResV1(): Incorrect contextSize: %d < %d", (int) _fileSize, RSC_MIN_FILESIZE);
return false;
}
_file->seek(-RSC_TABLEINFO_SIZE, SEEK_END);
if (_file->read(tableInfo, RSC_TABLEINFO_SIZE) != RSC_TABLEINFO_SIZE) {
warning("ResourceContext::loadResV1(): Incorrect table size: %d for %s", RSC_TABLEINFO_SIZE, _fileName);
return false;
}
Common::MemoryReadStreamEndian readS(tableInfo, RSC_TABLEINFO_SIZE, _isBigEndian);
resourceTableOffset = readS.readUint32();
count = readS.readUint32();
// Check for sane table offset
if (resourceTableOffset != _fileSize - RSC_TABLEINFO_SIZE - RSC_TABLEENTRY_SIZE * count) {
warning("ResourceContext::loadResV1(): Incorrect tables offset: %d != %d for %s, endian is %d",
resourceTableOffset, (int)_fileSize - RSC_TABLEINFO_SIZE - RSC_TABLEENTRY_SIZE * count,
_fileName, _isBigEndian);
return false;
}
// Load resource table
tableBuffer.resize(RSC_TABLEENTRY_SIZE * count);
_file->seek(resourceTableOffset, SEEK_SET);
result = (_file->read(tableBuffer.getBuffer(), tableBuffer.size()) == tableBuffer.size());
if (result) {
_table.resize(count);
Common::MemoryReadStreamEndian readS1(tableBuffer.getBuffer(), tableBuffer.size(), _isBigEndian);
for (i = 0; i < count; i++) {
resourceData = &_table[i];
resourceData->offset = readS1.readUint32();
resourceData->size = readS1.readUint32();
// Sanity check
if ((resourceData->offset > (uint)_fileSize) || (resourceData->size > (uint)_fileSize)) {
result = false;
break;
}
}
}
return result;
}
bool ResourceContext::load(SagaEngine *vm, Resource *resource) {
if (_fileName == nullptr) // IHNM special case
return true;
_file.reset(Common::MacResManager::openFileOrDataFork(_fileName));
if (!_file)
return false;
_fileSize = _file->size();
_isBigEndian = vm->isBigEndian();
if (_fileType & GAME_SWAPENDIAN)
_isBigEndian = !_isBigEndian;
if ((_fileType & (GAME_MACBINARY | GAME_MUSICFILE_GM)) == (GAME_MACBINARY | GAME_MUSICFILE_GM)) {
_macRes.reset(new Common::MacResManager());
if (!_macRes->open(_fileName))
return false;
// Unpacking MacBinary packed MIDI files happens on-demand
return true;
}
if (!loadRes(vm, _fileType))
return false;
GamePatchList index = vm->getPatchList();
if (index < PATCHLIST_MAX && index > PATCHLIST_NONE)
processPatches(resource, PatchLists[index]);
// Close the file if it's part of a series of files.
// This prevents having all voice files open in IHNM for no reason, as each chapter uses
// a different voice file.
if (_serial > 0)
closeFile();
return true;
}
Resource::Resource(SagaEngine *vm): _vm(vm) {
}
Resource::~Resource() {
clearContexts();
}
void Resource::addContext(const char *fileName, uint16 fileType, bool isCompressed, int serial) {
ResourceContext *context;
context = createContext();
context->_fileName = fileName;
context->_fileType = fileType;
context->_isCompressed = isCompressed;
context->_serial = serial;
_contexts.push_back(context);
}
bool Resource::createContexts() {
bool soundFileInArray = false;
bool voiceFileInArray = false;
_vm->_voiceFilesExist = true;
struct SoundFileInfo {
int gameId;
char fileName[40];
bool isCompressed;
uint16 voiceFileAddType;
};
for (const ADGameFileDescription *gameFileDescription = _vm->getFilesDescriptions();
gameFileDescription->fileName; gameFileDescription++) {
if (gameFileDescription->fileType > 0)
addContext(gameFileDescription->fileName, gameFileDescription->fileType);
if ((gameFileDescription->fileType & GAME_RESOURCEFILE) && _vm->getPlatform() == Common::kPlatformAmiga && _vm->getGameId() == GID_ITE)
addContext(gameFileDescription->fileName, (gameFileDescription->fileType & ~GAME_RESOURCEFILE) | GAME_SCRIPTFILE | GAME_SWAPENDIAN);
if ((gameFileDescription->fileType & GAME_RESOURCEFILE) && _vm->getPlatform() == Common::kPlatformAmiga && _vm->getGameId() == GID_ITE)
addContext(gameFileDescription->fileName, (gameFileDescription->fileType & ~GAME_RESOURCEFILE) | GAME_MUSICFILE_FM);
if (gameFileDescription->fileType == GAME_SOUNDFILE) {
soundFileInArray = true;
}
if (gameFileDescription->fileType == GAME_VOICEFILE) {
voiceFileInArray = true;
}
}
//// Detect and add SFX files ////////////////////////////////////////////////
SoundFileInfo sfxFiles[] = {
{ GID_ITE, "sounds.rsc", false, 0 },
{ GID_ITE, "sounds.cmp", true, 0 },
{ GID_ITE, "soundsd.rsc", false, 0 },
{ GID_ITE, "soundsd.cmp", true, 0 },
#ifdef ENABLE_IHNM
{ GID_IHNM, "sfx.res", false, 0 },
{ GID_IHNM, "sfx.cmp", true, 0 },
#endif
{ -1, "", false, 0 }
};
_soundFileName[0] = 0;
if (!soundFileInArray) {
for (SoundFileInfo *curSoundFile = sfxFiles; (curSoundFile->gameId != -1); curSoundFile++) {
if (curSoundFile->gameId != _vm->getGameId()) continue;
if (!Common::File::exists(curSoundFile->fileName)) continue;
Common::strcpy_s(_soundFileName, curSoundFile->fileName);
uint32 flags = GAME_SOUNDFILE;
if (_vm->getFeatures() & GF_SOME_MAC_RESOURCES)
flags |= GAME_SWAPENDIAN;
addContext(_soundFileName, flags, curSoundFile->isCompressed);
break;
}
}
//// Detect and add voice files /////////////////////////////////////////////
SoundFileInfo voiceFiles[] = {
{ GID_ITE, "voices.rsc", false , (uint16)((_soundFileName[0] == 0) ? GAME_SOUNDFILE : 0)},
{ GID_ITE, "voices.cmp", true , (uint16)((_soundFileName[0] == 0) ? GAME_SOUNDFILE : 0)},
{ GID_ITE, "voicesd.rsc", false , (uint16)((_soundFileName[0] == 0) ? GAME_SOUNDFILE : 0)},
{ GID_ITE, "voicesd.cmp", true , (uint16)((_soundFileName[0] == 0) ? GAME_SOUNDFILE : 0)},
// The resources in the Wyrmkeep combined Windows/Mac/Linux CD version are little endian, but
// the voice file is big endian. If we got such a version with mixed files, mark this voice file
// as big endian
{ GID_ITE, "inherit the earth voices", false , (uint16)(_vm->isBigEndian() ? 0 : GAME_SWAPENDIAN)},
{ GID_ITE, "inherit the earth voices.cmp", true , (uint16)(_vm->isBigEndian() ? 0 : GAME_SWAPENDIAN)},
{ GID_ITE, "ite voices", false , GAME_MACBINARY},
#ifdef ENABLE_IHNM
{ GID_IHNM, "voicess.res", false , 0},
{ GID_IHNM, "voicess.cmp", true , 0},
{ GID_IHNM, "voicesd.res", false , 0},
{ GID_IHNM, "voicesd.cmp", true , 0},
#endif
{ -1, "", false , 0}
};
// Detect and add voice files
_voicesFileName[0][0] = 0;
if (!voiceFileInArray) {
for (SoundFileInfo *curSoundFile = voiceFiles; (curSoundFile->gameId != -1); curSoundFile++) {
if (curSoundFile->gameId != _vm->getGameId()) continue;
bool exists = curSoundFile->voiceFileAddType & GAME_MACBINARY ? Common::MacResManager::exists(curSoundFile->fileName) : Common::File::exists(curSoundFile->fileName);
if (!exists) continue;
Common::strcpy_s(_voicesFileName[0], curSoundFile->fileName);
addContext(_voicesFileName[0], GAME_VOICEFILE | curSoundFile->voiceFileAddType, curSoundFile->isCompressed);
// Special cases
if (!scumm_stricmp(curSoundFile->fileName, "voicess.res") ||
!scumm_stricmp(curSoundFile->fileName, "voicess.cmp")) {
// IHNM has multiple voice files
for (size_t i = 1; i <= 6; i++) { // voices1-voices6
Common::sprintf_s(_voicesFileName[i], "voices%i.%s", (uint)i, curSoundFile->isCompressed ? "cmp" : "res");
if (i == 4) {
// The German and French versions of IHNM don't have Nimdok's chapter,
// therefore the voices file for that chapter is missing
if (!Common::File::exists(_voicesFileName[i])) {
continue;
}
}
addContext(_voicesFileName[i], GAME_VOICEFILE, curSoundFile->isCompressed, i);
}
}
break;
}
}
if (!voiceFileInArray && _voicesFileName[0][0] == 0) {
#ifdef ENABLE_IHNM
if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) {
// The Macintosh version of IHNM has no voices.res, and it has all
// its voice files in subdirectories, so don't do anything here
_contexts.push_back(new VoiceResourceContext_RES());
} else {
#endif
warning("No voice file found, voices will be disabled");
_vm->_voicesEnabled = false;
_vm->_subtitlesEnabled = true;
_vm->_voiceFilesExist = false;
#ifdef ENABLE_IHNM
}
#endif
}
//// Detect and add music files /////////////////////////////////////////
SoundFileInfo musicFiles[] = {
{ GID_ITE, "music.rsc", false, 0 },
{ GID_ITE, "music.cmp", true, 0 },
{ GID_ITE, "musicd.rsc", false, 0 },
{ GID_ITE, "musicd.cmp", true, 0 },
{ -1, "", false , 0}
};
// Check for digital music in ITE
for (SoundFileInfo *curSoundFile = musicFiles; (curSoundFile->gameId != -1); curSoundFile++) {
if (curSoundFile->gameId != _vm->getGameId()) continue;
if (!Common::File::exists(curSoundFile->fileName)) continue;
Common::strcpy_s(_musicFileName, curSoundFile->fileName);
uint32 flags = GAME_DIGITALMUSICFILE;
if (_vm->getFeatures() & GF_SOME_MAC_RESOURCES)
flags |= GAME_SWAPENDIAN;
addContext(_musicFileName, flags, curSoundFile->isCompressed);
break;
}
for (auto &context : _contexts) {
if (!context->load(_vm, this)) {
warning("Cannot load context %s", context->_fileName);
return false;
}
}
return true;
}
void Resource::clearContexts() {
ResourceContextList::iterator i = _contexts.begin();
while (i != _contexts.end()) {
ResourceContext * context = *i;
i = _contexts.erase(i);
delete context;
}
}
#define ID_MIDI MKTAG('M','i','d','i')
void Resource::loadResource(ResourceContext *context, uint32 resourceId, ByteArray &resourceBuffer) {
if ((context->_fileType & (GAME_MACBINARY | GAME_MUSICFILE_GM)) == (GAME_MACBINARY | GAME_MUSICFILE_GM) && context->_macRes) {
Common::SeekableReadStream *s = context->_macRes->getResource(ID_MIDI, resourceId);
if (!s)
return;
resourceBuffer.resize(s->size());
s->read(resourceBuffer.getBuffer(), s->size());
delete s;
return;
}
ResourceData *resourceData = context->getResourceData(resourceId);
Common::SeekableReadStream *file = nullptr;
uint32 resourceOffset = resourceData->offset;
Common::File actualFile;
if (resourceData->diskNum == -1)
file = context->getFile(resourceData);
else {
Common::String fileName = context->_fileName;
int sz = fileName.size();
while(sz > 0 && fileName[sz - 1] != '.')
sz--;
if (sz > 0)
sz--;
Common::Path filePath;
if (_vm->getFeatures() & GF_ITE_FLOPPY)
filePath = Common::Path(Common::String::format("%s%02d.adf", fileName.substr(0, sz).c_str(), resourceData->diskNum + 1));
else
filePath = Common::Path(Common::String::format("%s.%03d", fileName.substr(0, sz).c_str(), resourceData->diskNum));
if (!actualFile.open(filePath))
error("Resource::loadResource() failed to open %s", filePath.toString().c_str());
file = &actualFile;
}
debug(8, "loadResource %d 0x%X:0x%X", resourceId, resourceOffset, uint(resourceData->size));
resourceBuffer.resize(resourceData->size);
file->seek((long)resourceOffset, SEEK_SET);
if (file->read(resourceBuffer.getBuffer(), resourceBuffer.size()) != resourceBuffer.size()) {
error("Resource::loadResource() failed to read");
}
if (_vm->getPlatform() == Common::Platform::kPlatformAmiga &&
resourceBuffer.size() >= 16 && READ_BE_UINT32(resourceBuffer.getBuffer()) == MKTAG('H', 'E', 'A', 'D')
&& READ_BE_UINT32(resourceBuffer.getBuffer() + 12) == MKTAG('P', 'A', 'C', 'K')) {
uint32 unpackedLen = READ_BE_UINT32(resourceBuffer.getBuffer() + 4);
uint32 packedLen = READ_BE_UINT32(resourceBuffer.getBuffer() + 8);
uint32 actualUncompressedLen = 0;
if (packedLen != resourceBuffer.size() - 20) {
warning("Compressed size mismatch in resource %d: %d vs %d", resourceId, packedLen, resourceBuffer.size() - 20);
}
byte *uncompressed = Common::PowerPackerStream::unpackBuffer(resourceBuffer.getBuffer() + 12, packedLen + 8, actualUncompressedLen);
if (uncompressed == nullptr || unpackedLen != actualUncompressedLen) {
warning("Uncompressed size mismatch in resource %d: %d vs %d", resourceId, unpackedLen, actualUncompressedLen);
}
if (context->fileType() & GAME_MUSICFILE_FM) {
byte b = 0;
for (uint32 i = 0; i < unpackedLen; i++) {
b += uncompressed[i];
uncompressed[i] = b;
}
}
// TODO: Use move semantics
resourceBuffer = ByteArray(uncompressed, actualUncompressedLen);
delete[] uncompressed;
}
}
ResourceContext *Resource::getContext(uint16 fileType, int serial) {
for (const auto &context : _contexts) {
if ((context->fileType() & fileType) && (context->serial() == serial)) {
return context;
}
}
return nullptr;
}
} // End of namespace Saga

305
engines/saga/resource.h Normal file
View File

@@ -0,0 +1,305 @@
/* 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/>.
*
*/
// RSC Resource file management header file
#ifndef SAGA_RESOURCE_H
#define SAGA_RESOURCE_H
#include "common/array.h"
#include "common/file.h"
#include "common/list.h"
#include "common/macresman.h"
namespace Saga {
#define RSC_TABLEINFO_SIZE 8
#define RSC_TABLEENTRY_SIZE 8
#define RSC_MIN_FILESIZE (RSC_TABLEINFO_SIZE + RSC_TABLEENTRY_SIZE + 1)
class SagaEngine;
class ByteArray;
class PatchData {
private:
Common::SeekableReadStream *_patchFile;
const char *_fileName;
bool _deletePatchFile;
bool _patchFileOpened;
public:
PatchData(const char *fileName): _fileName(fileName), _deletePatchFile(true), _patchFile(nullptr), _patchFileOpened(false) {
}
PatchData(Common::SeekableReadStream *patchFile, const char *fileName): _patchFile(patchFile), _fileName(fileName), _deletePatchFile(false), _patchFileOpened(true) {
}
Common::SeekableReadStream *getStream() {
if (_patchFileOpened)
return _patchFile;
Common::File *file = new Common::File();
_patchFileOpened = true;
if (!file->open(_fileName)) {
_patchFile = nullptr;
delete file;
return nullptr;
}
_patchFile = file;
return _patchFile;
}
void closeStream() {
if (_deletePatchFile && _patchFileOpened) {
delete _patchFile;
_patchFile = nullptr;
_patchFileOpened = false;
}
}
~PatchData() {
if (_deletePatchFile) {
delete _patchFile;
}
}
};
struct ResourceData {
size_t offset;
size_t size;
int diskNum; // -1 = without disk id. -2 = mac res fork
PatchData *patchData;
ResourceData() :
offset(0), size(0), patchData(NULL), diskNum(-1) {
}
~ResourceData() {
if (patchData) {
delete patchData;
patchData = NULL;
}
}
};
typedef Common::Array<ResourceData> ResourceDataArray;
class ResourceContext {
friend class Resource;
public:
ResourceContext():
_fileName(NULL), _fileType(0), _isCompressed(false), _serial(0),
_isBigEndian(false),
_fileSize(0), _tombstone(false) {
}
virtual ~ResourceContext() { }
bool isCompressed() const { return _isCompressed; }
uint16 fileType() const { return _fileType; }
int32 fileSize() const { return _fileSize; }
int serial() const { return _serial; }
bool isBigEndian() const { return _isBigEndian; }
const char * fileName() const { return _fileName; }
Common::SeekableReadStream *getFile(ResourceData *resourceData) {
if (resourceData && resourceData->patchData != NULL) {
return resourceData->patchData->getStream();
} else {
if (!_file && !_tombstone)
_file.reset(Common::MacResManager::openFileOrDataFork(_fileName));
if (!_file)
_tombstone = true;
return _file.get();
}
}
bool validResourceId(uint32 resourceId) const {
return (resourceId < _table.size());
}
ResourceData *getResourceData(uint32 resourceId) {
if (resourceId >= _table.size()) {
error("ResourceContext::getResourceData() wrong resourceId %d", resourceId);
}
return &_table[resourceId];
}
void closeFile() {
_file.reset();
}
protected:
const char *_fileName;
uint16 _fileType;
bool _isCompressed;
int _serial; // IHNM speech files
bool _isBigEndian;
ResourceDataArray _table;
Common::ScopedPtr<Common::SeekableReadStream> _file;
Common::ScopedPtr<Common::MacResManager> _macRes;
int32 _fileSize;
bool _tombstone;
bool load(SagaEngine *_vm, Resource *resource);
bool loadResV1();
bool loadResIteAmiga(SagaEngine *_vm, int type, bool isFloppy);
bool loadResIteAmigaSound(SagaEngine *_vm, int type);
virtual bool loadRes(SagaEngine *_vm, int type) = 0;
virtual void processPatches(Resource *resource, const GamePatchDescription *patchFiles) { }
};
typedef Common::List<ResourceContext *> ResourceContextList;
struct MetaResource {
int16 sceneIndex;
int16 objectCount;
int32 objectsStringsResourceID;
int32 inventorySpritesID;
int32 mainSpritesID;
int32 objectsResourceID;
int16 actorCount;
int32 actorsStringsResourceID;
int32 actorsResourceID;
int32 protagFaceSpritesID;
int32 field_22;
int16 field_26;
int16 protagStatesCount;
int32 protagStatesResourceID;
int32 cutawayListResourceID;
int32 songTableID;
MetaResource() {
memset(this, 0, sizeof(*this));
}
};
class Resource {
public:
Resource(SagaEngine *vm);
virtual ~Resource();
bool createContexts();
void clearContexts();
void loadResource(ResourceContext *context, uint32 resourceId, ByteArray &resourceBuffer);
virtual uint32 convertResourceId(uint32 resourceId) = 0;
virtual void loadGlobalResources(int chapter, int actorsEntrance) = 0;
ResourceContext *getContext(uint16 fileType, int serial = 0);
virtual MetaResource* getMetaResource() = 0;
protected:
SagaEngine *_vm;
ResourceContextList _contexts;
char _voicesFileName[8][256];
char _musicFileName[256];
char _soundFileName[256];
void addContext(const char *fileName, uint16 fileType, bool isCompressed = false, int serial = 0);
virtual ResourceContext *createContext() = 0;
};
// ITE
class ResourceContext_RSC: public ResourceContext {
protected:
bool loadRes(SagaEngine *_vm, int type) override {
return loadResV1();
}
void processPatches(Resource *resource, const GamePatchDescription *patchFiles) override;
};
class ResourceContext_RSC_ITE_Amiga: public ResourceContext {
public:
ResourceContext_RSC_ITE_Amiga(bool isFloppy) : _isFloppy(isFloppy) {}
protected:
bool loadRes(SagaEngine *_vm, int type) override {
return loadResIteAmiga(_vm, type, _isFloppy);
}
bool _isFloppy;
};
class Resource_RSC : public Resource {
public:
Resource_RSC(SagaEngine *vm) : Resource(vm) {}
uint32 convertResourceId(uint32 resourceId) override {
return _vm->isMacResources() ? resourceId - 2 : resourceId;
}
void loadGlobalResources(int chapter, int actorsEntrance) override {}
MetaResource* getMetaResource() override {
MetaResource *dummy = 0;
return dummy;
}
protected:
ResourceContext *createContext() override {
if (_vm->getPlatform() == Common::kPlatformAmiga && _vm->getGameId() == GID_ITE) {
return new ResourceContext_RSC_ITE_Amiga(_vm->getFeatures() & GF_ITE_FLOPPY);
}
return new ResourceContext_RSC();
}
};
#ifdef ENABLE_IHNM
// IHNM
class ResourceContext_RES: public ResourceContext {
protected:
bool loadRes(SagaEngine *_vm, int type) override {
return loadResV1();
}
void processPatches(Resource *resource, const GamePatchDescription *patchFiles) override;
};
// TODO: move load routines from sndres
class VoiceResourceContext_RES: public ResourceContext {
protected:
bool loadRes(SagaEngine *_vm, int type) override {
return false;
}
public:
VoiceResourceContext_RES() : ResourceContext() {
_fileType = GAME_VOICEFILE;
_isBigEndian = true;
}
};
class Resource_RES : public Resource {
public:
Resource_RES(SagaEngine *vm) : Resource(vm) {}
uint32 convertResourceId(uint32 resourceId) override { return resourceId; }
void loadGlobalResources(int chapter, int actorsEntrance) override;
MetaResource* getMetaResource() override { return &_metaResource; }
protected:
ResourceContext *createContext() override {
return new ResourceContext_RES();
}
private:
MetaResource _metaResource;
};
#endif
} // End of namespace Saga
#endif

View File

@@ -0,0 +1,233 @@
/* 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/>.
*
*/
// RSC Resource file management module (SAGA 1, used in IHNM)
#include "saga/saga.h"
#include "saga/actor.h"
#include "saga/animation.h"
#include "saga/interface.h"
#include "saga/music.h"
#include "saga/resource.h"
#include "saga/scene.h"
#include "saga/sndres.h"
namespace Saga {
#ifdef ENABLE_IHNM
static int metaResourceTable[] = { 0, 326, 517, 677, 805, 968, 1165, 0, 1271 };
static int metaResourceTableDemo[] = { 0, 0, 0, 0, 0, 0, 0, 285, 0 };
void Resource_RES::loadGlobalResources(int chapter, int actorsEntrance) {
if (chapter < 0)
chapter = !_vm->isIHNMDemo() ? 8 : 7;
_vm->_script->_globalVoiceLUT.clear();
ResourceContext *resourceContext;
ResourceContext *soundContext;
uint i;
resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
if (resourceContext == NULL) {
error("Resource::loadGlobalResources() resource context not found");
}
soundContext = _vm->_resource->getContext(GAME_SOUNDFILE);
if (soundContext == NULL) {
error("Resource::loadGlobalResources() sound context not found");
}
ByteArray resourceData;
if (!_vm->isIHNMDemo()) {
_vm->_resource->loadResource(resourceContext, metaResourceTable[chapter], resourceData);
} else {
_vm->_resource->loadResource(resourceContext, metaResourceTableDemo[chapter], resourceData);
}
if (resourceData.empty()) {
error("Resource::loadGlobalResources wrong metaResource");
}
{
ByteArrayReadStreamEndian metaS(resourceData);
_metaResource.sceneIndex = metaS.readSint16LE();
_metaResource.objectCount = metaS.readSint16LE();
_metaResource.objectsStringsResourceID = metaS.readSint32LE();
_metaResource.inventorySpritesID = metaS.readSint32LE();
_metaResource.mainSpritesID = metaS.readSint32LE();
_metaResource.objectsResourceID = metaS.readSint32LE();
_metaResource.actorCount = metaS.readSint16LE();
_metaResource.actorsStringsResourceID = metaS.readSint32LE();
_metaResource.actorsResourceID = metaS.readSint32LE();
_metaResource.protagFaceSpritesID = metaS.readSint32LE();
_metaResource.field_22 = metaS.readSint32LE();
_metaResource.field_26 = metaS.readSint16LE();
_metaResource.protagStatesCount = metaS.readSint16LE();
_metaResource.protagStatesResourceID = metaS.readSint32LE();
_metaResource.cutawayListResourceID = metaS.readSint32LE();
_metaResource.songTableID = metaS.readSint32LE();
}
_vm->_actor->loadActorList(actorsEntrance, _metaResource.actorCount,
_metaResource.actorsResourceID, _metaResource.protagStatesCount,
_metaResource.protagStatesResourceID);
_vm->_actor->_protagonist->_sceneNumber = _metaResource.sceneIndex;
_vm->_actor->_objectsStrings.clear();
_vm->_resource->loadResource(resourceContext, _metaResource.objectsStringsResourceID, resourceData);
_vm->loadStrings(_vm->_actor->_objectsStrings, resourceData, _vm->isBigEndian());
if (uint(chapter) >= _vm->_sndRes->_fxTableIDs.size()) {
error("Chapter ID exceeds fxTableIDs length");
}
debug(0, "Going to read %d of %d", chapter, _vm->_sndRes->_fxTableIDs[chapter]);
_vm->_resource->loadResource(soundContext, _vm->_sndRes->_fxTableIDs[chapter],
resourceData);
if (resourceData.empty()) {
error("Resource::loadGlobalResources Can't load sound effects for current track");
}
_vm->_sndRes->_fxTable.resize(resourceData.size() / 4);
{
ByteArrayReadStreamEndian fxS(resourceData);
for (i = 0; i < _vm->_sndRes->_fxTable.size(); i++) {
_vm->_sndRes->_fxTable[i].res = fxS.readSint16LE();
_vm->_sndRes->_fxTable[i].vol = fxS.readSint16LE();
}
}
_vm->_interface->_defPortraits.clear();
_vm->_sprite->loadList(_metaResource.protagFaceSpritesID, _vm->_interface->_defPortraits);
_vm->_actor->_actorsStrings.clear();
_vm->_resource->loadResource(resourceContext, _metaResource.actorsStringsResourceID, resourceData);
_vm->loadStrings(_vm->_actor->_actorsStrings, resourceData, _vm->isBigEndian());
_vm->_sprite->_inventorySprites.clear();
_vm->_sprite->loadList(_metaResource.inventorySpritesID, _vm->_sprite->_inventorySprites);
_vm->_sprite->_mainSprites.clear();
_vm->_sprite->loadList(_metaResource.mainSpritesID, _vm->_sprite->_mainSprites);
_vm->_actor->loadObjList(_metaResource.objectCount, _metaResource.objectsResourceID);
_vm->_resource->loadResource(resourceContext, _metaResource.cutawayListResourceID, resourceData);
if (resourceData.empty()) {
error("Resource::loadGlobalResources Can't load cutaway list");
}
_vm->_anim->loadCutawayList(resourceData);
if (_metaResource.songTableID > 0) {
_vm->_resource->loadResource(resourceContext, _metaResource.songTableID, resourceData);
if (chapter == 6) {
if (resourceData.size() < (uint(actorsEntrance) * 4 + 4)) {
error("Resource::loadGlobalResources chapter 6 has wrong resource");
}
int32 id = READ_LE_UINT32(&resourceData[actorsEntrance * 4]);
_vm->_resource->loadResource(resourceContext, id, resourceData);
}
if (resourceData.empty()) {
error("Resource::loadGlobalResources Can't load songs list for current track");
}
_vm->_music->_songTable.resize(resourceData.size() / 4);
ByteArrayReadStreamEndian songS(resourceData);
for (i = 0; i < _vm->_music->_songTable.size(); i++)
_vm->_music->_songTable[i] = songS.readSint32LE();
} else {
// The IHNM demo has a fixed music track and doesn't load a song table
_vm->_music->play(3, MUSIC_LOOP);
}
int voiceLUTResourceID = 0;
if (chapter != 7) {
int voiceBank = (chapter == 8) ? 0 : chapter;
_vm->_sndRes->setVoiceBank(voiceBank);
voiceLUTResourceID = 22 + voiceBank;
} else {
// IHNM demo
_vm->_sndRes->setVoiceBank(0);
voiceLUTResourceID = 17;
}
if (voiceLUTResourceID) {
_vm->_resource->loadResource(resourceContext, voiceLUTResourceID, resourceData);
_vm->_script->loadVoiceLUT(_vm->_script->_globalVoiceLUT, resourceData);
}
_vm->_spiritualBarometer = 0;
_vm->_scene->setChapterNumber(chapter);
}
void ResourceContext_RES::processPatches(Resource *resource, const GamePatchDescription *patchFiles) {
uint16 subjectResourceType;
ResourceContext *subjectContext;
uint32 subjectResourceId;
uint32 patchResourceId;
ResourceData *subjectResourceData;
ResourceData *resourceData;
// Process internal patch files
if (_fileType & GAME_PATCHFILE) {
subjectResourceType = ~GAME_PATCHFILE & _fileType;
subjectContext = resource->getContext((GameFileTypes)subjectResourceType);
if (subjectContext == NULL) {
error("ResourceContext::load() Subject context not found");
}
ByteArray tableBuffer;
resource->loadResource(this, _table.size() - 1, tableBuffer);
ByteArrayReadStreamEndian readS2(tableBuffer, _isBigEndian);
for (uint32 i = 0; i < tableBuffer.size() / 8; i++) {
subjectResourceId = readS2.readUint32();
patchResourceId = readS2.readUint32();
subjectResourceData = subjectContext->getResourceData(subjectResourceId);
resourceData = getResourceData(patchResourceId);
subjectResourceData->patchData = new PatchData(_file.get(), _fileName);
subjectResourceData->offset = resourceData->offset;
subjectResourceData->size = resourceData->size;
}
}
}
#endif
} // End of namespace Saga

View File

@@ -0,0 +1,57 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 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/>.
*
*/
// RSC Resource file management module (SAGA 1, used in ITE)
#include "saga/saga.h"
#include "saga/resource.h"
namespace Saga {
void ResourceContext_RSC::processPatches(Resource *resource, const GamePatchDescription *patchFiles) {
const GamePatchDescription *patchDescription;
ResourceData *resourceData;
// Process external patch files
for (patchDescription = patchFiles; patchDescription && patchDescription->fileName; ++patchDescription) {
if ((patchDescription->fileType & _fileType) != 0) {
if (patchDescription->resourceId < _table.size()) {
resourceData = &_table[patchDescription->resourceId];
// Check if we've already found a patch for this resource. One is enough.
if (!resourceData->patchData) {
resourceData->patchData = new PatchData(patchDescription->fileName);
Common::SeekableReadStream *s = resourceData->patchData->getStream();
if (s) {
resourceData->offset = 0;
resourceData->size = s->size();
// The patched ITE file is in memory, so close the patch file
resourceData->patchData->closeStream();
} else {
delete resourceData->patchData;
resourceData->patchData = nullptr;
}
}
}
}
}
}
} // End of namespace Saga

886
engines/saga/saga.cpp Normal file
View File

@@ -0,0 +1,886 @@
/* 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/fs.h"
#include "common/config-manager.h"
#include "common/system.h"
#include "common/events.h"
#include "common/translation.h"
#include "common/compression/unarj.h"
#include "common/compression/unzip.h"
#include "audio/mixer.h"
#include "saga/saga.h"
#include "saga/resource.h"
#include "saga/gfx.h"
#include "saga/render.h"
#include "saga/actor.h"
#include "saga/animation.h"
#include "saga/console.h"
#include "saga/events.h"
#include "saga/font.h"
#include "saga/interface.h"
#include "saga/isomap.h"
#include "saga/puzzle.h"
#include "saga/script.h"
#include "saga/scene.h"
#include "saga/sndres.h"
#include "saga/sprite.h"
#include "saga/sound.h"
#include "saga/music.h"
#include "saga/palanim.h"
#include "saga/objectmap.h"
namespace Saga {
const char *engineKeyMapId = "engine-default";
const char *gameKeyMapId = "game-shortcuts";
const char *optionKeyMapId = "option-panel";
const char *saveKeyMapId = "save-panel";
const char *loadKeyMapId = "load-panel";
const char *quitKeyMapId = "quit-panel";
const char *converseKeyMapId = "converse-panel";
static const GameResourceDescription ITE_Resources_GermanAGACD = {
1810, // Scene lookup table RN
216, // Script lookup table RN
3, // Main panel
4, // Converse panel
5, // Option panel
6, // Main sprites
7, // Main panel sprites
35, // Main strings
// ITE specific resources
36, // Actor names
125, // Default portraits
// IHNM specific resources
0, // Option panel sprites
0, // Warning panel
0, // Warning panel sprites
0 // Psychic profile background
};
static const GameResourceDescription ITE_Resources_GermanECSCD = {
1816, // Scene lookup table RN
216, // Script lookup table RN
3, // Main panel
4, // Converse panel
5, // Option panel
6, // Main sprites
7, // Main panel sprites
35, // Main strings
// ITE specific resources
36, // Actor names
125, // Default portraits
// IHNM specific resources
0, // Option panel sprites
0, // Warning panel
0, // Warning panel sprites
0 // Psychic profile background
};
static const GameResourceDescription ITE_Resources = {
1806, // Scene lookup table RN
216, // Script lookup table RN
3, // Main panel
4, // Converse panel
5, // Option panel
6, // Main sprites
7, // Main panel sprites
35, // Main strings
// ITE specific resources
36, // Actor names
125, // Default portraits
// IHNM specific resources
0, // Option panel sprites
0, // Warning panel
0, // Warning panel sprites
0 // Psychic profile background
};
static const GameResourceDescription ITE_Resources_EnglishECSCD = {
1812, // Scene lookup table RN
216, // Script lookup table RN
3, // Main panel
4, // Converse panel
5, // Option panel
6, // Main sprites
7, // Main panel sprites
35, // Main strings
// ITE specific resources
36, // Actor names
125, // Default portraits
// IHNM specific resources
0, // Option panel sprites
0, // Warning panel
0, // Warning panel sprites
0 // Psychic profile background
};
// FIXME: Option panel should be 4 but it is an empty resource.
// Proper fix would be to not load the options panel when the demo is running
static const GameResourceDescription ITEDemo_Resources = {
318, // Scene lookup table RN
146, // Script lookup table RN
2, // Main panel
3, // Converse panel
3, // Option panel
5, // Main sprites
6, // Main panel sprites
8, // Main strings
// ITE specific resources
9, // Actor names
80, // Default portraits
// IHNM specific resources
0, // Option panel sprites
0, // Warning panel
0, // Warning panel sprites
0 // Psychic profile background
};
static const GameResourceDescription IHNM_Resources = {
1272, // Scene lookup table RN
29, // Script lookup table RN
9, // Main panel
10, // Converse panel
15, // Option panel
12, // Main sprites
12, // Main panel sprites
21, // Main strings
// ITE specific resources
0, // Actor names
0, // Default portraits
// IHNM specific resources
16, // Option panel sprites
17, // Warning panel
18, // Warning panel sprites
20 // Psychic profile background
};
static const GameResourceDescription IHNMDEMO_Resources = {
286, // Scene lookup table RN
18, // Script lookup table RN
5, // Main panel
6, // Converse panel
10, // Option panel
7, // Main sprites
7, // Main panel sprites
16, // Main strings
// ITE specific resources
0, // Actor names
0, // Default portraits
// IHNM specific resources
11, // Option panel sprites
12, // Warning panel
13, // Warning panel sprites
15 // Psychic profile background
};
static const GameResourceDescription *ResourceLists[RESOURCELIST_MAX] = {
/* RESOURCELIST_NONE */ nullptr,
/* RESOURCELIST_ITE */ &ITE_Resources,
/* RESOURCELIST_ITE_ENGLISH_ECS_CD */ &ITE_Resources_EnglishECSCD,
/* RESOURCELIST_ITE_GERMAN_AGA_CD */ &ITE_Resources_GermanAGACD,
/* RESOURCELIST_ITE_GERMAN_ECS_CD */ &ITE_Resources_GermanECSCD,
/* RESOURCELIST_ITE_DEMO */ &ITEDemo_Resources,
/* RESOURCELIST_IHNM */ &IHNM_Resources,
/* RESOURCELIST_IHNM_DEMO */ &IHNMDEMO_Resources
};
#define MAX_TIME_DELTA 100
SagaEngine::SagaEngine(OSystem *syst, const SAGAGameDescription *gameDesc)
: Engine(syst), _gameDescription(gameDesc), _rnd("saga") {
_framesEsc = 0;
_globalFlags = 0;
memset(_ethicsPoints, 0, sizeof(_ethicsPoints));
_spiritualBarometer = 0;
_soundVolume = 0;
_speechVolume = 0;
_subtitlesEnabled = false;
_voicesEnabled = false;
_voiceFilesExist = false;
_readingSpeed = 0;
_copyProtection = false;
_musicWasPlaying = false;
_hasITESceneSubstitutes = false;
_sndRes = NULL;
_sound = NULL;
_music = NULL;
_anim = NULL;
_render = NULL;
_isoMap = NULL;
_gfx = NULL;
_script = NULL;
_actor = NULL;
_font = NULL;
_sprite = NULL;
_scene = NULL;
_interface = NULL;
_console = NULL;
_events = NULL;
_palanim = NULL;
_puzzle = NULL;
_resource = NULL;
_previousTicks = 0;
_saveFilesCount = 0;
_leftMouseButtonPressed = _rightMouseButtonPressed = false;
_mouseClickCount = 0;
_gameNumber = 0;
_frameCount = 0;
const Common::FSNode gameDataDir(ConfMan.getPath("path"));
// The Linux version of Inherit the Earth puts all data files in an
// 'itedata' sub-directory, except for voices.rsc
SearchMan.addSubDirectoryMatching(gameDataDir, "itedata");
// The Windows version of Inherit the Earth puts various data files in
// other subdirectories.
SearchMan.addSubDirectoryMatching(gameDataDir, "graphics");
SearchMan.addSubDirectoryMatching(gameDataDir, "music");
SearchMan.addSubDirectoryMatching(gameDataDir, "sound");
// Location of Miles audio files (sample.ad and sample.opl) in IHNM
SearchMan.addSubDirectoryMatching(gameDataDir, "drivers");
// The Multi-OS version puts the voices file in the root directory of
// the CD. The rest of the data files are in game/itedata
SearchMan.addSubDirectoryMatching(gameDataDir, "game/itedata");
// Mac CD Wyrmkeep
SearchMan.addSubDirectoryMatching(gameDataDir, "patch");
if (getPlatform() == Common::Platform::kPlatformMacintosh)
SearchMan.addSubDirectoryMatching(gameDataDir, "ITE Data Files");
_displayClip.left = _displayClip.top = 0;
}
SagaEngine::~SagaEngine() {
if (_scene != NULL) {
if (_scene->isSceneLoaded()) {
_scene->endScene();
}
}
if (getGameId() == GID_ITE) {
delete _isoMap;
_isoMap = NULL;
delete _puzzle;
_puzzle = NULL;
}
delete _sndRes;
_sndRes = NULL;
delete _events;
_events = NULL;
delete _font;
_font = NULL;
delete _sprite;
_sprite = NULL;
delete _anim;
_anim = NULL;
delete _script;
_script = NULL;
delete _interface;
_interface = NULL;
delete _actor;
_actor = NULL;
delete _palanim;
_palanim = NULL;
delete _scene;
_scene = NULL;
delete _render;
_render = NULL;
delete _music;
_music = NULL;
delete _sound;
_sound = NULL;
delete _gfx;
_gfx = NULL;
//_console is deleted by Engine
_console = NULL;
delete _resource;
_resource = NULL;
}
Common::Error SagaEngine::run() {
setTotalPlayTime(0);
if (getFeatures() & GF_INSTALLER) {
Common::Array<Common::Path> filenames;
for (const ADGameFileDescription *gameArchiveDescription = getArchivesDescriptions();
gameArchiveDescription->fileName; gameArchiveDescription++)
filenames.push_back(gameArchiveDescription->fileName);
Common::Archive *archive = nullptr;
if (filenames.size() == 1 && filenames[0].baseName().hasSuffix(".exe"))
archive = Common::makeZipArchive(filenames[0], true);
else
archive = Common::makeArjArchive(filenames, true);
if (!archive)
error("Error opening archive");
SearchMan.add("archive", archive, DisposeAfterUse::YES);
}
// Assign default values to the config manager, in case settings are missing
ConfMan.registerDefault("talkspeed", "255");
ConfMan.registerDefault("subtitles", "true");
_subtitlesEnabled = ConfMan.getBool("subtitles");
_readingSpeed = getTalkspeed();
_copyProtection = ConfMan.getBool("copy_protection");
_musicWasPlaying = false;
_isIHNMDemo = Common::File::exists("music.res");
_hasITESceneSubstitutes = Common::File::exists("boarhall.bbm");
if (_readingSpeed > 3)
_readingSpeed = 0;
switch (getGameId()) {
case GID_ITE:
_resource = new Resource_RSC(this);
break;
#ifdef ENABLE_IHNM
case GID_IHNM:
_resource = new Resource_RES(this);
break;
#endif
default:
break;
}
// Detect game and open resource files
if (!initGame()) {
GUIErrorMessage(_("Error loading game resources."));
return Common::kUnknownError;
}
// Initialize engine modules
_sndRes = new SndRes(this);
_events = new Events(this);
if (getLanguage() == Common::JA_JPN)
_font = new SJISFont(this);
else
_font = new DefaultFont(this);
_sprite = new Sprite(this);
_script = new SAGA1Script(this);
_anim = new Anim(this);
_interface = new Interface(this); // requires script module
_scene = new Scene(this);
_actor = new Actor(this);
_palanim = new PalAnim(this);
if (getGameId() == GID_ITE) {
_isoMap = new IsoMap(this);
_puzzle = new Puzzle(this);
}
// System initialization
_previousTicks = _system->getMillis();
// Initialize graphics
_gfx = new Gfx(this, _system, getDisplayInfo().width, getDisplayInfo().height);
// Graphics driver should be initialized before console
_console = new Console(this);
setDebugger(_console);
// Graphics should be initialized before music
_music = new Music(this, _mixer);
_render = new Render(this, _system);
if (!_render->initialized()) {
return Common::kUnknownError;
}
// Initialize system specific sound
_sound = new Sound(this, _mixer);
_interface->converseClear();
_script->setVerb(_script->getVerbType(kVerbWalkTo));
_music->resetVolume();
_gfx->initPalette();
if (_voiceFilesExist) {
if (getGameId() == GID_IHNM) {
if (!ConfMan.hasKey("voices")) {
_voicesEnabled = true;
ConfMan.setBool("voices", true);
} else {
_voicesEnabled = ConfMan.getBool("voices");
}
} else {
_voicesEnabled = true;
}
}
syncSoundSettings();
int msec = 0;
_previousTicks = _system->getMillis();
if (ConfMan.hasKey("start_scene")) {
_scene->changeScene(ConfMan.getInt("start_scene"), 0, kTransitionNoFade);
} else if (ConfMan.hasKey("boot_param")) {
if (getGameId() == GID_ITE)
_interface->addToInventory(_actor->objIndexToId(0)); // Magic hat
_scene->changeScene(ConfMan.getInt("boot_param"), 0, kTransitionNoFade);
} else if (ConfMan.hasKey("save_slot")) {
// Init the current chapter to 8 (character selection) for IHNM
if (getGameId() == GID_IHNM)
_scene->changeScene(-2, 0, kTransitionFade, 8);
// First scene sets up palette
_scene->changeScene(getStartSceneNumber(), 0, kTransitionNoFade);
_events->handleEvents(0); // Process immediate events
if (getGameId() == GID_ITE)
_interface->setMode(kPanelMain);
else
_interface->setMode(kPanelChapterSelection);
char *fileName = calcSaveFileName(ConfMan.getInt("save_slot"));
load(fileName);
syncSoundSettings();
} else {
_framesEsc = 0;
_scene->startScene();
}
uint32 currentTicks;
while (!shouldQuit()) {
if (_render->getFlags() & RF_RENDERPAUSE) {
// Freeze time while paused
_previousTicks = _system->getMillis();
} else {
currentTicks = _system->getMillis();
// Timer has rolled over after 49 days
if (currentTicks < _previousTicks)
msec = 0;
else {
msec = currentTicks - _previousTicks;
_previousTicks = currentTicks;
}
if (msec > MAX_TIME_DELTA) {
msec = MAX_TIME_DELTA;
}
// Since Puzzle and forced text are actorless, we do them here
if ((getGameId() == GID_ITE && _puzzle->isActive()) || _actor->isForcedTextShown()) {
_actor->handleSpeech(msec);
} else if (!_scene->isInIntro()) {
if (_interface->getMode() == kPanelMain ||
_interface->getMode() == kPanelConverse ||
_interface->getMode() == kPanelCutaway ||
_interface->getMode() == kPanelNull ||
_interface->getMode() == kPanelChapterSelection)
_actor->direct(msec);
}
_events->handleEvents(msec);
_script->executeThreads(msec);
}
// Per frame processing
_render->drawScene();
_system->delayMillis(10);
}
_music->close();
return Common::kNoError;
}
const GameResourceDescription *SagaEngine::getResourceDescription() const {
GameResourceList index = getResourceList();
assert(index < RESOURCELIST_MAX && index > RESOURCELIST_NONE);
return ResourceLists[index];
}
void SagaEngine::loadStrings(StringsTable &stringsTable, const ByteArray &stringsData, bool isBigEndian) {
uint16 stringsCount;
size_t offset;
size_t prevOffset = 0;
Common::Array<size_t> tempOffsets;
uint ui;
if (stringsData.empty()) {
error("SagaEngine::loadStrings() Error loading strings list resource");
}
ByteArrayReadStreamEndian scriptS(stringsData, isBigEndian);
offset = scriptS.readUint16();
stringsCount = offset / 2;
ui = 0;
scriptS.seek(0);
tempOffsets.resize(stringsCount);
while (ui < stringsCount) {
offset = scriptS.readUint16();
// In some rooms in IHNM, string offsets can be greater than the maximum value than a 16-bit integer can hold
// We detect this by checking the previous offset, and if it was bigger than the current one, an overflow
// occurred (since the string offsets are sequential), so we're adding the missing part of the number
// Fixes bug #3629 - "IHNM: end game text/caption error"
if (prevOffset > offset)
offset += 65536;
prevOffset = offset;
if (offset == stringsData.size()) {
stringsCount = ui;
tempOffsets.resize(stringsCount);
break;
}
if (offset > stringsData.size()) {
// This case should never occur, but apparently it does in the Italian fan
// translation of IHNM
warning("SagaEngine::loadStrings wrong strings table");
stringsCount = ui;
tempOffsets.resize(stringsCount);
break;
}
tempOffsets[ui] = offset;
ui++;
}
prevOffset = scriptS.pos();
int32 left = scriptS.size() - prevOffset;
if (left < 0) {
error("SagaEngine::loadStrings() Error loading strings buffer");
}
stringsTable.buffer.resize(left);
if (left > 0) {
scriptS.read(&stringsTable.buffer.front(), left);
}
stringsTable.strings.resize(tempOffsets.size());
for (ui = 0; ui < tempOffsets.size(); ui++) {
offset = tempOffsets[ui] - prevOffset;
if (offset >= stringsTable.buffer.size()) {
error("SagaEngine::loadStrings() Wrong offset");
}
stringsTable.strings[ui] = &stringsTable.buffer[offset];
debug(9, "string[%i]=%s", ui, stringsTable.strings[ui]);
}
}
const char *SagaEngine::getObjectName(uint16 objectId) const {
ActorData *actor;
ObjectData *obj;
const HitZone *hitZone;
// Disable the object names in IHNM when the chapter is 8
if (getGameId() == GID_IHNM && _scene->currentChapterNumber() == 8)
return "";
switch (objectTypeId(objectId)) {
case kGameObjectObject:
obj = _actor->getObj(objectId);
if (getGameId() == GID_ITE)
return _script->_mainStrings.getString(obj->_nameIndex);
return _actor->_objectsStrings.getString(obj->_nameIndex);
case kGameObjectActor:
actor = _actor->getActor(objectId);
return _actor->_actorsStrings.getString(actor->_nameIndex);
case kGameObjectHitZone:
hitZone = _scene->_objectMap->getHitZone(objectIdToIndex(objectId));
if (hitZone == NULL)
return "";
return _scene->_sceneStrings.getString(hitZone->getNameIndex());
default:
break;
}
warning("SagaEngine::getObjectName name not found for 0x%X", objectId);
return NULL;
}
int SagaEngine::getLanguageIndex() {
switch (getLanguage()) {
case Common::EN_ANY:
return 0;
case Common::DE_DEU:
return 1;
case Common::IT_ITA:
return 2;
case Common::ES_ESP:
return 3;
case Common::FR_FRA:
return 4;
case Common::JA_JPN:
return 5;
case Common::RU_RUS:
return 6;
case Common::HE_ISR:
return 7;
case Common::ZH_TWN:
return 8;
default:
return 0;
}
}
const char *SagaEngine::getTextString(int textStringId) {
const char *string;
int lang = getLanguageIndex();
if (getLanguage() == Common::RU_RUS && textStringId == 43) {
if (getGameId() == GID_ITE)
return "\xCF\xF0\xE8\xEC\xE5\xED\xE8\xF2\xFC -> %s -> %s"; // "Применить -> %s -> %s"
else
return "\xC8\xF1\xEF\xEE\xEB\xFC\xE7\xEE\xE2\xE0\xF2\xFC %s >> %s"; // "Использовать %s >> %s"
}
string = ITEinterfaceTextStrings[lang][textStringId];
if (!string)
string = ITEinterfaceTextStrings[0][textStringId];
return string;
}
void SagaEngine::getExcuseInfo(int verb, const char *&textString, int &soundResourceId) {
textString = NULL;
if (verb == _script->getVerbType(kVerbOpen)) {
textString = getTextString(kTextNoPlaceToOpen);
soundResourceId = 239; // Boar voice 0
}
if (verb == _script->getVerbType(kVerbClose)) {
textString = getTextString(kTextNoOpening);
soundResourceId = 241; // Boar voice 2
}
if (verb == _script->getVerbType(kVerbUse)) {
textString = getTextString(kTextDontKnow);
soundResourceId = 244; // Boar voice 5
}
if (verb == _script->getVerbType(kVerbLookAt)) {
textString = getTextString(kTextNothingSpecial);
soundResourceId = 245; // Boar voice 6
}
if (verb == _script->getVerbType(kVerbPickUp)) {
textString = getTextString(kTextICantPickup);
soundResourceId = 246; // Boar voice 7
}
}
void SagaEngine::enableKeyMap(int mode) {
PanelModes newPanelMode = (PanelModes)mode;
if (_currentPanelMode == newPanelMode) {
return;
}
Common::String id;
switch (newPanelMode) {
case kPanelMain:
id = gameKeyMapId;
break;
case kPanelOption:
id = optionKeyMapId;
break;
case kPanelSave:
id = saveKeyMapId;
break;
case kPanelLoad:
id = loadKeyMapId;
break;
case kPanelQuit:
id = quitKeyMapId;
break;
case kPanelConverse:
id = converseKeyMapId;
break;
default:
id = ""; // disable all keymaps if it is not any of above Panels
break;
}
Common::Keymapper *keymapper = g_system->getEventManager()->getKeymapper();
const Common::KeymapArray &keymaps = keymapper->getKeymaps();
for (Common::Keymap *keymap : keymaps) {
const Common::String &keymapId = keymap->getId();
if (keymap->getType() == Common::Keymap::kKeymapTypeGame && keymapId != engineKeyMapId) {
keymap->setEnabled(keymapId == id);
}
}
_currentPanelMode = newPanelMode;
}
ColorId SagaEngine::KnownColor2ColorId(KnownColor knownColor) {
ColorId colorId = kITEDOSColorTransBlack;
if (getGameId() == GID_ITE) {
switch (knownColor) {
case(kKnownColorTransparent):
colorId = iteColorTransBlack();
break;
case (kKnownColorBrightWhite):
colorId = iteColorBrightWhite();
break;
case (kKnownColorWhite):
colorId = iteColorWhite();
break;
case (kKnownColorBlack):
colorId = iteColorBlack();
break;
case (kKnownColorSubtitleTextColor):
colorId = isECS() ? kITEECSColorWhite : (ColorId)255;
break;
case (kKnownColorSubtitleEffectColorPC98):
colorId = (ColorId)210;
break;
case (kKnownColorVerbText):
colorId = isECS() ? kITEECSBottomColorBlue : kITEDOSColorBlue;
break;
case (kKnownColorVerbTextShadow):
colorId = isECS() ? kITEECSColorBlack : kITEDOSColorBlack;
break;
case (kKnownColorVerbTextActive):
colorId = isECS() ? kITEECSBottomColorYellow60 : kITEDOSColorYellow60;
break;
default:
error("SagaEngine::KnownColor2ColorId unknown color %i", knownColor);
}
#ifdef ENABLE_IHNM
} else if (getGameId() == GID_IHNM) {
// The default colors in the Spanish, version of IHNM are shifted by one
// Fixes bug #3498 - "IHNM: Wrong Subtitles Color (Spanish)". This
// also applies to the German and French versions (bug #7064 - "IHNM:
// text mistake in german version").
int offset = (getFeatures() & GF_IHNM_COLOR_FIX) ? 1 : 0;
switch (knownColor) {
case(kKnownColorTransparent):
colorId = (ColorId)(249 - offset);
break;
case (kKnownColorBrightWhite):
colorId = (ColorId)(251 - offset);
break;
case (kKnownColorWhite):
colorId = (ColorId)(251 - offset);
break;
case (kKnownColorBlack):
colorId = (ColorId)(249 - offset);
break;
case (kKnownColorVerbText):
colorId = (ColorId)(253 - offset);
break;
case (kKnownColorVerbTextShadow):
colorId = (ColorId)(15 - offset);
break;
case (kKnownColorVerbTextActive):
colorId = (ColorId)(252 - offset);
break;
default:
error("SagaEngine::KnownColor2ColorId unknown color %i", knownColor);
}
#endif
}
return colorId;
}
void SagaEngine::setTalkspeed(int talkspeed) {
ConfMan.setInt("talkspeed", (talkspeed * 255 + 3 / 2) / 3);
}
int SagaEngine::getTalkspeed() const {
return (ConfMan.getInt("talkspeed") * 3 + 255 / 2) / 255;
}
void SagaEngine::syncSoundSettings() {
Engine::syncSoundSettings();
_subtitlesEnabled = ConfMan.getBool("subtitles");
_readingSpeed = getTalkspeed();
if (_readingSpeed > 3)
_readingSpeed = 0;
_music->syncSoundSettings();
}
void SagaEngine::pauseEngineIntern(bool pause) {
if (!_render || !_music)
return;
bool engineIsPaused = (_render->getFlags() & RF_RENDERPAUSE);
if (engineIsPaused == pause)
return;
if (pause) {
_render->setFlag(RF_RENDERPAUSE);
if (_music->isPlaying() && !_music->hasDigitalMusic()) {
_music->pause();
_musicWasPlaying = true;
} else {
_musicWasPlaying = false;
}
} else {
_render->clearFlag(RF_RENDERPAUSE);
if (_musicWasPlaying) {
_music->resume();
}
}
_mixer->pauseAll(pause);
}
} // End of namespace Saga

727
engines/saga/saga.h Normal file
View File

@@ -0,0 +1,727 @@
/* 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 SAGA_SAGA_H
#define SAGA_SAGA_H
#include "engines/engine.h"
#include "common/array.h"
#include "common/random.h"
#include "common/memstream.h"
#include "common/textconsole.h"
#include "backends/keymapper/action.h"
#include "backends/keymapper/keymapper.h"
#include "saga/gfx.h"
#include "saga/detection.h"
struct ADGameFileDescription;
/**
* This is the namespace of the SAGA engine.
*
* Status of this engine: complete
*
* Games using this engine:
* - Inherit the Earth
* - I Have No Mouth And I Must Scream
*
*/
namespace Saga {
class SndRes;
class Sound;
class Music;
class Anim;
class Render;
class IsoMap;
class Gfx;
class Script;
class Actor;
class Font;
class Sprite;
class Scene;
class Interface;
class Console;
class Events;
class PalAnim;
class Puzzle;
class Resource;
class ResourceContext;
// #define SAGA_DEBUG 1 // define for test functions
#define SAGA_IMAGE_DATA_OFFSET 776
#define SAGA_IMAGE_HEADER_LEN 8
// Note that IHNM has a smaller save title size than ITE
// We allocate the ITE save title size in savegames, to
// preserve savegame backwards compatibility. We only check
// for IHNM's save title during text input
#define SAVE_TITLE_SIZE 28
#define TITLESIZE 80
#define IHNM_SAVE_TITLE_SIZE 22
#define MAX_SAVES 96
#define MAX_FILE_NAME 256
#define ID_NOTHING 0
#define ID_PROTAG 1
#define OBJECT_TYPE_SHIFT 13
#define OBJECT_TYPE_MASK ((1 << OBJECT_TYPE_SHIFT) - 1)
#define IHNM_OBJ_PROFILE 0x4000
#define memoryError(Place) error("%s Memory allocation error.", Place)
enum ERRORCODE {
FAILURE = -1,
SUCCESS = 0
};
enum SAGAAction {
kActionNone,
kActionLookAt,
kActionWalkTo,
kActionPickUp,
kActionTalkTo,
kActionOpen,
kActionClose,
kActionUse,
kActionGive,
kActionTake,
kActionSwallow,
kActionPush,
kActionPause,
kActionAbortSpeech,
kActionBossKey,
kActionEscape,
kActionShowDialogue,
kActionOptions,
kActionOptionQuit,
kActionOptionCancel,
kActionOptionSave,
kActionOptionOkay,
kActionOptionReadingSpeed,
kActionOptionMusic,
kActionOptionSound,
kActionOptionVoices,
kActionOptionContinue,
kActionOptionLoad,
kActionOptionQuitGame,
kActionOptionSaveGame,
kActionConverseExit,
kActionConversePosUp,
kActionConversePosDown
};
extern const char *engineKeyMapId;
extern const char *gameKeyMapId;
extern const char *optionKeyMapId;
extern const char *saveKeyMapId;
extern const char *loadKeyMapId;
extern const char *quitKeyMapId;
extern const char *converseKeyMapId;
enum VerbTypeIds {
kVerbITENone = 0,
kVerbITEPickUp = 1,
kVerbITELookAt = 2,
kVerbITEWalkTo = 3,
kVerbITETalkTo = 4,
kVerbITEOpen = 5,
kVerbITEClose = 6,
kVerbITEGive = 7,
kVerbITEUse = 8,
kVerbITEOptions = 9,
kVerbITEEnter = 10,
kVerbITELeave = 11,
kVerbITEBegin = 12,
kVerbITEWalkOnly = 13,
kVerbITELookOnly = 14,
kVerbIHNMNone = 0,
kVerbIHNMWalk = 1,
kVerbIHNMLookAt = 2,
kVerbIHNMTake = 3,
kVerbIHNMUse = 4,
kVerbIHNMTalkTo = 5,
kVerbIHNMSwallow = 6,
kVerbIHNMGive = 7,
kVerbIHNMPush = 8,
kVerbIHNMOptions = 9,
kVerbIHNMEnter = 10,
kVerbIHNMLeave = 11,
kVerbIHNMBegin = 12,
kVerbIHNMWalkOnly = 13,
kVerbIHNMLookOnly = 14,
kVerbTypeIdsMax = kVerbITELookOnly + 1
};
enum PanelButtonType {
kPanelButtonVerb = 1 << 0,
kPanelButtonArrow = 1 << 1,
kPanelButtonConverseText = 1 << 2,
kPanelButtonInventory = 1 << 3,
kPanelButtonOption = 1 << 4,
kPanelButtonOptionSlider = 1 << 5,
kPanelButtonOptionSaveFiles = 1 << 6,
kPanelButtonOptionText = 1 << 7,
kPanelButtonQuit = 1 << 8,
kPanelButtonQuitText = 1 << 9,
kPanelButtonLoad = 1 << 10,
kPanelButtonLoadText = 1 << 11,
kPanelButtonSave = 1 << 12,
kPanelButtonSaveText = 1 << 13,
kPanelButtonSaveEdit = 1 << 14,
kPanelButtonProtectText = 1 << 15,
kPanelButtonProtectEdit = 1 << 16,
kPanelAllButtons = 0xFFFFF
};
enum TextStringIds {
kTextPickUp,
kTextLookAt,
kTextWalkTo,
kTextTalkTo,
kTextOpen,
kTextClose,
kTextGive,
kTextUse,
kTextOptions,
kTextTest,
kTextDemo,
kTextHelp,
kTextQuitGame,
kTextFast,
kTextSlow,
kTextOn,
kTextOff,
kTextContinuePlaying,
kTextLoad,
kTextSave,
kTextGameOptions,
kTextReadingSpeed,
kTextMusic,
kTextSound,
kTextCancel,
kTextQuit,
kTextOK,
kTextMid,
kTextClick,
kText10Percent,
kText20Percent,
kText30Percent,
kText40Percent,
kText50Percent,
kText60Percent,
kText70Percent,
kText80Percent,
kText90Percent,
kTextMax,
kTextQuitTheGameQuestion,
kTextLoadSuccessful,
kTextEnterSaveGameName,
kTextGiveTo,
kTextUseWidth,
kTextNewSave,
kTextICantPickup,
kTextNothingSpecial,
kTextNoPlaceToOpen,
kTextNoOpening,
kTextDontKnow,
kTextShowDialog,
kTextEnterProtectAnswer,
kTextVoices,
kTextText,
kTextAudio,
kTextBoth,
kTextLoadSavedGame
};
struct GameResourceDescription {
uint32 sceneLUTResourceId;
uint32 moduleLUTResourceId;
uint32 mainPanelResourceId;
uint32 conversePanelResourceId;
uint32 optionPanelResourceId;
uint32 mainSpritesResourceId;
uint32 mainPanelSpritesResourceId;
uint32 mainStringsResourceId;
// ITE specific resources
uint32 actorsStringsResourceId;
uint32 defaultPortraitsResourceId;
// IHNM specific resources
uint32 optionPanelSpritesResourceId;
uint32 warningPanelResourceId;
uint32 warningPanelSpritesResourceId;
uint32 psychicProfileResourceId;
};
struct GameFontDescription {
uint32 fontResourceId;
};
struct GameDisplayInfo;
struct GamePatchDescription {
const char *fileName;
uint16 fileType;
uint32 resourceId;
};
enum GameObjectTypes {
kGameObjectNone = 0,
kGameObjectActor = 1,
kGameObjectObject = 2,
kGameObjectHitZone = 3,
kGameObjectStepZone = 4
};
enum ScriptTimings {
kScriptTimeTicksPerSecond = (728L/10L),
kRepeatSpeedTicks = (728L/10L)/3,
kNormalFadeDuration = 320, // 64 steps, 5 msec each
kQuickFadeDuration = 64, // 64 steps, 1 msec each
kPuzzleHintTime = 30000000L // 30 secs. used in timer
};
enum Directions {
kDirUp = 0,
kDirUpRight = 1,
kDirRight = 2,
kDirDownRight = 3,
kDirDown = 4,
kDirDownLeft = 5,
kDirLeft = 6,
kDirUpLeft = 7
};
enum HitZoneFlags {
kHitZoneEnabled = (1 << 0), // Zone is enabled
kHitZoneExit = (1 << 1), // Causes char to exit
// The following flag causes the zone to act differently.
// When the actor hits the zone, it will immediately begin walking
// in the specified direction, and the actual specified effect of
// the zone will be delayed until the actor leaves the zone.
kHitZoneAutoWalk = (1 << 2),
// When set on a hit zone, this causes the character not to walk
// to the object (but they will look at it).
kHitZoneNoWalk = (1 << 2),
// zone activates only when character stops walking
kHitZoneTerminus = (1 << 3),
// Hit zones only - when the zone is clicked on it projects the
// click point downwards from the middle of the zone until it
// reaches the lowest point in the zone.
kHitZoneProject = (1 << 3)
};
struct ImageHeader {
int width;
int height;
};
struct StringsTable {
Common::Array<char> buffer;
Common::Array<char *> strings;
const char *getString(uint index) const {
if (strings.size() <= index) {
// This occurs at the end of Ted's chapter, right after the ending cutscene
warning("StringsTable::getString wrong index 0x%X (%d)", index, strings.size());
return "";
}
return strings[index];
}
void clear() {
strings.clear();
buffer.clear();
}
};
typedef Common::Array<Point> PointList;
enum ColorId {
// DOS and AGA palettes
kITEDOSColorTransBlack = 0x00,
kITEDOSColorBrightWhite = 0x01,
kITEDOSColorWhite = 0x02,
kITEDOSColorLightGrey = 0x04,
kITEDOSColorGrey = 0x0a,
kITEDOSColorDarkGrey = 0x0b,
kITEDOSColorDarkGrey0C = 0x0C,
kITEDOSColorBlack = 0x0f,
kITEDOSColorYellow60 = 0x60,
kITEDOSColorRed = 0x65,
kITEDOSColorDarkBlue8a = 0x8a,
kITEDOSColorBlue89 = 0x89,
kITEDOSColorLightBlue92 = 0x92,
kITEDOSColorBlue = 0x93,
kITEDOSColorLightBlue94 = 0x94,
kITEDOSColorLightBlue96 = 0x96,
kITEDOSColorGreen = 0xba,
// ECS palette
// Constant colors
kITEECSColorTransBlack = 0x00,
kITEECSColorBrightWhite = 0x4f,
kITEECSColorWhite = kITEECSColorBrightWhite,
kITEECSColorBlack = 0x50,
// ECS palette after the palette switch
kITEECSBottomColorGreen = 0x25,
kITEECSBottomColorLightBlue96 = 0x28,
kITEECSBottomColorWhite = 0x2f,
kITEECSBottomColorBrightWhite = 0x2f,
kITEECSBottomColorDarkGrey = 0x32,
kITEECSBottomColorGrey = 0x36,
kITEECSBottomColorBlue = 0x3b,
kITEECSBottomColorYellow60 = 0x3e,
// ECS palette for options
kITEECSOptionsColorLightBlue94 = 0x48,
kITEECSOptionsColorBlue = 0x48,
kITEECSOptionsColorDarkBlue8a = 0x48,
kITEECSOptionsColorLightBlue92 = 0x48,
kITEECSOptionsColorLightBlue96 = 0x48,
kITEECSOptionsColorDarkGrey0C = 0x49,
kITEECSOptionsColorBlack = kITEECSColorBlack,
kITEECSOptionsColorBrightWhite = kITEECSColorBrightWhite,
kITEECSOptionsColorDarkGrey = 0x52,
};
enum KnownColor {
kKnownColorTransparent,
kKnownColorBrightWhite,
kKnownColorWhite,
kKnownColorBlack,
kKnownColorSubtitleTextColor,
kKnownColorSubtitleEffectColorPC98,
kKnownColorVerbText,
kKnownColorVerbTextShadow,
kKnownColorVerbTextActive
};
struct SaveFileData {
char name[SAVE_TITLE_SIZE];
uint slotNumber;
};
struct SaveGameHeader {
uint32 type;
uint32 size;
uint32 version;
char name[SAVE_TITLE_SIZE];
};
inline int objectTypeId(uint16 objectId) {
return objectId >> OBJECT_TYPE_SHIFT;
}
inline int objectIdToIndex(uint16 objectId) {
return OBJECT_TYPE_MASK & objectId;
}
inline uint16 objectIndexToId(int type, int index) {
return (type << OBJECT_TYPE_SHIFT) | (OBJECT_TYPE_MASK & index);
}
class ByteArray : public Common::Array<byte> {
public:
/**
* Return a pointer to the start of the buffer underlying this byte array,
* or NULL if the buffer is empty.
*/
byte *getBuffer() {
return empty() ? NULL : &front();
}
const byte *getBuffer() const {
return empty() ? NULL : &front();
}
void assign(const ByteArray &src) {
resize(src.size());
if (!empty()) {
memcpy(&front(), &src.front(), size());
}
}
ByteArray() : Common::Array<byte>() {}
ByteArray(const byte *array, size_type n) : Common::Array<byte>(array, n) {}
};
class ByteArrayReadStreamEndian : public Common::MemoryReadStreamEndian {
public:
ByteArrayReadStreamEndian(const ByteArray & byteArray, bool bigEndian = false)
: Common::MemoryReadStreamEndian(byteArray.getBuffer(), byteArray.size(), bigEndian),
ReadStreamEndian(bigEndian) {
}
};
class SagaEngine : public Engine {
friend class Scene;
public:
// Engine APIs
Common::Error run() override;
bool hasFeature(EngineFeature f) const override;
void syncSoundSettings() override;
void pauseEngineIntern(bool pause) override;
SagaEngine(OSystem *syst, const SAGAGameDescription *gameDesc);
~SagaEngine() override;
void save(const char *fileName, const char *saveName);
void load(const char *fileName);
uint32 getCurrentLoadVersion() const {
return _saveHeader.version;
}
void fillSaveList();
char *calcSaveFileName(uint slotNumber);
Common::String getSaveStateName(int slot) const override {
return Common::String::format("%s.s%02u", _targetName.c_str(), slot);
}
SaveFileData *getSaveFile(uint idx);
uint getNewSaveSlotNumber() const;
bool locateSaveFile(char *saveName, uint &titleNumber);
bool isSaveListFull() const {
return _saveFilesCount == MAX_SAVES;
}
uint getSaveFilesCount() const {
return isSaveListFull() ? _saveFilesCount : _saveFilesCount + 1;
}
bool isIHNMDemo() const { return _isIHNMDemo; }
bool isITEAmiga() const { return getPlatform() == Common::kPlatformAmiga && getGameId() == GID_ITE; }
bool isAGA() const { return _gameDescription->features & GF_AGA_GRAPHICS; }
bool isECS() const { return _gameDescription->features & GF_ECS_GRAPHICS; }
unsigned getPalNumEntries() const { return isECS() ? 32 : 256; }
GameIntroList getIntroList() const { return _gameDescription->introList; }
int16 _framesEsc;
uint32 _globalFlags;
int16 _ethicsPoints[8];
int _spiritualBarometer;
int _soundVolume;
int _speechVolume;
bool _subtitlesEnabled;
bool _voicesEnabled;
bool _voiceFilesExist;
int _readingSpeed;
bool _copyProtection;
bool _musicWasPlaying;
bool _isIHNMDemo;
bool _hasITESceneSubstitutes;
SndRes *_sndRes;
Sound *_sound;
Music *_music;
Anim *_anim;
Render *_render;
IsoMap *_isoMap;
Gfx *_gfx;
Script *_script;
Actor *_actor;
Font *_font;
Sprite *_sprite;
Scene *_scene;
Interface *_interface;
Console *_console;
Events *_events;
PalAnim *_palanim;
Puzzle *_puzzle;
Resource *_resource;
// Random number generator
Common::RandomSource _rnd;
private:
bool decodeBGImageRLE(const byte *inbuf, size_t inbuf_len, ByteArray &outbuf);
void flipImage(byte *imageBuffer, int columns, int scanlines);
void unbankBGImage(byte *dest_buf, const byte *src_buf, int columns, int scanlines);
uint32 _previousTicks;
public:
bool decodeBGImage(const ByteArray &imageData, ByteArray &outputBuffer, int *w, int *h, bool flip = false);
bool decodeBGImageMask(const ByteArray &imageData, ByteArray &outputBuffer, int *w, int *h, bool flip = false);
const byte *getImagePal(const ByteArray &imageData) {
if (imageData.size() <= SAGA_IMAGE_HEADER_LEN) {
return NULL;
}
return &imageData.front() + SAGA_IMAGE_HEADER_LEN;
}
void loadStrings(StringsTable &stringsTable, const ByteArray &stringsData, bool isBigEndian);
const char *getObjectName(uint16 objectId) const;
public:
int processInput();
Point mousePos() const;
int getMouseClickCount() const {
return _mouseClickCount;
}
void incrementMouseClickCount() {
_mouseClickCount++;
}
void resetMouseClickCount() {
_mouseClickCount = 0;
}
bool leftMouseButtonPressed() const {
return _leftMouseButtonPressed;
}
bool rightMouseButtonPressed() const {
return _rightMouseButtonPressed;
}
bool mouseButtonPressed() const {
return _leftMouseButtonPressed || _rightMouseButtonPressed;
}
inline int ticksToMSec(int tick) const {
return tick * 1000 / kScriptTimeTicksPerSecond;
}
private:
uint _saveFilesCount;
SaveFileData _saveFiles[MAX_SAVES];
SaveGameHeader _saveHeader;
bool _leftMouseButtonPressed;
bool _rightMouseButtonPressed;
int _mouseClickCount;
//current game description
int _gameNumber;
const SAGAGameDescription *_gameDescription;
Common::String _gameTitle;
Common::Rect _displayClip;
public:
int32 _frameCount;
public:
bool initGame();
bool isBigEndian() const;
bool isMacResources() const;
const GameResourceDescription *getResourceDescription() const;
GameResourceList getResourceList() const;
GameFontList getFontList() const;
GamePatchList getPatchList() const;
int getGameId() const;
uint32 getFeatures() const;
Common::Language getLanguage() const;
Common::Platform getPlatform() const;
int getGameNumber() const;
int getStartSceneNumber() const;
const ADGameFileDescription *getFilesDescriptions() const;
const ADGameFileDescription *getArchivesDescriptions() const;
const Common::Rect &getDisplayClip() const { return _displayClip;}
Common::Error loadGameState(int slot) override;
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
const GameDisplayInfo &getDisplayInfo();
int getLanguageIndex();
const char *getTextString(int textStringId);
void getExcuseInfo(int verb, const char *&textString, int &soundResourceId);
void enableKeyMap(int mode);
private:
int _currentPanelMode;
public:
ColorId KnownColor2ColorId(KnownColor knownColor);
void setTalkspeed(int talkspeed);
int getTalkspeed() const;
#define ITE_COLOR_DISPATCHER_TYPE(NAME, TYPE) \
ColorId iteColor ## TYPE ## NAME() const { return isECS() ? kITEECS ## TYPE ## Color ## NAME : kITEDOSColor ## NAME; }
#define ITE_COLOR_DISPATCHER_BOTTOM(NAME) ITE_COLOR_DISPATCHER_TYPE(NAME, Bottom)
#define ITE_COLOR_DISPATCHER_OPTIONS(NAME) ITE_COLOR_DISPATCHER_TYPE(NAME, Options)
#define ITE_COLOR_DISPATCHER(NAME) ITE_COLOR_DISPATCHER_TYPE(NAME, )
ITE_COLOR_DISPATCHER(Black)
ITE_COLOR_DISPATCHER(TransBlack)
ITE_COLOR_DISPATCHER(BrightWhite)
ITE_COLOR_DISPATCHER(White)
ITE_COLOR_DISPATCHER_BOTTOM(DarkGrey)
ITE_COLOR_DISPATCHER_BOTTOM(Blue)
ITE_COLOR_DISPATCHER_BOTTOM(Grey)
ITE_COLOR_DISPATCHER_BOTTOM(White)
ITE_COLOR_DISPATCHER_BOTTOM(BrightWhite)
ITE_COLOR_DISPATCHER_BOTTOM(Green)
ITE_COLOR_DISPATCHER_OPTIONS(DarkGrey)
ITE_COLOR_DISPATCHER_OPTIONS(LightBlue92)
ITE_COLOR_DISPATCHER_OPTIONS(LightBlue94)
ITE_COLOR_DISPATCHER_OPTIONS(LightBlue96)
ITE_COLOR_DISPATCHER_OPTIONS(DarkBlue8a)
ITE_COLOR_DISPATCHER_OPTIONS(DarkGrey0C)
ITE_COLOR_DISPATCHER_OPTIONS(Blue)
ITE_COLOR_DISPATCHER_OPTIONS(BrightWhite)
#undef ITE_COLOR_DISPATCHER
#undef ITE_COLOR_DISPATCHER_BOTTOM
#undef ITE_COLOR_DISPATCHER_OPTIONS
#undef ITE_COLOR_DISPATCHER_TYPE
};
} // End of namespace Saga
#endif

399
engines/saga/saveload.cpp Normal file
View File

@@ -0,0 +1,399 @@
/* 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/savefile.h"
#include "common/system.h"
#include "graphics/thumbnail.h"
#include "saga/saga.h"
#include "saga/actor.h"
#include "saga/events.h"
#include "saga/interface.h"
#include "saga/isomap.h"
#include "saga/music.h"
#include "saga/render.h"
#include "saga/scene.h"
#include "saga/script.h"
#define CURRENT_SAGA_VER 8
namespace Saga {
static SaveFileData emptySlot = {
"", 0
};
char* SagaEngine::calcSaveFileName(uint slotNumber) {
static char name[MAX_FILE_NAME];
Common::sprintf_s(name, "%s.s%02u", _targetName.c_str(), slotNumber);
return name;
}
SaveFileData *SagaEngine::getSaveFile(uint idx) {
if (idx >= MAX_SAVES) {
error("getSaveFileName wrong idx");
}
if (isSaveListFull()) {
return &_saveFiles[_saveFilesCount - idx - 1];
} else {
if (!emptySlot.name[0])
Common::strlcpy(emptySlot.name, getTextString(kTextNewSave), SAVE_TITLE_SIZE);
return (idx == 0) ? &emptySlot : &_saveFiles[_saveFilesCount - idx];
}
}
bool SagaEngine::locateSaveFile(char *saveName, uint &titleNumber) {
uint i;
for (i = 0; i < _saveFilesCount; i++) {
if (strcmp(saveName, _saveFiles[i].name) == 0) {
if (isSaveListFull()) {
titleNumber = _saveFilesCount - i - 1;
} else {
titleNumber = _saveFilesCount - i;
}
return true;
}
}
return false;
}
uint SagaEngine::getNewSaveSlotNumber() const {
uint i, j;
bool found;
for (i = 0; i < MAX_SAVES; i++) {
found = false;
for (j = 0; j < _saveFilesCount; j++) {
if (_saveFiles[j].slotNumber == i) {
found = true;
break;
}
}
if (!found) {
return i;
}
}
error("getNewSaveSlotNumber save list is full");
}
static int compareSaveFileData(const void *a, const void *b) {
const SaveFileData *s1 = (const SaveFileData *)a;
const SaveFileData *s2 = (const SaveFileData *)b;
if (s1->slotNumber < s2->slotNumber) {
return -1;
} else if (s1->slotNumber > s2->slotNumber) {
return 1;
} else {
return 0;
}
}
void SagaEngine::fillSaveList() {
int i;
Common::InSaveFile *in;
Common::StringArray filenames;
char slot[3];
int slotNumber;
char *name;
name = calcSaveFileName(MAX_SAVES);
name[strlen(name) - 2] = '*';
name[strlen(name) - 1] = 0;
filenames = _saveFileMan->listSavefiles(name);
for (i = 0; i < MAX_SAVES; i++) {
_saveFiles[i].name[0] = 0;
_saveFiles[i].slotNumber = (uint)-1;
}
_saveFilesCount = 0;
for (Common::StringArray::iterator file = filenames.begin(); file != filenames.end(); ++file){
//Obtain the last 2 digits of the filename, since they correspond to the save slot
slot[0] = file->c_str()[file->size()-2];
slot[1] = file->c_str()[file->size()-1];
slot[2] = 0;
slotNumber = atoi(slot);
if (slotNumber >= 0 && slotNumber < MAX_SAVES) {
name = calcSaveFileName(slotNumber);
if ((in = _saveFileMan->openForLoading(name)) != nullptr) {
_saveHeader.type = in->readUint32BE();
_saveHeader.size = in->readUint32LE();
_saveHeader.version = in->readUint32LE();
in->read(_saveHeader.name, sizeof(_saveHeader.name));
if (_saveHeader.type != MKTAG('S','A','G','A')) {
warning("SagaEngine::load wrong save %s format", name);
i++;
continue;
}
Common::CodePage cp = Common::kDos850;
if (getGameId() == GID_ITE) {
if (getLanguage() == Common::JA_JPN)
cp = Common::kWindows932;
}
Common::strlcpy(_saveFiles[_saveFilesCount].name, Common::U32String(_saveHeader.name).encode(cp).c_str(), sizeof(_saveFiles[_saveFilesCount].name));
_saveFiles[_saveFilesCount].slotNumber = slotNumber;
delete in;
_saveFilesCount++;
}
}
}
qsort(_saveFiles, _saveFilesCount, sizeof(_saveFiles[0]), compareSaveFileData);
}
void SagaEngine::save(const char *fileName, const char *saveName) {
Common::OutSaveFile *out;
char title[TITLESIZE];
if (!(out = _saveFileMan->openForSaving(fileName))) {
return;
}
_saveHeader.type = MKTAG('S','A','G','A');
_saveHeader.size = 0;
_saveHeader.version = CURRENT_SAGA_VER;
// Note that IHNM has a smaller save title size than ITE
// We allocate the ITE save title size here, to preserve
// savegame backwards compatibility
Common::strlcpy(_saveHeader.name, saveName, SAVE_TITLE_SIZE);
out->writeUint32BE(_saveHeader.type);
out->writeUint32LE(_saveHeader.size);
out->writeUint32LE(_saveHeader.version);
out->write(_saveHeader.name, sizeof(_saveHeader.name));
// Original game title
memset(title, 0, TITLESIZE);
Common::strlcpy(title, _gameTitle.c_str(), TITLESIZE);
out->write(title, TITLESIZE);
// Thumbnail
// First draw scene without save dialog
int oldMode = _interface->getMode();
_render->clearFlag(RF_RENDERPAUSE); // Don't show paused game message in saved thumbnail
_interface->setMode(kPanelMain);
_render->drawScene();
Graphics::saveThumbnail(*out);
_interface->setMode(oldMode);
// Date / time
TimeDate curTime;
_system->getTimeAndDate(curTime);
uint32 saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
uint16 saveTime = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF);
uint32 playTime = g_engine->getTotalPlayTime() / 1000;
out->writeUint32BE(saveDate);
out->writeUint16BE(saveTime);
out->writeUint32BE(playTime);
// Surrounding scene
out->writeSint32LE(_scene->getOutsetSceneNumber());
#ifdef ENABLE_IHNM
if (getGameId() == GID_IHNM) {
out->writeSint32LE(_scene->currentChapterNumber());
out->writeSint32LE(0); // obsolete, was used for the protagonist
out->writeSint32LE(_scene->getCurrentMusicTrack());
out->writeSint32LE(_scene->getCurrentMusicRepeat());
}
#endif
// Inset scene
out->writeSint32LE(_scene->currentSceneNumber());
#ifdef ENABLE_IHNM
if (getGameId() == GID_IHNM) {
out->writeUint32LE(_globalFlags);
for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++)
out->writeSint16LE(_ethicsPoints[i]);
}
#endif
_interface->saveState(out);
_actor->saveState(out);
out->writeSint16LE(_script->_commonBuffer.size());
out->write(_script->_commonBuffer.getBuffer(), _script->_commonBuffer.size());
// ISO map x, y coordinates for ITE
if (getGameId() == GID_ITE) {
out->writeSint16LE(_isoMap->getMapPosition().x);
out->writeSint16LE(_isoMap->getMapPosition().y);
}
out->finalize();
if (out->err())
warning("Can't write file '%s'. (Disk full?)", fileName);
delete out;
_interface->resetSaveReminder();
}
void SagaEngine::load(const char *fileName) {
Common::InSaveFile *in;
int commonBufferSize;
int sceneNumber, insetSceneNumber;
int mapx, mapy;
char title[TITLESIZE];
if (!(in = _saveFileMan->openForLoading(fileName))) {
return;
}
_saveHeader.type = in->readUint32BE();
_saveHeader.size = in->readUint32LE();
_saveHeader.version = in->readUint32LE();
in->read(_saveHeader.name, sizeof(_saveHeader.name));
// Some older saves were not written in an endian safe fashion.
// We try to detect this here by checking for extremely high version values.
// If found, we retry with the data swapped.
if (_saveHeader.version > 0xFFFFFF) {
warning("This savegame is not endian safe, retrying with the data swapped");
_saveHeader.version = SWAP_BYTES_32(_saveHeader.version);
}
debug(2, "Save version: 0x%X", _saveHeader.version);
if (_saveHeader.version < 4)
warning("This savegame is not endian-safe. There may be problems");
if (_saveHeader.type != MKTAG('S','A','G','A')) {
error("SagaEngine::load wrong save game format");
}
if (_saveHeader.version > 4) {
in->read(title, TITLESIZE);
debug(0, "Save is for: %s", title);
}
if (_saveHeader.version >= 6) {
// We don't need the thumbnail here, so just read it and discard it
Graphics::skipThumbnail(*in);
in->readUint32BE(); // save date
in->readUint16BE(); // save time
if (_saveHeader.version >= 8) {
uint32 playTime = in->readUint32BE();
g_engine->setTotalPlayTime(playTime * 1000);
}
}
// Clear pending events here, and don't process queued music events
_events->clearList(false);
// Surrounding scene
sceneNumber = in->readSint32LE();
#ifdef ENABLE_IHNM
if (getGameId() == GID_IHNM) {
int currentChapter = _scene->currentChapterNumber();
_scene->setChapterNumber(in->readSint32LE());
in->skip(4); // obsolete, was used for setting the protagonist
if (_scene->currentChapterNumber() != currentChapter)
_scene->changeScene(-2, 0, kTransitionFade, _scene->currentChapterNumber());
_scene->setCurrentMusicTrack(in->readSint32LE());
_scene->setCurrentMusicRepeat(in->readSint32LE());
_music->stop();
if (_scene->currentChapterNumber() == 8)
_interface->setMode(kPanelChapterSelection);
if (!isIHNMDemo()) {
_music->play(_music->_songTable[_scene->getCurrentMusicTrack()], _scene->getCurrentMusicRepeat() ? MUSIC_LOOP : MUSIC_NORMAL);
} else {
_music->play(3, MUSIC_LOOP);
}
}
#endif
// Inset scene
insetSceneNumber = in->readSint32LE();
#ifdef ENABLE_IHNM
if (getGameId() == GID_IHNM) {
_globalFlags = in->readUint32LE();
for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++)
_ethicsPoints[i] = in->readSint16LE();
}
#endif
_interface->loadState(in);
_actor->loadState(in);
commonBufferSize = in->readSint16LE();
_script->_commonBuffer.resize(commonBufferSize);
in->read(_script->_commonBuffer.getBuffer(), commonBufferSize);
if (getGameId() == GID_ITE) {
mapx = in->readSint16LE();
mapy = in->readSint16LE();
_isoMap->setMapPosition(mapx, mapy);
}
// Note: the mapx, mapy ISO map positions were incorrectly saved
// for IHNM too, which has no ISO map scenes, up to save version 6.
// Since they're at the end of the savegame, we just ignore them
delete in;
// Mute volume to prevent outScene music play
int volume = _music->getVolume();
_music->setVolume(0);
_scene->clearSceneQueue();
_scene->changeScene(sceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade);
_events->handleEvents(0); //dissolve backgrounds
if (insetSceneNumber != sceneNumber) {
_render->setFlag(RF_DISABLE_ACTORS);
_scene->draw();
_render->drawScene();
_render->clearFlag(RF_DISABLE_ACTORS);
_scene->changeScene(insetSceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade);
}
_music->setVolume(volume);
_interface->draw();
// Abort any scene entry protagonist animations and auto-cue speeches.
// Fixes bug #10009.
_actor->abortAllSpeeches();
_actor->_protagonist->_location = _actor->_protagonist->_finalTarget;
_actor->actorEndWalk(ID_PROTAG, true);
}
} // End of namespace Saga

1453
engines/saga/scene.cpp Normal file

File diff suppressed because it is too large Load Diff

393
engines/saga/scene.h Normal file
View File

@@ -0,0 +1,393 @@
/* 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/>.
*
*/
// Scene management module private header file
#ifndef SAGA_SCENE_H
#define SAGA_SCENE_H
#include "saga/font.h"
#include "saga/actor.h"
#include "saga/interface.h"
#include "saga/puzzle.h"
#include "saga/events.h"
// Some defines used for detection.
#include "saga/shared_detection_defines.h"
namespace Saga {
//#define SCENE_DEBUG // for scene debugging
#define SCENE_DOORS_MAX 16
#define NO_CHAPTER_CHANGE -2
// Scenes
#define ITE_SCENE_INV -1
#define ITE_SCENE_PUZZLE 26
#define ITE_SCENE_LODGE 21
#define ITE_SCENE_ENDCREDIT1 295
#define ITE_SCENE_OVERMAP 226
class ObjectMap;
enum SceneFlags {
kSceneFlagISO = 1,
kSceneFlagShowCursor = 2
};
struct BGInfo {
Rect bounds;
byte *buffer;
};
typedef int (SceneProc) (int, void *);
enum SCENE_PROC_PARAMS {
SCENE_BEGIN = 0,
SCENE_END
};
// Resource type numbers
enum SAGAResourceTypes {
SAGA_UNKNOWN,
SAGA_ACTOR,
SAGA_OBJECT,
SAGA_BG_IMAGE,
SAGA_BG_MASK,
SAGA_STRINGS,
SAGA_OBJECT_MAP,
SAGA_ACTION_MAP,
SAGA_ISO_IMAGES,
SAGA_ISO_MAP,
SAGA_ISO_PLATFORMS,
SAGA_ISO_METATILES,
SAGA_ENTRY,
SAGA_ANIM,
SAGA_ISO_MULTI,
SAGA_PAL_ANIM,
SAGA_FACES,
SAGA_PALETTE
};
#define SAGA_RESLIST_ENTRY_LEN 4
struct SceneResourceData {
uint32 resourceId;
int resourceType;
bool invalid;
SceneResourceData() : resourceId(0), resourceType(0), invalid(false) {
}
};
typedef Common::Array<SceneResourceData> SceneResourceDataArray;
#define SAGA_SCENE_DESC_LEN 16
struct SceneDescription {
int16 flags;
int16 resourceListResourceId;
int16 endSlope;
int16 beginSlope;
uint16 scriptModuleNumber;
uint16 sceneScriptEntrypointNumber;
uint16 startScriptEntrypointNumber;
int16 musicResourceId;
void reset() {
flags = resourceListResourceId = endSlope = beginSlope = scriptModuleNumber = sceneScriptEntrypointNumber = startScriptEntrypointNumber = musicResourceId = 0;
}
};
struct SceneEntry {
Location location;
uint16 facing;
};
typedef Common::Array<SceneEntry> SceneEntryList;
struct SceneImage {
bool loaded;
int w;
int h;
int p;
ByteArray buffer;
PalEntry pal[256];
SceneImage() : loaded(false), w(0), h(0), p(0) {
memset(pal, 0, sizeof(pal));
}
};
enum SceneTransitionType {
kTransitionNoFade,
kTransitionFade
};
enum SceneLoadFlags {
kLoadByResourceId = 0,
kLoadBySceneNumber = 1,
kLoadIdTypeMask = 1,
kLoadBgMaskIsImage = 1 << 16,
};
struct LoadSceneParams {
int32 sceneDescriptor;
SceneLoadFlags loadFlag;
SceneProc *sceneProc;
bool sceneSkipTarget;
SceneTransitionType transitionType;
int actorsEntrance;
int chapter;
};
typedef Common::List<LoadSceneParams> SceneQueueList;
///// IHNM-specific stuff
#define IHNM_PALFADE_TIME 1000
#define IHNM_INTRO_FRAMETIME 80
#define IHNM_DGLOGO_TIME 8000
#define IHNM_TITLE_TIME_GM 28750
#define IHNM_TITLE_TIME_FM 19500
#define CREDIT_DURATION1 4000
class Scene {
public:
Scene(SagaEngine *vm);
~Scene();
// Console functions
void cmdActionMapInfo();
void cmdObjectMapInfo();
void cmdSceneChange(int argc, const char **argv);
void startScene();
void creditsScene();
void nextScene();
void skipScene();
void endScene();
void restoreScene();
void queueScene(const LoadSceneParams &sceneQueue) {
_sceneQueue.push_back(sceneQueue);
}
void draw();
int getFlags() const { return _sceneDescription.flags; }
int getScriptModuleNumber() const { return _sceneDescription.scriptModuleNumber; }
bool isInIntro() { return !_inGame; }
const Rect& getSceneClip() const { return _sceneClip; }
void getBGMaskInfo(int &width, int &height, byte *&buffer);
int isBGMaskPresent() { return _bgMask.loaded; }
int getBGMaskType(const Point &testPoint) {
uint offset;
if (!_bgMask.loaded) {
return 0;
}
offset = testPoint.x + testPoint.y * _bgMask.w;
#ifdef SCENE_DEBUG
if (offset >= _bgMask.buf_len) {
error("Scene::getBGMaskType offset 0x%X exceed bufferLength 0x%X", offset, (int)_bgMask.buf_len);
}
#endif
return (_bgMask.buffer[offset] >> 4) & 0x0f;
}
bool validBGMaskPoint(const Point &testPoint) {
#ifdef SCENE_DEBUG
if (!_bgMask.loaded) {
error("Scene::validBGMaskPoint _bgMask not loaded");
}
#endif
return !((testPoint.x < 0) || (testPoint.x >= _bgMask.w) ||
(testPoint.y < 0) || (testPoint.y >= _bgMask.h));
}
bool canWalk(const Point &testPoint);
bool offscreenPath(Point &testPoint);
void setDoorState(int doorNumber, int doorState) {
#ifdef SCENE_DEBUG
if ((doorNumber < 0) || (doorNumber >= SCENE_DOORS_MAX))
error("Scene::setDoorState wrong doorNumber");
#endif
_sceneDoors[doorNumber] = doorState;
}
int getDoorState(int doorNumber) {
#ifdef SCENE_DEBUG
if ((doorNumber < 0) || (doorNumber >= SCENE_DOORS_MAX))
error("Scene::getDoorState wrong doorNumber");
#endif
return _sceneDoors[doorNumber];
}
void initDoorsState();
void getBGInfo(BGInfo &bgInfo);
void getBGPal(PalEntry *&pal) {
pal = (PalEntry *)_bg.pal;
}
void getSlopes(int &beginSlope, int &endSlope);
void clearSceneQueue() {
_sceneQueue.clear();
}
void changeScene(int16 sceneNumber, int actorsEntrance, SceneTransitionType transitionType, int chapter = NO_CHAPTER_CHANGE);
bool isSceneLoaded() const { return _sceneLoaded; }
uint16 getSceneResourceId(int sceneNumber) {
#ifdef SCENE_DEBUG
if ((sceneNumber < 0) || (sceneNumber >= _sceneCount)) {
error("getSceneResourceId: wrong sceneNumber %i", sceneNumber);
}
#endif
return _sceneLUT[sceneNumber];
}
int currentSceneNumber() const { return _sceneNumber; }
int currentChapterNumber() const { return _chapterNumber; }
void setChapterNumber(int ch) { _chapterNumber = ch; }
int getOutsetSceneNumber() const { return _outsetSceneNumber; }
int currentSceneResourceId() const { return _sceneResourceId; }
int getCurrentMusicTrack() const { return _currentMusicTrack; }
void setCurrentMusicTrack(int tr) { _currentMusicTrack = tr; }
int getCurrentMusicRepeat() const { return _currentMusicRepeat; }
void setCurrentMusicRepeat(int rp) { _currentMusicRepeat = rp; }
bool haveChapterPointsChanged() const { return _chapterPointsChanged; }
void setChapterPointsChanged(bool cp) { _chapterPointsChanged = cp; }
void cutawaySkip() {
_vm->_framesEsc = _vm->_scene->isInIntro() ? 2 : 1;
}
void drawTextList();
int getHeight(bool speech = false) const {
if (_vm->getGameId() == GID_IHNM && _vm->_scene->currentChapterNumber() == 8 && !speech)
return _vm->getDisplayInfo().height;
else
return _vm->getDisplayInfo().sceneHeight;
}
void clearPlacard();
void showPsychicProfile(const char *text);
void clearPsychicProfile();
void showIHNMDemoSpecialScreen();
bool isNonInteractiveIHNMDemoPart() {
return _vm->isIHNMDemo() && (_sceneNumber >= 144 && _sceneNumber <= 149);
}
bool isITEPuzzleScene() {
return _vm->getGameId() == GID_ITE && _vm->_puzzle->isActive();
}
private:
void loadScene(LoadSceneParams &loadSceneParams);
void loadSceneDescriptor(uint32 resourceId);
void loadSceneResourceList(uint32 resourceId, SceneResourceDataArray &resourceList);
void loadSceneEntryList(const ByteArray &resourceData);
void processSceneResources(SceneResourceDataArray &resourceList, SceneLoadFlags flags);
void getResourceTypes(SAGAResourceTypes *&types, int &typesCount);
SagaEngine *_vm;
ResourceContext *_sceneContext;
Common::Array<uint16> _sceneLUT;
SceneQueueList _sceneQueue;
bool _sceneLoaded;
int _sceneNumber;
int _chapterNumber;
int _outsetSceneNumber;
int _sceneResourceId;
int _currentMusicTrack;
int _currentMusicRepeat;
bool _chapterPointsChanged;
bool _inGame;
SceneDescription _sceneDescription;
SceneProc *_sceneProc;
SceneImage _bg;
SceneImage _bgMask;
Common::Rect _sceneClip;
int _sceneDoors[SCENE_DOORS_MAX];
public:
ObjectMap *_actionMap;
ObjectMap *_objectMap;
SceneEntryList _entryList;
StringsTable _sceneStrings;
TextList _textList;
private:
int ITEStartProc();
int IHNMStartProc();
int IHNMCreditsProc();
void IHNMLoadCutaways();
bool checkKey();
void fadeMusic();
bool playTitle(int title, int time, int mode = kPanelVideo);
bool playLoopingTitle(int title, int seconds);
public:
static int SC_ITEIntroAnimProc(int param, void *refCon);
static int SC_ITEIntroCave1Proc(int param, void *refCon);
static int SC_ITEIntroCave2Proc(int param, void *refCon);
static int SC_ITEIntroCave3Proc(int param, void *refCon);
static int SC_ITEIntroCave4Proc(int param, void *refCon);
static int SC_ITEIntroValleyProc(int param, void *refCon);
static int SC_ITEIntroTreeHouseProc(int param, void *refCon);
static int SC_ITEIntroFairePathProc(int param, void *refCon);
static int SC_ITEIntroFaireTentProc(int param, void *refCon);
static int SC_ITEIntroCaveDemoProc(int param, void *refCon);
private:
EventColumns *queueIntroDialogue(EventColumns *eventColumns, int n_dialogues, const IntroDialogue dialogue[]);
EventColumns *queueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]);
int ITEIntroAnimProc(int param);
int ITEIntroCaveCommonProc(int param, int caveScene);
int ITEIntroCaveDemoProc(int param);
int ITEIntroValleyProc(int param);
int ITEIntroTreeHouseProc(int param);
int ITEIntroFairePathProc(int param);
int ITEIntroFaireTentProc(int param);
};
} // End of namespace Saga
#endif

1699
engines/saga/script.cpp Normal file

File diff suppressed because it is too large Load Diff

616
engines/saga/script.h Normal file
View File

@@ -0,0 +1,616 @@
/* 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/>.
*
*/
// Scripting module private header
#ifndef SAGA_SCRIPT_H
#define SAGA_SCRIPT_H
#include "common/endian.h"
#include "saga/font.h"
namespace Saga {
#define COMMON_BUFFER_SIZE 1024 // Why 1024?
#define SCRIPT_TBLENTRY_LEN 4
#define SCRIPT_MAX 5000
#define ITE_SCRIPT_FUNCTION_MAX 78
#define IHNM_SCRIPT_FUNCTION_MAX 105
enum AddressTypes {
kAddressCommon = 0, // offset from global variables
kAddressStatic = 1, // offset from global variables
kAddressModule = 2, // offset from start of module
kAddressStack = 3, // offset from stack
kAddressThread = 4 // offset from thread structure
/* kAddressId = 5, // offset from const id object
kAddressIdIndirect = 6, // offset from stack id object
kAddressIndex = 7 // index from id*/
};
enum VerbTypes {
kVerbNone,
kVerbWalkTo,
kVerbGive,
kVerbUse,
kVerbEnter,
kVerbLookAt,
kVerbPickUp,
kVerbOpen,
kVerbClose,
kVerbTalkTo,
kVerbWalkOnly,
kVerbLookOnly,
kVerbOptions
};
#define STHREAD_TIMESLICE 8
enum ThreadVarTypes {
kThreadVarObject = 0,
kThreadVarWithObject = 1,
kThreadVarAction = 2,
kThreadVarActor = 3,
kThreadVarMax = kThreadVarActor + 1
};
enum ThreadFlags {
kTFlagNone = 0,
kTFlagWaiting = 1, // wait for even denoted in waitType
kTFlagFinished = 2,
kTFlagAborted = 4,
kTFlagAsleep = kTFlagWaiting | kTFlagFinished | kTFlagAborted // Combination of all flags which can halt a thread
};
enum ThreadWaitTypes {
kWaitTypeNone = 0, // waiting for nothing
kWaitTypeDelay = 1, // waiting for a timer
kWaitTypeSpeech = 2, // waiting for speech to finish
kWaitTypeDialogEnd = 3, // waiting for my dialog to finish
kWaitTypeDialogBegin = 4, // waiting for other dialog to finish
kWaitTypeWalk = 5, // waiting to finish walking
kWaitTypeRequest = 6, // a request is up
kWaitTypePause = 7,
kWaitTypePlacard = 8,
kWaitTypeStatusTextInput = 9,
kWaitTypeWaitFrames = 10, // IHNM. waiting for a frame count
kWaitTypeWakeUp = 11 // IHNM. wait until get waken up
};
enum CycleFlags {
kCyclePong = 1 << 0,
kCycleOnce = 1 << 1,
kCycleRandom = 1 << 2,
kCycleReverse = 1 << 3
};
enum WalkFlags {
kWalkBackPedal = 1 << 0,
kWalkAsync = 1 << 1,
kWalkUseAngle = 1 << 2,
kWalkFace = 1 << 5
};
enum ReplyFlags {
kReplyOnce = 1 << 0,
kReplySummary = 1 << 1,
kReplyCondition = 1 << 2
};
struct EntryPoint {
uint16 nameOffset;
uint16 offset;
};
typedef Common::Array<uint16> VoiceLUT;
struct ModuleData {
bool loaded; // is it loaded or not?
int scriptResourceId;
int stringsResourceId;
int voicesResourceId;
ByteArray moduleBase; // all base module
uint16 staticSize; // size of static data
uint staticOffset; // offset of static data beginning in _commonBuffer
Common::Array<EntryPoint> entryPoints;
StringsTable strings;
VoiceLUT voiceLUT;
void clear() {
loaded = false;
strings.clear();
voiceLUT.clear();
moduleBase.clear();
entryPoints.clear();
}
ModuleData() : loaded(false), scriptResourceId(0), stringsResourceId(0), voicesResourceId(0), staticSize(0), staticOffset(0) {
}
};
class ScriptThread {
public:
Common::Array<int16> _stackBuf;
uint16 _stackTopIndex = 0;
uint16 _frameIndex = 0;
uint16 _threadVars[kThreadVarMax];
byte *_moduleBase = 0; //
uint16 _moduleBaseSize = 0;
byte *_commonBase = nullptr; //
byte *_staticBase = nullptr; //
VoiceLUT *_voiceLUT = nullptr; //
StringsTable *_strings = nullptr; //
int _flags = 0; // ThreadFlags
int _waitType = 0; // ThreadWaitTypes
uint _sleepTime = 0;
void *_threadObj = nullptr; // which object we're handling
int16 _returnValue = 0;
uint16 _instructionOffset = 0; // Instruction offset
int32 _frameWait = 0;
enum {
THREAD_STACK_SIZE = 256
};
public:
byte *baseAddress(byte addrMode) {
switch (addrMode) {
case kAddressCommon:
return _commonBase;
case kAddressStatic:
return _staticBase;
case kAddressModule:
return _moduleBase;
case kAddressStack:
return (byte *)&_stackBuf[_frameIndex];
case kAddressThread:
return (byte *)_threadVars;
default:
return _commonBase;
}
}
int16 stackTop() {
return _stackBuf[_stackTopIndex];
}
uint pushedSize() {
return THREAD_STACK_SIZE - _stackTopIndex - 2;
}
void push(int16 value) {
if (_stackTopIndex <= 0) {
error("ScriptThread::push() stack overflow");
}
_stackBuf[--_stackTopIndex] = value;
}
int16 pop() {
if (_stackTopIndex >= THREAD_STACK_SIZE) {
error("ScriptThread::pop() stack underflow");
}
return _stackBuf[_stackTopIndex++];
}
// wait stuff
void wait(int waitType) {
_waitType = waitType;
_flags |= kTFlagWaiting;
}
void waitWalk(void *threadObj) {
debug(3, "waitWalk()");
wait(kWaitTypeWalk);
_threadObj = threadObj;
}
void waitDelay(int sleepTime) {
debug(3, "waitDelay(%d)", sleepTime);
wait(kWaitTypeDelay);
_sleepTime = sleepTime;
}
void waitFrames(int frames) {
debug(3, "waitFrames(%d)", frames);
wait(kWaitTypeWaitFrames);
_frameWait = frames;
}
ScriptThread() {
memset(&_frameIndex, 0xFE, sizeof(_frameIndex));
memset(_threadVars, 0xFE, sizeof(_threadVars));
memset(&_waitType, 0xFE, sizeof(_waitType));
memset(&_sleepTime, 0xFE, sizeof(_sleepTime));
memset(&_threadObj, 0xFE, sizeof(_threadObj));
memset(&_returnValue, 0xFE, sizeof(_returnValue));
memset(&_frameWait, 0xFE, sizeof(_frameWait));
_flags = kTFlagNone;
}
};
typedef Common::List<ScriptThread> ScriptThreadList;
#define SCRIPTOP_PARAMS ScriptThread *thread, Common::SeekableReadStream *scriptS, bool &stopParsing, bool &breakOut
#define SCRIPTFUNC_PARAMS ScriptThread *thread, int nArgs, bool &disContinue
#define OPCODE(x) {&Script::x, #x}
class Script {
public:
StringsTable _mainStrings;
Script(SagaEngine *vm);
virtual ~Script();
void loadModule(uint scriptModuleNumber);
void clearModules();
void doVerb();
void showVerb(int statusColor = -1);
void setVerb(int verb);
int getCurrentVerb() const { return _currentVerb; }
void setPointerVerb();
void whichObject(const Point& mousePoint);
void hitObject(bool leftButton);
void playfieldClick(const Point& mousePoint, bool leftButton);
void setLeftButtonVerb(int verb);
int getLeftButtonVerb() const { return _leftButtonVerb; }
void setRightButtonVerb(int verb);
int getRightButtonVerb() const { return _rightButtonVerb; }
void setNonPlayfieldVerb() {
setRightButtonVerb(getVerbType(kVerbNone));
_pointerObject = ID_NOTHING;
_currentObject[_firstObjectSet ? 1 : 0] = ID_NOTHING;
}
void setNoPendingVerb() {
_pendingVerb = getVerbType(kVerbNone);
_currentObject[0] = _currentObject[1] = ID_NOTHING;
setPointerVerb();
}
int getVerbType(VerbTypes verbType);
TextListEntry *getPlacardTextEntry() { return _placardTextEntry; }
bool isNonInteractiveDemo();
protected:
// When reading or writing data to the common buffer, we have to use a
// well-defined byte order since it's stored in savegames. Otherwise,
// we use native byte ordering since that data may be accessed in other
// ways than through these functions.
//
// The "module" area is a special case, which possibly never ever
// happens. But if it does, we need well-defined byte ordering.
uint16 readUint16(byte *addr, byte addrMode) {
switch (addrMode) {
case kAddressCommon:
case kAddressStatic:
case kAddressModule:
return READ_LE_UINT16(addr);
default:
return READ_UINT16(addr);
}
}
void writeUint16(byte *addr, uint16 value, byte addrMode) {
switch (addrMode) {
case kAddressCommon:
case kAddressStatic:
case kAddressModule:
WRITE_LE_UINT16(addr, value);
break;
default:
WRITE_UINT16(addr, value);
break;
}
}
SagaEngine *_vm;
ResourceContext *_scriptContext;
ResourceContext *_dataContext;
uint16 _modulesLUTEntryLen;
Common::Array<ModuleData> _modules;
TextListEntry *_placardTextEntry;
friend class SagaEngine;
ByteArray _commonBuffer;
uint _staticSize;
ScriptThreadList _threadList;
ScriptThread *_conversingThread;
//verb
bool _firstObjectSet;
bool _secondObjectNeeded;
uint16 _currentObject[2];
int16 _currentObjectFlags[2];
int _currentVerb;
int _stickyVerb;
int _leftButtonVerb;
int _rightButtonVerb;
int _ihnmDemoCurrentY;
public:
uint16 _pendingObject[2];
int _pendingVerb;
uint16 _pointerObject;
bool _skipSpeeches;
bool _abortEnabled;
VoiceLUT _globalVoiceLUT;
public:
ScriptThread &createThread(uint16 scriptModuleNumber, uint16 scriptEntryPointNumber);
int executeThread(ScriptThread *thread, int entrypointNumber);
void executeThreads(uint msec);
void completeThread();
void abortAllThreads();
void wakeUpActorThread(int waitType, void *threadObj);
void wakeUpThreads(int waitType);
void wakeUpThreadsDelayed(int waitType, int sleepTime);
void loadVoiceLUT(VoiceLUT &voiceLUT, const ByteArray &resourceData);
protected:
void loadModuleBase(ModuleData &module, const ByteArray &resourceData);
// runThread returns true if we should break running of other threads
bool runThread(ScriptThread &thread);
void setThreadEntrypoint(ScriptThread *thread, int entrypointNumber);
public:
void finishDialog(int strID, int replyID, int flags, int bitOffset);
protected:
// Script opcodes ------------------------------------------------------------
typedef void (Script::*ScriptOpType)(SCRIPTOP_PARAMS);
struct ScriptOpDescription {
ScriptOpType scriptOp;
const char *scriptOpName;
};
const ScriptOpDescription *_scriptOpsList;
void setupScriptOpcodeList();
void opDummy(SCRIPTOP_PARAMS) { warning("Dummy opcode called"); }
void opNextBlock(SCRIPTOP_PARAMS) {
thread->_instructionOffset = (((thread->_instructionOffset) >> 10) + 1) << 10;
}
void opDup(SCRIPTOP_PARAMS);
void opDrop(SCRIPTOP_PARAMS);
void opZero(SCRIPTOP_PARAMS);
void opOne(SCRIPTOP_PARAMS);
void opConstInt(SCRIPTOP_PARAMS);
void opStrLit(SCRIPTOP_PARAMS);
void opGetFlag(SCRIPTOP_PARAMS);
void opGetByte(SCRIPTOP_PARAMS); // SAGA 2
void opGetInt(SCRIPTOP_PARAMS);
void opPutFlag(SCRIPTOP_PARAMS);
void opPutByte(SCRIPTOP_PARAMS); // SAGA 2
void opPutInt(SCRIPTOP_PARAMS);
void opPutFlagV(SCRIPTOP_PARAMS);
void opPutByteV(SCRIPTOP_PARAMS);
void opPutIntV(SCRIPTOP_PARAMS);
void opCall(SCRIPTOP_PARAMS); // SAGA 1
void opCallNear(SCRIPTOP_PARAMS); // SAGA 2
void opCallFar(SCRIPTOP_PARAMS); // SAGA 2
void opCcall(SCRIPTOP_PARAMS);
void opCcallV(SCRIPTOP_PARAMS);
void opCallMember(SCRIPTOP_PARAMS); // SAGA 2
void opCallMemberV(SCRIPTOP_PARAMS); // SAGA 2
void opEnter(SCRIPTOP_PARAMS);
void opReturn(SCRIPTOP_PARAMS);
void opReturnV(SCRIPTOP_PARAMS);
void opJmp(SCRIPTOP_PARAMS);
void opJmpTrueV(SCRIPTOP_PARAMS);
void opJmpFalseV(SCRIPTOP_PARAMS);
void opJmpTrue(SCRIPTOP_PARAMS);
void opJmpFalse(SCRIPTOP_PARAMS);
void opJmpSwitch(SCRIPTOP_PARAMS);
void opJmpRandom(SCRIPTOP_PARAMS);
void opNegate(SCRIPTOP_PARAMS);
void opNot(SCRIPTOP_PARAMS);
void opCompl(SCRIPTOP_PARAMS);
void opIncV(SCRIPTOP_PARAMS);
void opDecV(SCRIPTOP_PARAMS);
void opPostInc(SCRIPTOP_PARAMS);
void opPostDec(SCRIPTOP_PARAMS);
void opAdd(SCRIPTOP_PARAMS);
void opSub(SCRIPTOP_PARAMS);
void opMul(SCRIPTOP_PARAMS);
void opDiv(SCRIPTOP_PARAMS);
void opMod(SCRIPTOP_PARAMS);
void opEq(SCRIPTOP_PARAMS);
void opNe(SCRIPTOP_PARAMS);
void opGt(SCRIPTOP_PARAMS);
void opLt(SCRIPTOP_PARAMS);
void opGe(SCRIPTOP_PARAMS);
void opLe(SCRIPTOP_PARAMS);
void opRsh(SCRIPTOP_PARAMS);
void opLsh(SCRIPTOP_PARAMS);
void opAnd(SCRIPTOP_PARAMS);
void opOr(SCRIPTOP_PARAMS);
void opXor(SCRIPTOP_PARAMS);
void opLAnd(SCRIPTOP_PARAMS);
void opLOr(SCRIPTOP_PARAMS);
void opLXor(SCRIPTOP_PARAMS);
void opSpeak(SCRIPTOP_PARAMS);
void opDialogBegin(SCRIPTOP_PARAMS);
void opDialogEnd(SCRIPTOP_PARAMS);
void opReply(SCRIPTOP_PARAMS);
void opAnimate(SCRIPTOP_PARAMS);
void opJmpSeedRandom(SCRIPTOP_PARAMS);
// Script functions ----------------------------------------------------------
typedef void (Script::*ScriptFunctionType)(SCRIPTFUNC_PARAMS);
struct ScriptFunctionDescription {
ScriptFunctionType scriptFunction;
const char *scriptFunctionName;
};
const ScriptFunctionDescription *_scriptFunctionsList;
void setupITEScriptFuncList();
void setupIHNMScriptFuncList();
void sfPutString(SCRIPTFUNC_PARAMS);
void sfWait(SCRIPTFUNC_PARAMS);
void sfTakeObject(SCRIPTFUNC_PARAMS);
void sfIsCarried(SCRIPTFUNC_PARAMS);
void sfStatusBar(SCRIPTFUNC_PARAMS);
void sfMainMode(SCRIPTFUNC_PARAMS);
void sfScriptWalkTo(SCRIPTFUNC_PARAMS);
void sfScriptDoAction(SCRIPTFUNC_PARAMS);
void sfSetActorFacing(SCRIPTFUNC_PARAMS);
void sfStartBgdAnim(SCRIPTFUNC_PARAMS);
void sfStopBgdAnim(SCRIPTFUNC_PARAMS);
void sfLockUser(SCRIPTFUNC_PARAMS);
void sfPreDialog(SCRIPTFUNC_PARAMS);
void sfKillActorThreads(SCRIPTFUNC_PARAMS);
void sfFaceTowards(SCRIPTFUNC_PARAMS);
void sfSetFollower(SCRIPTFUNC_PARAMS);
void sfScriptGotoScene(SCRIPTFUNC_PARAMS);
void sfSetObjImage(SCRIPTFUNC_PARAMS);
void sfSetObjName(SCRIPTFUNC_PARAMS);
void sfGetObjImage(SCRIPTFUNC_PARAMS);
void sfGetNumber(SCRIPTFUNC_PARAMS);
void sfScriptOpenDoor(SCRIPTFUNC_PARAMS);
void sfScriptCloseDoor(SCRIPTFUNC_PARAMS);
void sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS);
void sfCycleColors(SCRIPTFUNC_PARAMS);
void sfDoCenterActor(SCRIPTFUNC_PARAMS);
void sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS);
void sfScriptWalkToAsync(SCRIPTFUNC_PARAMS);
void sfEnableZone(SCRIPTFUNC_PARAMS);
void sfSetActorState(SCRIPTFUNC_PARAMS);
void sfScriptMoveTo(SCRIPTFUNC_PARAMS);
void sfSceneEq(SCRIPTFUNC_PARAMS);
void sfDropObject(SCRIPTFUNC_PARAMS);
void sfFinishBgdAnim(SCRIPTFUNC_PARAMS);
void sfSwapActors(SCRIPTFUNC_PARAMS);
void sfSimulSpeech(SCRIPTFUNC_PARAMS);
void sfScriptWalk(SCRIPTFUNC_PARAMS);
void sfCycleFrames(SCRIPTFUNC_PARAMS);
void sfSetFrame(SCRIPTFUNC_PARAMS);
void sfSetPortrait(SCRIPTFUNC_PARAMS);
void sfSetProtagPortrait(SCRIPTFUNC_PARAMS);
void sfChainBgdAnim(SCRIPTFUNC_PARAMS);
void sfScriptSpecialWalk(SCRIPTFUNC_PARAMS);
void sfPlaceActor(SCRIPTFUNC_PARAMS);
void sfCheckUserInterrupt(SCRIPTFUNC_PARAMS);
void sfScriptWalkRelative(SCRIPTFUNC_PARAMS);
void sfScriptMoveRelative(SCRIPTFUNC_PARAMS);
void sfSimulSpeech2(SCRIPTFUNC_PARAMS);
void sfPlacard(SCRIPTFUNC_PARAMS);
void sfPlacardOff(SCRIPTFUNC_PARAMS);
void sfSetProtagState(SCRIPTFUNC_PARAMS);
void sfResumeBgdAnim(SCRIPTFUNC_PARAMS);
void sfThrowActor(SCRIPTFUNC_PARAMS);
void sfWaitWalk(SCRIPTFUNC_PARAMS);
void sfScriptSceneID(SCRIPTFUNC_PARAMS);
void sfChangeActorScene(SCRIPTFUNC_PARAMS);
void sfScriptClimb(SCRIPTFUNC_PARAMS);
void sfSetDoorState(SCRIPTFUNC_PARAMS);
void sfSetActorZ(SCRIPTFUNC_PARAMS);
void sfScriptText(SCRIPTFUNC_PARAMS);
void sfGetActorX(SCRIPTFUNC_PARAMS);
void sfGetActorY(SCRIPTFUNC_PARAMS);
void sfEraseDelta(SCRIPTFUNC_PARAMS);
void sfPlayMusic(SCRIPTFUNC_PARAMS);
void sfPickClimbOutPos(SCRIPTFUNC_PARAMS);
void sfTossRif(SCRIPTFUNC_PARAMS);
void sfShowControls(SCRIPTFUNC_PARAMS);
void sfShowMap(SCRIPTFUNC_PARAMS);
void sfPuzzleWon(SCRIPTFUNC_PARAMS);
void sfEnableEscape(SCRIPTFUNC_PARAMS);
void sfPlaySound(SCRIPTFUNC_PARAMS);
void sfPlayLoopedSound(SCRIPTFUNC_PARAMS);
void sfGetDeltaFrame(SCRIPTFUNC_PARAMS);
void sfShowProtect(SCRIPTFUNC_PARAMS);
void sfProtectResult(SCRIPTFUNC_PARAMS);
void sfRand(SCRIPTFUNC_PARAMS);
void sfFadeMusic(SCRIPTFUNC_PARAMS);
void sfScriptStartCutAway(SCRIPTFUNC_PARAMS);
void sfReturnFromCutAway(SCRIPTFUNC_PARAMS);
void sfEndCutAway(SCRIPTFUNC_PARAMS);
void sfGetMouseClicks(SCRIPTFUNC_PARAMS);
void sfResetMouseClicks(SCRIPTFUNC_PARAMS);
void sfWaitFrames(SCRIPTFUNC_PARAMS);
void sfScriptFade(SCRIPTFUNC_PARAMS);
void sfPlayVoice(SCRIPTFUNC_PARAMS);
void sfVstopFX(SCRIPTFUNC_PARAMS);
void sfVstopLoopedFX(SCRIPTFUNC_PARAMS);
void sfDemoIsInteractive(SCRIPTFUNC_PARAMS);
void sfVsetTrack(SCRIPTFUNC_PARAMS);
void sfDebugShowData(SCRIPTFUNC_PARAMS);
void sfNull(SCRIPTFUNC_PARAMS);
void sfWaitFramesEsc(SCRIPTFUNC_PARAMS);
void sfPsychicProfile(SCRIPTFUNC_PARAMS);
void sfPsychicProfileOff(SCRIPTFUNC_PARAMS);
void sfSetSpeechBox(SCRIPTFUNC_PARAMS);
void sfSetChapterPoints(SCRIPTFUNC_PARAMS);
void sfSetPortraitBgColor(SCRIPTFUNC_PARAMS);
void sfScriptStartVideo(SCRIPTFUNC_PARAMS);
void sfScriptReturnFromVideo(SCRIPTFUNC_PARAMS);
void sfScriptEndVideo(SCRIPTFUNC_PARAMS);
void sfShowIHNMDemoHelpBg(SCRIPTFUNC_PARAMS);
void sfAddIHNMDemoHelpTextLine(SCRIPTFUNC_PARAMS);
void sfShowIHNMDemoHelpPage(SCRIPTFUNC_PARAMS);
void sfGetPoints(SCRIPTFUNC_PARAMS);
void sfSetGlobalFlag(SCRIPTFUNC_PARAMS);
void sfDemoSetInteractive(SCRIPTFUNC_PARAMS);
void sfClearGlobalFlag(SCRIPTFUNC_PARAMS);
void sfTestGlobalFlag(SCRIPTFUNC_PARAMS);
void sfSetPoints(SCRIPTFUNC_PARAMS);
void sfQueueMusic(SCRIPTFUNC_PARAMS);
void sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS);
void sfStub(const char *name, ScriptThread *thread, int nArgs);
};
class SAGA1Script : public Script {
public:
SAGA1Script(SagaEngine *vm);
~SAGA1Script() override;
};
} // End of namespace Saga
#endif

1558
engines/saga/sfuncs.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,453 @@
/* 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/>.
*
*/
#ifdef ENABLE_IHNM
// Scripting module script function component
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/actor.h"
#include "saga/animation.h"
#include "saga/console.h"
#include "saga/events.h"
#include "saga/font.h"
#include "saga/interface.h"
#include "saga/music.h"
#include "saga/itedata.h"
#include "saga/puzzle.h"
#include "saga/render.h"
#include "saga/sound.h"
#include "saga/sndres.h"
#include "saga/resource.h"
#include "saga/script.h"
#include "saga/objectmap.h"
#include "saga/scene.h"
#include "saga/isomap.h"
#include "common/config-manager.h"
namespace Saga {
void Script::setupIHNMScriptFuncList() {
static const ScriptFunctionDescription IHNMScriptFunctionsList[IHNM_SCRIPT_FUNCTION_MAX] = {
OPCODE(sfNull),
OPCODE(sfWait),
OPCODE(sfTakeObject),
OPCODE(sfIsCarried),
OPCODE(sfStatusBar),
OPCODE(sfMainMode),
OPCODE(sfScriptWalkTo),
OPCODE(sfScriptDoAction),
OPCODE(sfSetActorFacing),
OPCODE(sfStartBgdAnim),
OPCODE(sfStopBgdAnim),
OPCODE(sfLockUser),
OPCODE(sfPreDialog),
OPCODE(sfKillActorThreads),
OPCODE(sfFaceTowards),
OPCODE(sfSetFollower),
OPCODE(sfScriptGotoScene),
OPCODE(sfSetObjImage),
OPCODE(sfSetObjName),
OPCODE(sfGetObjImage),
OPCODE(sfGetNumber),
OPCODE(sfScriptOpenDoor),
OPCODE(sfScriptCloseDoor),
OPCODE(sfSetBgdAnimSpeed),
OPCODE(sfCycleColors),
OPCODE(sfDoCenterActor),
OPCODE(sfStartBgdAnimSpeed),
OPCODE(sfScriptWalkToAsync),
OPCODE(sfEnableZone),
OPCODE(sfSetActorState),
OPCODE(sfScriptMoveTo),
OPCODE(sfSceneEq),
OPCODE(sfDropObject),
OPCODE(sfFinishBgdAnim),
OPCODE(sfSwapActors),
OPCODE(sfSimulSpeech),
OPCODE(sfScriptWalk),
OPCODE(sfCycleFrames),
OPCODE(sfSetFrame),
OPCODE(sfSetPortrait),
OPCODE(sfSetProtagPortrait),
OPCODE(sfChainBgdAnim),
OPCODE(sfScriptSpecialWalk),
OPCODE(sfPlaceActor),
OPCODE(sfCheckUserInterrupt),
OPCODE(sfScriptWalkRelative),
OPCODE(sfScriptMoveRelative),
OPCODE(sfSimulSpeech2),
OPCODE(sfPsychicProfile),
OPCODE(sfPsychicProfileOff),
OPCODE(sfSetProtagState),
OPCODE(sfResumeBgdAnim),
OPCODE(sfThrowActor),
OPCODE(sfWaitWalk),
OPCODE(sfScriptSceneID),
OPCODE(sfChangeActorScene),
OPCODE(sfScriptClimb),
OPCODE(sfSetDoorState),
OPCODE(sfSetActorZ),
OPCODE(sfScriptText),
OPCODE(sfGetActorX),
OPCODE(sfGetActorY),
OPCODE(sfEraseDelta),
OPCODE(sfPlayMusic),
OPCODE(sfNull),
OPCODE(sfEnableEscape),
OPCODE(sfPlaySound),
OPCODE(sfPlayLoopedSound),
OPCODE(sfGetDeltaFrame),
OPCODE(sfNull),
OPCODE(sfNull),
OPCODE(sfRand),
OPCODE(sfFadeMusic),
OPCODE(sfNull),
OPCODE(sfSetChapterPoints),
OPCODE(sfSetPortraitBgColor),
OPCODE(sfScriptStartCutAway),
OPCODE(sfReturnFromCutAway),
OPCODE(sfEndCutAway),
OPCODE(sfGetMouseClicks),
OPCODE(sfResetMouseClicks),
OPCODE(sfWaitFrames),
OPCODE(sfScriptFade),
OPCODE(sfScriptStartVideo),
OPCODE(sfScriptReturnFromVideo),
OPCODE(sfScriptEndVideo),
OPCODE(sfSetActorZ),
OPCODE(sfShowIHNMDemoHelpBg),
OPCODE(sfAddIHNMDemoHelpTextLine),
OPCODE(sfShowIHNMDemoHelpPage),
OPCODE(sfVstopFX),
OPCODE(sfVstopLoopedFX),
OPCODE(sfDemoSetInteractive), // only used in the demo version of IHNM
OPCODE(sfDemoIsInteractive),
OPCODE(sfVsetTrack),
OPCODE(sfGetPoints),
OPCODE(sfSetGlobalFlag),
OPCODE(sfClearGlobalFlag),
OPCODE(sfTestGlobalFlag),
OPCODE(sfSetPoints),
OPCODE(sfSetSpeechBox),
OPCODE(sfDebugShowData),
OPCODE(sfWaitFramesEsc),
OPCODE(sfQueueMusic),
OPCODE(sfDisableAbortSpeeches)
};
_scriptFunctionsList = IHNMScriptFunctionsList;
}
void Script::sfSetChapterPoints(SCRIPTFUNC_PARAMS) {
int chapter = _vm->_scene->currentChapterNumber();
_vm->_ethicsPoints[chapter] = thread->pop();
int16 barometer = thread->pop();
static PalEntry cur_pal[PAL_ENTRIES];
PalEntry portraitBgColor = _vm->_interface->_portraitBgColor;
byte portraitColor = (_vm->getLanguage() == Common::ES_ESP) ? 253 : 254;
_vm->_spiritualBarometer = _vm->_ethicsPoints[chapter] * 256 / barometer;
_vm->_scene->setChapterPointsChanged(true); // don't save this music when saving in IHNM
// Set the portrait bg color, in case a saved state is restored from the
// launcher. In this case, sfSetPortraitBgColor is not called, thus the
// portrait color will always be 0 (black).
if (portraitBgColor.red == 0 && portraitBgColor.green == 0 && portraitBgColor.blue == 0)
portraitBgColor.green = 255;
if (_vm->_spiritualBarometer > 255)
_vm->_gfx->setPaletteColor(portraitColor, 0xff, 0xff, 0xff);
else
_vm->_gfx->setPaletteColor(portraitColor,
_vm->_spiritualBarometer * portraitBgColor.red / 256,
_vm->_spiritualBarometer * portraitBgColor.green / 256,
_vm->_spiritualBarometer * portraitBgColor.blue / 256);
_vm->_gfx->getCurrentPal(cur_pal);
_vm->_gfx->setPalette(cur_pal);
}
void Script::sfSetPortraitBgColor(SCRIPTFUNC_PARAMS) {
int16 red = thread->pop();
int16 green = thread->pop();
int16 blue = thread->pop();
_vm->_interface->setPortraitBgColor(red, green, blue);
}
void Script::sfScriptStartCutAway(SCRIPTFUNC_PARAMS) {
int16 cut = thread->pop();
thread->pop(); // Not used
int16 fade = thread->pop();
_vm->_anim->setCutAwayMode(kPanelCutaway);
_vm->_anim->playCutaway(cut, fade != 0);
}
void Script::sfReturnFromCutAway(SCRIPTFUNC_PARAMS) {
_vm->_anim->returnFromCutaway();
thread->wait(kWaitTypeWakeUp);
}
void Script::sfEndCutAway(SCRIPTFUNC_PARAMS) {
_vm->_anim->endCutaway();
}
void Script::sfGetMouseClicks(SCRIPTFUNC_PARAMS) {
thread->_returnValue = _vm->getMouseClickCount();
}
void Script::sfResetMouseClicks(SCRIPTFUNC_PARAMS) {
_vm->resetMouseClickCount();
}
void Script::sfWaitFrames(SCRIPTFUNC_PARAMS) {
int16 frames = thread->pop();
if (!_skipSpeeches)
thread->waitFrames(_vm->_frameCount + frames);
}
void Script::sfScriptFade(SCRIPTFUNC_PARAMS) {
int16 firstPalEntry = thread->pop();
int16 lastPalEntry = thread->pop();
int16 startingBrightness = thread->pop();
int16 endingBrightness = thread->pop();
Event event;
static PalEntry cur_pal[PAL_ENTRIES];
_vm->_gfx->getCurrentPal(cur_pal);
event.type = kEvTImmediate;
event.code = kPalEvent;
event.op = kEventPalFade;
event.time = 0;
event.duration = kNormalFadeDuration;
event.data = cur_pal;
event.param = startingBrightness;
event.param2 = endingBrightness;
event.param3 = firstPalEntry;
event.param4 = lastPalEntry - firstPalEntry + 1;
_vm->_events->queue(event);
}
void Script::sfScriptStartVideo(SCRIPTFUNC_PARAMS) {
int16 vid = thread->pop();
int16 fade = thread->pop();
_vm->_anim->setCutAwayMode(kPanelVideo);
_vm->_anim->startVideo(vid, fade != 0);
}
void Script::sfScriptReturnFromVideo(SCRIPTFUNC_PARAMS) {
_vm->_anim->returnFromVideo();
}
void Script::sfScriptEndVideo(SCRIPTFUNC_PARAMS) {
_vm->_anim->endVideo();
}
void Script::sfShowIHNMDemoHelpBg(SCRIPTFUNC_PARAMS) {
_ihnmDemoCurrentY = 0;
_vm->_scene->_textList.clear();
_vm->_interface->setMode(kPanelConverse);
_vm->_scene->showPsychicProfile(NULL);
}
void Script::sfAddIHNMDemoHelpTextLine(SCRIPTFUNC_PARAMS) {
int stringId = thread->pop();
TextListEntry textEntry;
Event event;
textEntry.knownColor = kKnownColorBlack;
textEntry.useRect = true;
textEntry.rect.left = 245;
textEntry.rect.setHeight(210 + 76);
textEntry.rect.setWidth(226);
textEntry.rect.top = 76 + _ihnmDemoCurrentY;
textEntry.font = kKnownFontVerb;
textEntry.flags = (FontEffectFlags)(kFontCentered);
textEntry.text = thread->_strings->getString(stringId);
TextListEntry *_psychicProfileTextEntry = _vm->_scene->_textList.addEntry(textEntry);
event.type = kEvTOneshot;
event.code = kTextEvent;
event.op = kEventDisplay;
event.data = _psychicProfileTextEntry;
_vm->_events->queue(event);
_ihnmDemoCurrentY += _vm->_font->getHeight(kKnownFontVerb, thread->_strings->getString(stringId), 226, kFontCentered);
}
void Script::sfShowIHNMDemoHelpPage(SCRIPTFUNC_PARAMS) {
// Note: The IHNM demo changes panel mode to 8 (kPanelProtect in ITE)
// when changing pages
_vm->_interface->setMode(kPanelPlacard);
_ihnmDemoCurrentY = 0;
}
void Script::sfVstopFX(SCRIPTFUNC_PARAMS) {
_vm->_sound->stopSound();
}
void Script::sfVstopLoopedFX(SCRIPTFUNC_PARAMS) {
_vm->_sound->stopSound();
}
void Script::sfDemoSetInteractive(SCRIPTFUNC_PARAMS) {
if (thread->pop() == 0) {
_vm->_interface->deactivate();
_vm->_interface->setMode(kPanelNull);
}
// Note: the original also sets an appropriate flag here, but we don't,
// as we don't use it
}
void Script::sfDemoIsInteractive(SCRIPTFUNC_PARAMS) {
thread->_returnValue = 0;
}
void Script::sfVsetTrack(SCRIPTFUNC_PARAMS) {
int16 chapter = thread->pop();
int16 sceneNumber = thread->pop();
int16 actorsEntrance = thread->pop();
debug(2, "sfVsetTrrack(%d, %d, %d)", chapter, sceneNumber, actorsEntrance);
_vm->_scene->changeScene(sceneNumber, actorsEntrance, kTransitionFade, chapter);
}
void Script::sfGetPoints(SCRIPTFUNC_PARAMS) {
int16 index = thread->pop();
if (index >= 0 && index < ARRAYSIZE(_vm->_ethicsPoints))
thread->_returnValue = _vm->_ethicsPoints[index];
else
thread->_returnValue = 0;
}
void Script::sfSetGlobalFlag(SCRIPTFUNC_PARAMS) {
int16 flag = thread->pop();
if (flag >= 0 && flag < 32)
_vm->_globalFlags |= (1 << flag);
}
void Script::sfClearGlobalFlag(SCRIPTFUNC_PARAMS) {
int16 flag = thread->pop();
if (flag >= 0 && flag < 32)
_vm->_globalFlags &= ~(1 << flag);
}
void Script::sfTestGlobalFlag(SCRIPTFUNC_PARAMS) {
int16 flag = thread->pop();
if (flag >= 0 && flag < 32 && _vm->_globalFlags & (1 << flag))
thread->_returnValue = 1;
else
thread->_returnValue = 0;
}
void Script::sfSetPoints(SCRIPTFUNC_PARAMS) {
int16 index = thread->pop();
int16 points = thread->pop();
if (index >= 0 && index < ARRAYSIZE(_vm->_ethicsPoints))
_vm->_ethicsPoints[index] = points;
}
void Script::sfSetSpeechBox(SCRIPTFUNC_PARAMS) {
int16 param1 = thread->pop();
int16 param2 = thread->pop();
int16 param3 = thread->pop();
int16 param4 = thread->pop();
_vm->_actor->_speechBoxScript.left = param1;
_vm->_actor->_speechBoxScript.top = param2;
_vm->_actor->_speechBoxScript.setWidth(param3 - param1);
_vm->_actor->_speechBoxScript.setHeight(param4 - param2);
}
void Script::sfDebugShowData(SCRIPTFUNC_PARAMS) {
int16 param = thread->pop();
Common::String buf = Common::String::format("Reached breakpoint %d", param);
_vm->_interface->setStatusText(buf.c_str());
}
void Script::sfWaitFramesEsc(SCRIPTFUNC_PARAMS) {
thread->_returnValue = _vm->_framesEsc;
}
void Script::sfQueueMusic(SCRIPTFUNC_PARAMS) {
int16 param1 = thread->pop();
int16 param2 = thread->pop();
Event event;
if (param1 < 0) {
_vm->_music->stop();
return;
}
if (uint(param1) >= _vm->_music->_songTable.size()) {
warning("sfQueueMusic: Wrong song number (%d > %d)", param1, _vm->_music->_songTable.size() - 1);
} else {
_vm->_music->resetVolume();
_vm->_events->queueMusic(_vm->_music->_songTable[param1], param2, _vm->ticksToMSec(1000));
if (!_vm->_scene->haveChapterPointsChanged()) {
_vm->_scene->setCurrentMusicTrack(param1);
_vm->_scene->setCurrentMusicRepeat(param2);
} else {
// Don't save this music track when saving in IHNM
_vm->_scene->setChapterPointsChanged(false);
}
}
}
void Script::sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS) {
_vm->_interface->disableAbortSpeeches(thread->pop() != 0);
}
void Script::sfPsychicProfile(SCRIPTFUNC_PARAMS) {
thread->wait(kWaitTypePlacard);
_vm->_scene->showPsychicProfile(thread->_strings->getString(thread->pop()));
}
void Script::sfPsychicProfileOff(SCRIPTFUNC_PARAMS) {
// This is called a while after the psychic profile is
// opened, to close it automatically
_vm->_scene->clearPsychicProfile();
}
} // End of namespace Saga
#endif

View File

@@ -0,0 +1,26 @@
/* 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/>.
*
*/
// Default scenes
#define ITE_DEFAULT_SCENE 32
#define IHNM_DEFAULT_SCENE 151
#define ITEDEMO_DEFAULT_SCENE 68
#define IHNMDEMO_DEFAULT_SCENE 144

323
engines/saga/small8.h Normal file
View File

@@ -0,0 +1,323 @@
static byte font_small8[] = {
0x00, 0x08, 0x00, 0x07, 0x00, 0xa0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03,
0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07,
0x00, 0x08, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b,
0x00, 0x0c, 0x00, 0x0d, 0x00, 0x0e, 0x00, 0x0f,
0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13,
0x00, 0x14, 0x00, 0x15, 0x00, 0x16, 0x00, 0x17,
0x00, 0x18, 0x00, 0x19, 0x00, 0x1a, 0x00, 0x1b,
0x00, 0x1c, 0x00, 0x1d, 0x00, 0x1e, 0x00, 0x1f,
0x00, 0x20, 0x00, 0x21, 0x00, 0x22, 0x00, 0x23,
0x00, 0x24, 0x00, 0x25, 0x00, 0x26, 0x00, 0x27,
0x00, 0x28, 0x00, 0x29, 0x00, 0x2a, 0x00, 0x2b,
0x00, 0x2c, 0x00, 0x2d, 0x00, 0x2e, 0x00, 0x2f,
0x00, 0x30, 0x00, 0x31, 0x00, 0x32, 0x00, 0x33,
0x00, 0x34, 0x00, 0x35, 0x00, 0x36, 0x00, 0x37,
0x00, 0x38, 0x00, 0x39, 0x00, 0x3a, 0x00, 0x3b,
0x00, 0x3c, 0x00, 0x3d, 0x00, 0x3e, 0x00, 0x3f,
0x00, 0x40, 0x00, 0x41, 0x00, 0x42, 0x00, 0x43,
0x00, 0x44, 0x00, 0x45, 0x00, 0x46, 0x00, 0x47,
0x00, 0x48, 0x00, 0x49, 0x00, 0x4a, 0x00, 0x4b,
0x00, 0x4c, 0x00, 0x4d, 0x00, 0x4e, 0x00, 0x4f,
0x00, 0x50, 0x00, 0x51, 0x00, 0x52, 0x00, 0x53,
0x00, 0x54, 0x00, 0x55, 0x00, 0x56, 0x00, 0x57,
0x00, 0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x5b,
0x00, 0x5c, 0x00, 0x5d, 0x00, 0x5e, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x66,
0x00, 0x67, 0x00, 0x68, 0x00, 0x69, 0x00, 0x6a,
0x00, 0x6b, 0x00, 0x6c, 0x00, 0x6d, 0x00, 0x6e,
0x00, 0x6f, 0x00, 0x70, 0x00, 0x71, 0x00, 0x72,
0x00, 0x73, 0x00, 0x74, 0x00, 0x75, 0x00, 0x76,
0x00, 0x77, 0x00, 0x78, 0x00, 0x79, 0x00, 0x7a,
0x00, 0x7b, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x7d,
0x00, 0x7e, 0x00, 0x7f, 0x00, 0x80, 0x00, 0x81,
0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83,
0x00, 0x84, 0x00, 0x85, 0x00, 0x86, 0x00, 0x87,
0x00, 0x88, 0x00, 0x89, 0x00, 0x00, 0x00, 0x8a,
0x00, 0x8b, 0x00, 0x8c, 0x00, 0x8d, 0x00, 0x8e,
0x00, 0x8f, 0x00, 0x90, 0x00, 0x91, 0x00, 0x92,
0x00, 0x93, 0x00, 0x94, 0x00, 0x95, 0x00, 0x96,
0x00, 0x97, 0x00, 0x98, 0x00, 0x00, 0x00, 0x99,
0x00, 0x9a, 0x00, 0x9b, 0x00, 0x9c, 0x00, 0x9d,
0x00, 0x9e, 0x00, 0x00, 0x00, 0x9f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x05, 0x05, 0x06, 0x06, 0x03, 0x04, 0x03,
0x04, 0x05, 0x02, 0x05, 0x01, 0x05, 0x05, 0x03,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
0x01, 0x02, 0x03, 0x04, 0x03, 0x05, 0x05, 0x06,
0x06, 0x05, 0x06, 0x06, 0x06, 0x05, 0x06, 0x05,
0x06, 0x07, 0x07, 0x08, 0x07, 0x06, 0x06, 0x06,
0x06, 0x05, 0x05, 0x06, 0x07, 0x08, 0x07, 0x07,
0x07, 0x03, 0x05, 0x03, 0x05, 0x05, 0x03, 0x05,
0x05, 0x04, 0x04, 0x05, 0x05, 0x04, 0x05, 0x01,
0x04, 0x05, 0x02, 0x07, 0x05, 0x04, 0x05, 0x04,
0x04, 0x04, 0x04, 0x05, 0x05, 0x07, 0x06, 0x05,
0x05, 0x05, 0x01, 0x05, 0x04, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09,
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x07, 0x05, 0x05, 0x06,
0x06, 0x05, 0x06, 0x06, 0x06, 0x06, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x07, 0x06,
0x06, 0x06, 0x06, 0x08, 0x00, 0x00, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x07, 0x00, 0x05, 0x05,
0x05, 0x05, 0x02, 0x02, 0x03, 0x03, 0x05, 0x05,
0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x00, 0x05, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02,
0x04, 0x06, 0x06, 0x06, 0x07, 0x04, 0x04, 0x04,
0x05, 0x06, 0x03, 0x05, 0x02, 0x06, 0x06, 0x04,
0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
0x02, 0x03, 0x04, 0x05, 0x04, 0x06, 0x06, 0x06,
0x07, 0x06, 0x07, 0x07, 0x07, 0x06, 0x07, 0x04,
0x07, 0x08, 0x07, 0x09, 0x08, 0x07, 0x07, 0x07,
0x07, 0x06, 0x06, 0x06, 0x06, 0x08, 0x08, 0x08,
0x08, 0x04, 0x06, 0x04, 0x06, 0x06, 0x04, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x02,
0x05, 0x06, 0x03, 0x08, 0x05, 0x05, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x06, 0x09, 0x05, 0x05,
0x06, 0x06, 0x02, 0x06, 0x05, 0x04, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a,
0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x08, 0x06, 0x06, 0x06,
0x06, 0x06, 0x04, 0x04, 0x04, 0x04, 0x06, 0x06,
0x06, 0x06, 0x06, 0x06, 0x06, 0x00, 0x07, 0x06,
0x06, 0x06, 0x06, 0x07, 0x00, 0x00, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x08, 0x00, 0x05, 0x05,
0x05, 0x05, 0x03, 0x03, 0x04, 0x04, 0x05, 0x05,
0x05, 0x05, 0x05, 0x05, 0x05, 0x00, 0x06, 0x05,
0x05, 0x05, 0x05, 0x05, 0x00, 0x05, 0x80, 0xa0,
0x00, 0x70, 0x88, 0x30, 0x20, 0x20, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x70, 0x40, 0x70,
0x70, 0x10, 0xf8, 0x30, 0xf8, 0x70, 0x70, 0x00,
0x00, 0x00, 0x00, 0x00, 0x70, 0x70, 0x70, 0xf8,
0x70, 0xf8, 0xfc, 0xfc, 0x70, 0xc4, 0xe0, 0x1c,
0xc2, 0xc0, 0xc1, 0xc6, 0x78, 0xf8, 0x78, 0xf8,
0x70, 0xf8, 0x98, 0x98, 0x84, 0x82, 0x86, 0xfe,
0xe0, 0x80, 0xe0, 0x20, 0x00, 0x80, 0x00, 0x80,
0x00, 0x10, 0x00, 0x30, 0x00, 0x80, 0x80, 0x10,
0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x18, 0x80, 0xc0, 0x50, 0xa0, 0x80, 0x3e, 0x00,
0xed, 0x80, 0x00, 0x20, 0x40, 0x10, 0x20, 0x28,
0x50, 0x70, 0x7e, 0x70, 0x40, 0x10, 0x20, 0x50,
0x80, 0x20, 0x40, 0xa0, 0x70, 0x50, 0x40, 0x10,
0x20, 0x50, 0x50, 0x74, 0xc0, 0x18, 0x20, 0x50,
0x08, 0xc0, 0x30, 0x20, 0x68, 0x50, 0x60, 0x00,
0xc0, 0x30, 0x20, 0x50, 0x80, 0x40, 0x40, 0xa0,
0x10, 0x50, 0xc0, 0x30, 0x20, 0x50, 0x50, 0x00,
0xc0, 0x30, 0x20, 0x50, 0x30, 0x90, 0x80, 0xa0,
0x50, 0x88, 0x08, 0x50, 0x40, 0x40, 0x40, 0x90,
0x20, 0x00, 0x00, 0x00, 0x08, 0x88, 0xc0, 0x88,
0x88, 0x30, 0x80, 0x40, 0x08, 0x88, 0x88, 0x00,
0x00, 0x20, 0x00, 0x80, 0x88, 0x88, 0x88, 0x44,
0x88, 0x44, 0x44, 0x44, 0x88, 0x44, 0x40, 0x08,
0x44, 0x40, 0x63, 0x62, 0x84, 0x44, 0x84, 0x44,
0x88, 0x20, 0x88, 0x88, 0x82, 0x44, 0x82, 0x04,
0x80, 0x80, 0x20, 0x50, 0x00, 0x40, 0x00, 0x80,
0x00, 0x10, 0x00, 0x40, 0x00, 0x80, 0x00, 0x00,
0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x20, 0x80, 0x20, 0xa0, 0x50, 0x00, 0x41, 0x00,
0x4a, 0x80, 0x70, 0x00, 0x20, 0x20, 0x50, 0x50,
0x00, 0x88, 0x90, 0x88, 0x20, 0x20, 0x50, 0x00,
0x40, 0x40, 0xa0, 0x00, 0x48, 0xa0, 0x20, 0x20,
0x50, 0xa0, 0x00, 0x88, 0x20, 0x20, 0x50, 0x00,
0x10, 0x20, 0x40, 0x50, 0x90, 0x00, 0x90, 0x00,
0x20, 0x40, 0x50, 0x00, 0x40, 0x80, 0xa0, 0x00,
0x78, 0xa0, 0x20, 0x40, 0x50, 0xa0, 0x00, 0x00,
0x20, 0x40, 0x50, 0x00, 0x40, 0x00, 0x80, 0x00,
0xf8, 0x88, 0x10, 0x50, 0x80, 0x80, 0x20, 0x60,
0x20, 0x00, 0x00, 0x00, 0x10, 0x88, 0x40, 0x88,
0x88, 0x50, 0xf0, 0xf0, 0x08, 0x88, 0x88, 0x80,
0x40, 0x40, 0xf0, 0x40, 0x08, 0xb8, 0x88, 0x44,
0x80, 0x44, 0x70, 0x70, 0x80, 0x44, 0x40, 0x08,
0x48, 0x40, 0x55, 0x52, 0x84, 0x78, 0x84, 0x78,
0x80, 0x20, 0x88, 0x88, 0x82, 0x28, 0x44, 0x08,
0x80, 0x40, 0x20, 0x88, 0x00, 0x20, 0x60, 0xe0,
0x60, 0x70, 0x60, 0xe0, 0x70, 0xe0, 0x80, 0x10,
0x98, 0x80, 0x6c, 0xe0, 0x60, 0xe0, 0x70, 0xb0,
0x70, 0xf0, 0x90, 0x88, 0x42, 0x88, 0x90, 0xf8,
0x20, 0x80, 0x20, 0x00, 0xa0, 0x80, 0x9c, 0x80,
0x48, 0x80, 0xf8, 0x20, 0x70, 0x70, 0x70, 0x70,
0x70, 0x70, 0x90, 0x80, 0xf8, 0xf8, 0xf8, 0xf8,
0xe0, 0xe0, 0xe0, 0xe0, 0x48, 0x08, 0x70, 0x70,
0x70, 0x70, 0x70, 0x94, 0x88, 0x88, 0x88, 0x88,
0x84, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x6c,
0x60, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x40,
0x10, 0x00, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78,
0x90, 0x90, 0x00, 0x90, 0x90, 0x90, 0x80, 0x00,
0x50, 0xf0, 0x20, 0xa0, 0x00, 0x80, 0x20, 0xf0,
0xf8, 0x00, 0xf0, 0x00, 0x20, 0x88, 0x40, 0x30,
0x30, 0x90, 0x08, 0x88, 0x10, 0x70, 0x78, 0x00,
0x00, 0x80, 0x00, 0x20, 0x10, 0xb8, 0xf8, 0x78,
0x80, 0x44, 0x40, 0x40, 0x98, 0x7c, 0x40, 0x08,
0x70, 0x40, 0x49, 0x4a, 0x84, 0x40, 0x84, 0x50,
0x70, 0x20, 0x88, 0x50, 0x82, 0x10, 0x28, 0x10,
0x80, 0x20, 0x20, 0x00, 0x00, 0x00, 0x10, 0x90,
0x90, 0x90, 0x90, 0x40, 0x90, 0x90, 0x80, 0x10,
0xa0, 0x80, 0x92, 0x90, 0x90, 0x90, 0x90, 0xc0,
0x80, 0x40, 0x90, 0x88, 0x82, 0x50, 0x90, 0x10,
0xc0, 0x80, 0x18, 0x00, 0x50, 0x80, 0x90, 0x80,
0x00, 0x00, 0xf8, 0x40, 0x88, 0x88, 0x88, 0x88,
0x88, 0x88, 0xfc, 0x80, 0x80, 0x80, 0x80, 0x80,
0x40, 0x40, 0x40, 0x40, 0xe8, 0xc8, 0x88, 0x88,
0x88, 0x88, 0x88, 0xa4, 0x88, 0x88, 0x88, 0x88,
0x44, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x12,
0x90, 0x90, 0x90, 0x90, 0x40, 0x80, 0x40, 0x40,
0x70, 0xe0, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x80, 0x00,
0xf8, 0x88, 0x40, 0x94, 0x00, 0x80, 0x20, 0x60,
0x20, 0x00, 0x00, 0x00, 0x40, 0x88, 0x40, 0x40,
0x08, 0xf8, 0x08, 0x88, 0x20, 0x88, 0x08, 0x80,
0x40, 0x40, 0xf0, 0x40, 0x20, 0x80, 0x88, 0x44,
0x80, 0x44, 0x40, 0x40, 0x88, 0x44, 0x40, 0x08,
0x50, 0x40, 0x41, 0x46, 0x84, 0x40, 0x84, 0x48,
0x08, 0x20, 0x88, 0x50, 0x92, 0x28, 0x10, 0x20,
0x80, 0x10, 0x20, 0x00, 0x00, 0x00, 0x70, 0x90,
0x80, 0x90, 0xf0, 0x40, 0x90, 0x90, 0x80, 0x10,
0xc0, 0x80, 0x92, 0x90, 0x90, 0x90, 0x90, 0x80,
0x60, 0x40, 0x90, 0x50, 0x92, 0x20, 0x90, 0x20,
0x20, 0x80, 0x20, 0x00, 0xa0, 0x80, 0x9c, 0x80,
0x00, 0x00, 0xf8, 0x80, 0xf8, 0xf8, 0xf8, 0xf8,
0xf8, 0xf8, 0x90, 0x80, 0xe0, 0xf0, 0xf0, 0xf0,
0x40, 0x40, 0x40, 0x40, 0x48, 0xa8, 0x88, 0x88,
0x88, 0x88, 0x88, 0xa4, 0x88, 0x88, 0x88, 0x88,
0x28, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x7c,
0xf0, 0xf0, 0xf0, 0xf0, 0x40, 0x80, 0x40, 0x40,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xa8,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x00, 0x00,
0x50, 0x88, 0x80, 0x88, 0x00, 0x40, 0x40, 0x90,
0x20, 0x40, 0x00, 0x00, 0x80, 0x88, 0x40, 0x80,
0x88, 0x10, 0x88, 0x88, 0x40, 0x88, 0x10, 0x00,
0x40, 0x20, 0x00, 0x80, 0x00, 0x80, 0x88, 0x44,
0x80, 0x44, 0x40, 0x40, 0x88, 0x44, 0x40, 0x88,
0x48, 0x44, 0x41, 0x42, 0x84, 0x40, 0x94, 0x44,
0x88, 0x20, 0x98, 0x20, 0x92, 0x44, 0x10, 0x40,
0x80, 0x08, 0x20, 0x00, 0x00, 0x00, 0x90, 0x90,
0x80, 0x90, 0x80, 0x40, 0x70, 0x90, 0x80, 0x10,
0xa0, 0x80, 0x82, 0x90, 0x90, 0xe0, 0x70, 0x80,
0x10, 0x40, 0x90, 0x50, 0x92, 0x50, 0x50, 0x40,
0x20, 0x80, 0x20, 0x00, 0x50, 0x80, 0x41, 0x00,
0x00, 0x00, 0x70, 0x88, 0x88, 0x88, 0x88, 0x88,
0x88, 0x88, 0x90, 0x80, 0x80, 0x80, 0x80, 0x80,
0x40, 0x40, 0x40, 0x40, 0x48, 0x98, 0x88, 0x88,
0x88, 0x88, 0x88, 0x44, 0x98, 0x98, 0x98, 0x98,
0x10, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x80, 0x80, 0x80, 0x80, 0x40, 0x80, 0x40, 0x40,
0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x48,
0x90, 0x90, 0x90, 0x90, 0x50, 0x50, 0x80, 0x00,
0x50, 0x90, 0x88, 0x74, 0x00, 0x20, 0x80, 0x00,
0x00, 0x40, 0x00, 0x80, 0x80, 0x70, 0xe0, 0xf8,
0x70, 0x10, 0x70, 0x70, 0x40, 0x70, 0x60, 0x00,
0x40, 0x00, 0x00, 0x00, 0x20, 0x70, 0x88, 0x78,
0x78, 0x78, 0x7c, 0x40, 0x78, 0x44, 0xe0, 0x70,
0x46, 0x78, 0x43, 0x42, 0x78, 0x40, 0x78, 0x44,
0x70, 0x20, 0x68, 0x20, 0x6c, 0x82, 0x10, 0xfe,
0xe0, 0x08, 0xe0, 0x00, 0xf8, 0x00, 0x70, 0xe0,
0x70, 0x70, 0x70, 0x40, 0x10, 0x90, 0x80, 0x90,
0x98, 0x40, 0x84, 0x90, 0x60, 0x80, 0x10, 0x80,
0xe0, 0x30, 0x70, 0x20, 0x6c, 0x88, 0x20, 0xf8,
0x18, 0x80, 0xc0, 0x00, 0xa0, 0x80, 0x3e, 0x00,
0x00, 0x00, 0x00, 0x70, 0x88, 0x88, 0x88, 0x88,
0x88, 0x88, 0x9e, 0x78, 0xf8, 0xf8, 0xf8, 0xf8,
0xe0, 0xe0, 0xe0, 0xe0, 0x70, 0x88, 0x70, 0x70,
0x70, 0x70, 0x70, 0xb8, 0x68, 0x68, 0x68, 0x68,
0x10, 0x70, 0x70, 0x70, 0x70, 0x70, 0x70, 0x7e,
0x70, 0x70, 0x70, 0x70, 0x40, 0x80, 0x40, 0x40,
0x70, 0x90, 0x60, 0x60, 0x60, 0x60, 0x60, 0xf0,
0x70, 0x70, 0x70, 0x70, 0x20, 0x20, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x10, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0xc0,
};

474
engines/saga/sndres.cpp Normal file
View File

@@ -0,0 +1,474 @@
/* 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/>.
*
*/
// Sound resource management class
#include "saga/saga.h"
#include "saga/itedata.h"
#include "saga/resource.h"
#include "saga/sndres.h"
#include "saga/sound.h"
#include "common/file.h"
#include "common/substream.h"
#include "audio/audiostream.h"
#include "audio/decoders/adpcm.h"
#include "audio/decoders/aiff.h"
#include "audio/decoders/flac.h"
#include "audio/decoders/mac_snd.h"
#include "audio/decoders/mp3.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/voc.h"
#include "audio/decoders/vorbis.h"
#include "audio/decoders/wave.h"
namespace Saga {
#define RID_IHNM_SFX_LUT 265
#define RID_IHNMDEMO_SFX_LUT 222
SndRes::SndRes(SagaEngine *vm) : _vm(vm), _sfxContext(nullptr), _voiceContext(nullptr), _voiceSerial(-1) {
// Load sound module resource file contexts
_sfxContext = _vm->_resource->getContext(GAME_SOUNDFILE);
if (_sfxContext == nullptr) {
warning("SndRes::SndRes resource context not found");
}
setVoiceBank(0);
if (_vm->getGameId() == GID_ITE && _sfxContext) {
_fxTable.resize(ITE_SFXCOUNT);
for (uint i = 0; i < _fxTable.size(); i++) {
_fxTable[i].res = ITE_SfxTable[i].res;
_fxTable[i].vol = ITE_SfxTable[i].vol;
}
#ifdef ENABLE_IHNM
} else if (_vm->getGameId() == GID_IHNM && _sfxContext) {
ResourceContext *resourceContext;
resourceContext = _vm->_resource->getContext(GAME_SOUNDFILE);
if (resourceContext == NULL) {
error("Resource::loadGlobalResources() resource context not found");
}
ByteArray resourceData;
if (_vm->isIHNMDemo()) {
_vm->_resource->loadResource(resourceContext, RID_IHNMDEMO_SFX_LUT, resourceData);
} else {
_vm->_resource->loadResource(resourceContext, RID_IHNM_SFX_LUT, resourceData);
}
if (resourceData.empty()) {
error("Sndres::SndRes can't read SfxIDs table");
}
_fxTableIDs.resize(resourceData.size() / 2);
ByteArrayReadStreamEndian metaS(resourceData);
for (uint i = 0; i < _fxTableIDs.size(); i++) {
_fxTableIDs[i] = metaS.readSint16LE();
}
#endif
}
}
SndRes::~SndRes() {
#ifdef ENABLE_IHNM
if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) {
// Delete the dummy voice context. See setVoiceBank()
delete _voiceContext;
}
#endif
}
void SndRes::setVoiceBank(int serial) {
if (_voiceSerial == serial)
return;
#ifdef ENABLE_IHNM
// If we got the Macintosh version of IHNM, just set the voice bank
// so that we know which voices* subfolder to look for later
if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) {
_voiceSerial = serial;
// Set a dummy voice context
_voiceContext = new VoiceResourceContext_RES();
return;
}
#endif
// If there are no voice files present, don't set the voice bank
if (!_vm->_voiceFilesExist)
return;
// Close previous voice bank file
if (_voiceContext != nullptr) {
_voiceContext->closeFile();
}
_voiceSerial = serial;
_voiceContext = _vm->_resource->getContext(GAME_VOICEFILE, _voiceSerial);
}
void SndRes::playSound(uint32 resourceId, int volume, bool loop) {
SoundBuffer buffer;
debug(4, "SndRes::playSound %i", resourceId);
if (_sfxContext == nullptr)
return;
if (!_sfxContext->validResourceId(resourceId))
return;
if (!load(_sfxContext, resourceId, buffer, false)) {
warning("Failed to load sound");
return;
}
_vm->_sound->playSound(buffer, volume, loop, resourceId);
}
void SndRes::playVoice(uint32 resourceId) {
SoundBuffer buffer;
if (!(_vm->_voiceFilesExist))
return;
if (_vm->getGameId() == GID_IHNM && !(_vm->_voicesEnabled))
return;
debug(4, "SndRes::playVoice %i", resourceId);
if (!load(_voiceContext, resourceId, buffer, false)) {
warning("Failed to load voice");
return;
}
_vm->_sound->playVoice(buffer);
}
enum GameSoundType {
kSoundPCM = 0,
kSoundVOX = 1,
kSoundVOC = 2,
kSoundWAV = 3,
kSoundMP3 = 4,
kSoundOGG = 5,
kSoundFLAC = 6,
kSoundAIFF = 7,
//kSoundShorten = 8, // used in SAGA2
kSoundMacSND = 9,
kSoundPC98 = 10
};
// Use a macro to read in the sound data based on if we actually want to buffer it or not
#define READ_STREAM(streamSize) \
(onlyHeader \
? new Common::SeekableSubReadStream(&readS, readS.pos(), readS.pos() + (streamSize)) \
: readS.readStream(streamSize))
bool SndRes::load(ResourceContext *context, uint32 resourceId, SoundBuffer &buffer, bool onlyHeader) {
size_t soundResourceLength;
bool result = false;
GameSoundType resourceType = kSoundPCM;
int rate = 0, size = 0;
Common::SeekableReadStream *file;
if (context == nullptr)
return false;
if (resourceId == (uint32)-1) {
return false;
}
#ifdef ENABLE_IHNM
//TODO: move to resource_res so we can use normal "getResourceData" and "getFile" methods
if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) {
char soundFileName[40];
int dirIndex = resourceId / 64;
if ((context->fileType() & GAME_VOICEFILE) != 0) {
if (_voiceSerial == 0) {
Common::sprintf_s(soundFileName, "Voices/VoicesS/Voices%d/VoicesS%03x", dirIndex, resourceId);
} else {
Common::sprintf_s(soundFileName, "Voices/Voices%d/Voices%d/Voices%d%03x", _voiceSerial, dirIndex, _voiceSerial, resourceId);
}
} else {
Common::sprintf_s(soundFileName, "SFX/SFX%d/SFX%03x", dirIndex, resourceId);
}
Common::File *actualFile = new Common::File();
actualFile->open(soundFileName);
soundResourceLength = actualFile->size();
file = actualFile;
} else
#endif
{
ResourceData* resourceData = context->getResourceData(resourceId);
file = context->getFile(resourceData);
file->seek(resourceData->offset);
soundResourceLength = resourceData->size;
}
Common::SeekableReadStream &readS = *file;
bool uncompressedSound = false;
if (soundResourceLength >= 8) {
byte header[8];
readS.read(&header, 8);
readS.seek(readS.pos() - 8);
if (!memcmp(header, "Creative", 8)) {
resourceType = kSoundVOC;
} else if (!memcmp(header, "RIFF", 4)) {
resourceType = kSoundWAV;
} else if (!memcmp(header, "FORM", 4)) {
resourceType = kSoundAIFF;
}
// If patch data exists for sound resource 4 (used in ITE intro), don't treat this sound as compressed
// Patch data for this resource is in file p2_a.iaf or p2_a.voc
if (_vm->getGameId() == GID_ITE && resourceId == 4 && context->getResourceData(resourceId)->patchData != nullptr)
uncompressedSound = true;
// FIXME: Currently, the SFX.RES file in IHNM cannot be compressed
if (_vm->getGameId() == GID_IHNM && (context->fileType() & GAME_SOUNDFILE))
uncompressedSound = true;
if (context->isCompressed() && !uncompressedSound) {
if (header[0] == char(0)) {
resourceType = kSoundMP3;
} else if (header[0] == char(1)) {
resourceType = kSoundOGG;
} else if (header[0] == char(2)) {
resourceType = kSoundFLAC;
}
}
}
// Default sound type is 16-bit signed PCM, used in ITE
byte rawFlags = Audio::FLAG_16BITS;
if (_vm->getGameId() == GID_ITE) {
if (context->fileType() & GAME_MACBINARY) {
// ITE Mac has sound in the Mac snd format
resourceType = kSoundMacSND;
} else if (_vm->getPlatform() == Common::kPlatformPC98) {
resourceType = kSoundPC98;
} else if (_vm->getFeatures() & GF_8BIT_UNSIGNED_PCM) { // older ITE demos
rawFlags |= Audio::FLAG_UNSIGNED;
rawFlags &= ~Audio::FLAG_16BITS;
} else if (!uncompressedSound && !scumm_stricmp(context->fileName(), "voicesd.rsc")) {
// Voice files in newer ITE demo versions are OKI ADPCM (VOX) encoded.
resourceType = kSoundVOX;
}
}
buffer.stream = nullptr;
// Check for LE sounds
if (!context->isBigEndian())
rawFlags |= Audio::FLAG_LITTLE_ENDIAN;
switch (resourceType) {
case kSoundPCM: {
// In ITE CD German, some voices are absent and contain just 5 zero bytes.
// Round down to an even number when the audio is 16-bit so makeRawStream
// will accept the data (needs to be an even size for 16-bit data).
// See bug #2123
if ((soundResourceLength & 1) && (rawFlags & Audio::FLAG_16BITS))
soundResourceLength &= ~1;
Audio::SeekableAudioStream *audStream = Audio::makeRawStream(READ_STREAM(soundResourceLength),
_vm->getPlatform() == Common::Platform::kPlatformAmiga ? 11025 : 22050, rawFlags);
buffer.stream = audStream;
buffer.streamLength = audStream->getLength();
result = true;
} break;
case kSoundVOX:
buffer.stream = Audio::makeADPCMStream(READ_STREAM(soundResourceLength), DisposeAfterUse::YES, soundResourceLength, Audio::kADPCMOki, 22050, 1);
buffer.streamLength = Audio::Timestamp(0, soundResourceLength * 2, buffer.stream->getRate());
result = true;
break;
case kSoundMacSND: {
Audio::SeekableAudioStream *audStream = Audio::makeMacSndStream(READ_STREAM(soundResourceLength), DisposeAfterUse::YES);
buffer.stream = audStream;
buffer.streamLength = audStream->getLength();
result = true;
} break;
case kSoundPC98: {
const uint32 clock[] = { 24576000, 16934400 };
const uint16 divide[] = { 3072, 1536, 896, 768, 448, 384, 512, 2560 };
const uint16 ctrlBits[] = { 0x0B, 0x0D, 0x07, 0x02, 0x03, 0x00, 0x01, 0x01 };
uint16 rateIndex = readS.readUint16LE() & 7;
uint16 sfxRate = clock[ctrlBits[rateIndex] & 1] / divide[ctrlBits[rateIndex] >> 1];
readS.seek(-2, SEEK_CUR);
Common::MemoryWriteStreamDynamic cstream(DisposeAfterUse::NO);
for (int srcBytesLeft = soundResourceLength; srcBytesLeft; ) {
uint32 srcPos = readS.pos();
uint16 r = readS.readUint16LE() & 7;
if (r != rateIndex) {
// I am quite optimistic that this won't come up. But let's see...
warning("SndRes::load(): PCM resource with changing playback rates encountered. Currently not implemented.");
}
uncompressedSound = !(readS.readUint16LE() & 1);
uint32 chunkSize = readS.readUint32LE();
if (!chunkSize)
break;
uint8 *chunk = new uint8[chunkSize];
uint8 *dst = chunk;
if (!uncompressedSound) {
for (uint32 i = chunkSize; i;) {
uint8 cnt = readS.readByte();
if (cnt & 0x80) {
cnt &= 0x7F;
uint8 val = readS.readByte();
memset(dst, val, cnt);
} else {
readS.read(dst, cnt);
}
dst += cnt;
i -= cnt;
}
} else {
readS.read(dst, chunkSize);
}
cstream.write(chunk, chunkSize);
srcBytesLeft -= (readS.pos() - srcPos);
delete[] chunk;
}
cstream.finalize();
Audio::SeekableAudioStream *audStream = Audio::makeRawStream(cstream.getData(), cstream.size(), sfxRate, 0, DisposeAfterUse::YES);
buffer.stream = audStream;
buffer.streamLength = audStream->getLength();
result = true;
} break;
case kSoundAIFF: {
Audio::RewindableAudioStream *audStream = Audio::makeAIFFStream(READ_STREAM(soundResourceLength), DisposeAfterUse::YES);
Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(audStream);
if (!seekStream) {
warning("AIFF file is not seekable");
delete audStream;
result = false;
break;
}
buffer.stream = seekStream;
buffer.streamLength = seekStream->getLength();
result = true;
} break;
case kSoundVOC: {
Audio::SeekableAudioStream *audStream = Audio::makeVOCStream(READ_STREAM(soundResourceLength), Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
buffer.stream = audStream;
buffer.streamLength = audStream->getLength();
result = true;
} break;
case kSoundWAV:
result = Audio::loadWAVFromStream(readS, size, rate, rawFlags);
if (result) {
Audio::SeekableAudioStream *audStream = Audio::makeRawStream(READ_STREAM(size), rate, rawFlags);
buffer.stream = audStream;
buffer.streamLength = audStream->getLength();
}
break;
case kSoundMP3:
case kSoundOGG:
case kSoundFLAC: {
readS.skip(9); // skip sfx header
Audio::SeekableAudioStream *audStream = nullptr;
Common::SeekableReadStream *memStream = READ_STREAM(soundResourceLength - 9);
if (resourceType == kSoundMP3) {
#ifdef USE_MAD
audStream = Audio::makeMP3Stream(memStream, DisposeAfterUse::YES);
#endif
} else if (resourceType == kSoundOGG) {
#ifdef USE_VORBIS
audStream = Audio::makeVorbisStream(memStream, DisposeAfterUse::YES);
#endif
} else /* if (resourceType == kSoundFLAC) */ {
#ifdef USE_FLAC
audStream = Audio::makeFLACStream(memStream, DisposeAfterUse::YES);
#endif
}
if (audStream) {
buffer.stream = audStream;
buffer.streamLength = audStream->getLength();
result = true;
} else {
delete memStream;
}
} break;
default:
error("SndRes::load Unknown sound type");
}
if (_vm->getGameId() == GID_IHNM && _vm->isMacResources()) {
delete file;
}
if (onlyHeader) {
delete buffer.stream;
buffer.stream = nullptr;
}
return result;
}
#undef READ_STREAM
int SndRes::getVoiceLength(uint32 resourceId) {
SoundBuffer buffer;
if (!(_vm->_voiceFilesExist))
return -1;
if (!load(_voiceContext, resourceId, buffer, true))
return -1;
return buffer.streamLength.msecs();
}
} // End of namespace Saga

66
engines/saga/sndres.h Normal file
View File

@@ -0,0 +1,66 @@
/* 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/>.
*
*/
// Sound resource class header
#ifndef SAGA_SNDRES_H
#define SAGA_SNDRES_H
#include "saga/itedata.h"
#include "saga/sound.h"
namespace Saga {
struct FxTable {
int16 res;
int16 vol;
};
class SndRes {
public:
SndRes(SagaEngine *vm);
~SndRes();
void playSound(uint32 resourceId, int volume, bool loop);
void playVoice(uint32 resourceId);
int getVoiceLength(uint32 resourceId);
void setVoiceBank(int serial);
int getVoiceBank() { return _voiceSerial; }
Common::Array<FxTable> _fxTable;
Common::Array<int16> _fxTableIDs;
private:
bool load(ResourceContext *context, uint32 resourceId, SoundBuffer &buffer, bool onlyHeader);
ResourceContext *_sfxContext;
ResourceContext *_voiceContext;
int _voiceSerial; // voice bank number
SagaEngine *_vm;
};
} // End of namespace Saga
#endif

151
engines/saga/sound.cpp Normal file
View File

@@ -0,0 +1,151 @@
/* 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 "saga/saga.h"
#include "saga/sound.h"
#include "audio/audiostream.h"
#include "audio/mixer.h"
namespace Saga {
Sound::Sound(SagaEngine *vm, Audio::Mixer *mixer) :
_vm(vm), _mixer(mixer) {
for (int i = 0; i < SOUND_HANDLES; i++)
_handles[i].type = kFreeHandle;
setVolume();
}
Sound::~Sound() {
}
SndHandle *Sound::getHandle() {
for (int i = 0; i < SOUND_HANDLES; i++) {
if (_handles[i].type == kFreeHandle)
return &_handles[i];
if (!_mixer->isSoundHandleActive(_handles[i].handle)) {
_handles[i].type = kFreeHandle;
return &_handles[i];
}
}
error("Sound::getHandle(): Too many sound handles");
return nullptr; // for compilers that don't support NORETURN
}
void Sound::playSoundBuffer(Audio::SoundHandle *handle, const SoundBuffer &buffer, int volume,
sndHandleType handleType, bool loop) {
Audio::Mixer::SoundType soundType = (handleType == kVoiceHandle) ?
Audio::Mixer::kSpeechSoundType : Audio::Mixer::kSFXSoundType;
if (buffer.stream)
_mixer->playStream(soundType, handle, Audio::makeLoopingAudioStream(buffer.stream, loop ? 0 : 1), -1, volume);
}
void Sound::playSound(SoundBuffer &buffer, int volume, bool loop, int resId) {
// WORKAROUND
// Prevent playing same looped sound for several times
// Fixes bug #4686: "ITE: Cumulative Snoring sounds in Prince's Bedroom"
for (int i = 0; i < SOUND_HANDLES; i++)
if (_handles[i].type == kEffectHandle && _handles[i].resId == resId) {
debug(1, "Skipped playing SFX #%d", resId);
return;
}
SndHandle *handle = getHandle();
handle->type = kEffectHandle;
handle->resId = resId;
playSoundBuffer(&handle->handle, buffer, 2 * volume, handle->type, loop);
}
void Sound::pauseSound() {
for (int i = 0; i < SOUND_HANDLES; i++)
if (_handles[i].type == kEffectHandle)
_mixer->pauseHandle(_handles[i].handle, true);
}
void Sound::resumeSound() {
for (int i = 0; i < SOUND_HANDLES; i++)
if (_handles[i].type == kEffectHandle)
_mixer->pauseHandle(_handles[i].handle, false);
}
void Sound::stopSound() {
for (int i = 0; i < SOUND_HANDLES; i++)
if (_handles[i].type == kEffectHandle) {
_mixer->stopHandle(_handles[i].handle);
_handles[i].type = kFreeHandle;
_handles[i].resId = -1;
}
}
void Sound::playVoice(SoundBuffer &buffer) {
SndHandle *handle = getHandle();
handle->type = kVoiceHandle;
playSoundBuffer(&handle->handle, buffer, 255, handle->type, false);
}
void Sound::pauseVoice() {
for (int i = 0; i < SOUND_HANDLES; i++)
if (_handles[i].type == kVoiceHandle)
_mixer->pauseHandle(_handles[i].handle, true);
}
void Sound::resumeVoice() {
for (int i = 0; i < SOUND_HANDLES; i++)
if (_handles[i].type == kVoiceHandle)
_mixer->pauseHandle(_handles[i].handle, false);
}
void Sound::stopVoice() {
for (int i = 0; i < SOUND_HANDLES; i++)
if (_handles[i].type == kVoiceHandle) {
_mixer->stopHandle(_handles[i].handle);
_handles[i].type = kFreeHandle;
}
}
void Sound::stopAll() {
stopVoice();
stopSound();
}
void Sound::setVolume() {
bool mute = false;
if (ConfMan.hasKey("mute"))
mute = ConfMan.getBool("mute");
_vm->_soundVolume = mute ? 0 : ConfMan.getInt("sfx_volume");
_vm->_speechVolume = mute ? 0 : ConfMan.getInt("speech_volume");
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, _vm->_soundVolume);
_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, _vm->_speechVolume);
}
} // End of namespace Saga

95
engines/saga/sound.h Normal file
View File

@@ -0,0 +1,95 @@
/* 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/>.
*
*/
// Sound class
#ifndef SAGA_SOUND_H
#define SAGA_SOUND_H
#include "common/file.h"
#include "audio/mixer.h"
#include "audio/timestamp.h"
namespace Audio {
class RewindableAudioStream;
}
namespace Saga {
#define SOUND_HANDLES 10
enum SOUND_FLAGS {
SOUND_LOOP = 1
};
struct SoundBuffer {
Audio::RewindableAudioStream *stream;
Audio::Timestamp streamLength;
};
enum sndHandleType {
kFreeHandle,
kEffectHandle,
kVoiceHandle
};
struct SndHandle {
Audio::SoundHandle handle;
sndHandleType type;
int resId;
};
class Sound {
public:
Sound(SagaEngine *vm, Audio::Mixer *mixer);
~Sound();
void playSound(SoundBuffer &buffer, int volume, bool loop, int resId);
void pauseSound();
void resumeSound();
void stopSound();
void playVoice(SoundBuffer &buffer);
void pauseVoice();
void resumeVoice();
void stopVoice();
void stopAll();
void setVolume();
private:
void playSoundBuffer(Audio::SoundHandle *handle, const SoundBuffer &buffer, int volume,
sndHandleType handleType, bool loop);
SndHandle *getHandle();
SagaEngine *_vm;
Audio::Mixer *_mixer;
SndHandle _handles[SOUND_HANDLES];
};
} // End of namespace Saga
#endif

582
engines/saga/sprite.cpp Normal file
View File

@@ -0,0 +1,582 @@
/* 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/>.
*
*/
// Sprite management module
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/scene.h"
#include "saga/resource.h"
#include "saga/font.h"
#include "saga/sprite.h"
#include "saga/render.h"
namespace Saga {
namespace {
template<int bitsPerPixel, int bitsPerEntry>
bool blitAmigaSprite(byte *outBuf, int outPitch, const byte *inputBuffer, size_t inLength, int width, int height) {
byte *outPointer = outBuf;
int c;
int widthAligned = (width + 15) & ~15;
const byte *ptr = inputBuffer, *end = inputBuffer + inLength;
for (int bitOut = 0; bitOut < bitsPerPixel; bitOut++)
for (int x = 0; x < widthAligned; x += bitsPerEntry) {
for (int y = 0; y < height; ) {
int bg_runcount = bitsPerEntry == 16 ? READ_BE_UINT16(ptr) : *ptr;
ptr += bitsPerEntry / 8;
if (ptr >= end)
return true;
// Transparent
y += bg_runcount;
if (y > height) {
warning("Sprite height overrun in transparent run: coord=%d+%dx%d, size=%dx%d, pos=%d",
x, bitOut, y, width, height, (int)(ptr - inputBuffer));
return true;
}
if (y == height) {
continue;
}
int fg_runcount = bitsPerEntry == 16 ? READ_BE_UINT16(ptr) : *ptr;
ptr += bitsPerEntry / 8;
if (ptr >= end)
return true;
for (c = 0; c < fg_runcount && ptr < end; c++, y++) {
uint16 val = bitsPerEntry == 16 ? READ_BE_UINT16(ptr) : *ptr;
ptr += bitsPerEntry / 8;
if (y >= height) {
warning("Sprite height overrun in opaque run: coord=%d+%dx%d, size=%dx%d, pos=%d",
x, bitOut, y, width, height, (int)(ptr - inputBuffer));
return false;
}
for (int bitIn = 0; bitIn < bitsPerEntry; bitIn++) {
int realX = x + (bitsPerEntry - 1 - bitIn);
if (realX >= width) {
continue;
}
outPointer[y * outPitch + realX] =
(outPointer[y * outPitch + realX] & ~(1 << bitOut)) | (((val >> bitIn) & 1) << bitOut);
}
}
if (fg_runcount == 0 && bg_runcount == 0) {
warning("Sprite zero-sized run: coord=%d+%dx%d, size=%dx%d, pos=%d",
x, bitOut, y, width, height, (int)(ptr - inputBuffer));
return false;
}
}
}
return true;
}
}
#define RID_IHNM_ARROW_SPRITES 13
#define RID_IHNM_SAVEREMINDER_SPRITES 14
#define RID_IHNMDEMO_ARROW_SPRITES 8
#define RID_IHNMDEMO_SAVEREMINDER_SPRITES 9
Sprite::Sprite(SagaEngine *vm) : _vm(vm) {
debug(8, "Initializing sprite subsystem...");
// Load sprite module resource context
_spriteContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
if (_spriteContext == nullptr) {
error("Sprite::Sprite resource context not found");
}
if (_vm->getGameId() == GID_ITE) {
loadList(_vm->getResourceDescription()->mainSpritesResourceId, _mainSprites);
_arrowSprites = _saveReminderSprites = _inventorySprites = _mainSprites;
#ifdef ENABLE_IHNM
} else if (_vm->getGameId() == GID_IHNM) {
if (_vm->isIHNMDemo()) {
loadList(RID_IHNMDEMO_ARROW_SPRITES, _arrowSprites);
loadList(RID_IHNMDEMO_SAVEREMINDER_SPRITES, _saveReminderSprites);
} else {
loadList(RID_IHNM_ARROW_SPRITES, _arrowSprites);
loadList(RID_IHNM_SAVEREMINDER_SPRITES, _saveReminderSprites);
}
#endif
} else {
error("Sprite: unknown game type");
}
}
Sprite::~Sprite() {
debug(8, "Shutting down sprite subsystem...");
}
void Sprite::loadList(int resourceId, SpriteList &spriteList, byte keepMask) {
ByteArray spriteListData;
_vm->_resource->loadResource(_spriteContext, resourceId, spriteListData);
if (spriteListData.empty()) {
return;
}
ByteArrayReadStreamEndian readS(spriteListData, _spriteContext->isBigEndian());
uint16 spriteCount = readS.readUint16();
debug(9, "Sprites: %d", spriteCount);
uint16 oldSpriteCount = spriteList.size();
uint16 newSpriteCount = oldSpriteCount + spriteCount;
spriteList.resize(newSpriteCount);
bool bigHeader = _vm->getGameId() == GID_IHNM || _vm->isMacResources();
for (uint i = oldSpriteCount; i < spriteList.size(); i++) {
uint32 offset;
if (bigHeader || _vm->isITEAmiga())
offset = readS.readUint32();
else
offset = readS.readUint16();
if (offset >= spriteListData.size()) {
// ITE Mac demos throw this warning
warning("Sprite::loadList offset exceeded");
spriteList.resize(i);
return;
}
const byte *spritePointer = spriteListData.getBuffer();
spritePointer += offset;
const byte *spriteDataPointer;
SpriteInfo *spriteInfo = &spriteList[i];
if (bigHeader) {
Common::MemoryReadStreamEndian readS2(spritePointer, 8, _spriteContext->isBigEndian());
spriteInfo->xAlign = readS2.readSint16();
spriteInfo->yAlign = readS2.readSint16();
spriteInfo->width = readS2.readUint16();
spriteInfo->height = readS2.readUint16();
spriteDataPointer = spritePointer + readS2.pos();
} else {
Common::MemoryReadStreamEndian readS2(spritePointer, 4, false);
spriteInfo->xAlign = readS2.readSByte();
spriteInfo->yAlign = readS2.readSByte();
spriteInfo->width = readS2.readByte();
spriteInfo->height = readS2.readByte();
spriteDataPointer = spritePointer + readS2.pos();
}
int outputLength = spriteInfo->width * spriteInfo->height;
int inputLength = spriteListData.size() - (spriteDataPointer - spriteListData.getBuffer());
spriteInfo->decodedBuffer.resize(outputLength);
if (outputLength > 0) {
if (_vm->isAGA() || _vm->isECS()) {
_decodeBuf.resize(spriteInfo->width * spriteInfo->height);
memset(&_decodeBuf.front(), 0, _decodeBuf.size());
if (_vm->isAGA())
blitAmigaSprite<8, 16>(&_decodeBuf.front(), spriteInfo->width, spriteDataPointer, inputLength, spriteInfo->width, spriteInfo->height);
else
blitAmigaSprite<4, 8>(&_decodeBuf.front(), spriteInfo->width, spriteDataPointer, inputLength, spriteInfo->width, spriteInfo->height);
} else
decodeRLEBuffer(spriteDataPointer, inputLength, outputLength);
spriteInfo->keepMask = keepMask;
byte *dst = &spriteInfo->decodedBuffer.front();
#ifdef ENABLE_IHNM
// IHNM sprites are upside-down, for reasons which i can only
// assume are perverse. To simplify things, flip them now. Not
// at drawing time.
if (_vm->getGameId() == GID_IHNM) {
byte *src = &_decodeBuf[spriteInfo->width * (spriteInfo->height - 1)];
for (int j = 0; j < spriteInfo->height; j++) {
memcpy(dst, src, spriteInfo->width);
src -= spriteInfo->width;
dst += spriteInfo->width;
}
} else
#endif
memcpy(dst, &_decodeBuf.front(), outputLength);
}
}
}
void Sprite::getScaledSpriteBuffer(SpriteList &spriteList, uint spriteNumber, int scale, int &width, int &height, int &xAlign, int &yAlign, const byte *&buffer) {
SpriteInfo *spriteInfo;
if (spriteList.size() <= spriteNumber) {
// this can occur in IHNM while loading a saved game from chapter 1-5 when being in the end chapter
warning("spriteList.size() <= spriteNumber");
return;
}
spriteInfo = &spriteList[spriteNumber];
if (scale < 256) {
xAlign = (spriteInfo->xAlign * scale) >> 8; //TODO: do we need to take in account sprite x&y aligns ?
yAlign = (spriteInfo->yAlign * scale) >> 8; // ????
height = (spriteInfo->height * scale + 0x7f) >> 8;
width = (spriteInfo->width * scale + 0x7f) >> 8;
size_t outLength = width * height;
if (outLength > 0) {
scaleBuffer(&spriteInfo->decodedBuffer.front(), spriteInfo->width, spriteInfo->height, scale, outLength);
buffer = &_decodeBuf.front();
} else {
buffer = nullptr;
}
} else {
xAlign = spriteInfo->xAlign;
yAlign = spriteInfo->yAlign;
height = spriteInfo->height;
width = spriteInfo->width;
buffer = spriteInfo->decodedBuffer.getBuffer();
}
}
void Sprite::drawClip(const Point &spritePointer, int width, int height, const byte *spriteBuffer, bool clipToScene, byte keepMask) {
Common::Rect clipRect = clipToScene ? _vm->_scene->getSceneClip() : _vm->getDisplayClip();
int xDstOffset, yDstOffset, xSrcOffset, ySrcOffset, xDiff, yDiff, cWidth, cHeight;
byte *bufRowPointer;
byte *bufPointer;
const byte *srcRowPointer;
const byte *srcPointer;
int backBufferPitch = _vm->_gfx->getBackBufferPitch();
//find Rects intersection
yDiff = clipRect.top - spritePointer.y;
if (yDiff > 0) {
ySrcOffset = yDiff;
yDstOffset = clipRect.top;
cHeight = height - yDiff;
} else {
ySrcOffset = 0;
yDstOffset = spritePointer.y;
cHeight = height;
}
xDiff = clipRect.left - spritePointer.x;
if (xDiff > 0) {
xSrcOffset = xDiff;
xDstOffset = clipRect.left;
cWidth = width - xDiff;
} else {
xSrcOffset = 0;
xDstOffset = spritePointer.x;
cWidth = width;
}
yDiff = yDstOffset + cHeight - clipRect.bottom;
if (yDiff > 0) {
cHeight -= yDiff;
}
xDiff = xDstOffset + cWidth - clipRect.right;
if (xDiff > 0) {
cWidth -= xDiff;
}
if ((cHeight <= 0) || (cWidth <= 0)) {
//no intersection
return;
}
bufRowPointer = _vm->_gfx->getBackBufferPixels() + backBufferPitch * yDstOffset + xDstOffset;
srcRowPointer = spriteBuffer + width * ySrcOffset + xSrcOffset;
// validate src, dst buffers
assert(_vm->_gfx->getBackBufferPixels() <= bufRowPointer);
assert((_vm->_gfx->getBackBufferPixels() + (_vm->getDisplayInfo().width * _vm->getDisplayInfo().height)) >=
(byte *)(bufRowPointer + backBufferPitch * (cHeight - 1) + cWidth));
assert((const byte *)spriteBuffer <= srcRowPointer);
assert(((const byte *)spriteBuffer + (width * height)) >= (const byte *)(srcRowPointer + width * (cHeight - 1) + cWidth));
const byte *srcPointerFinish2 = srcRowPointer + width * cHeight;
if (keepMask) {
for (;;) {
srcPointer = srcRowPointer;
bufPointer = bufRowPointer;
const byte *srcPointerFinish = srcRowPointer + cWidth;
for (;;) {
if (*srcPointer != 0) {
*bufPointer = *srcPointer | (*bufPointer & keepMask);
}
srcPointer++;
bufPointer++;
if (srcPointer == srcPointerFinish) {
break;
}
}
srcRowPointer += width;
if (srcRowPointer == srcPointerFinish2) {
break;
}
bufRowPointer += backBufferPitch;
}
} else {
for (;;) {
srcPointer = srcRowPointer;
bufPointer = bufRowPointer;
const byte *srcPointerFinish = srcRowPointer + cWidth;
for (;;) {
if (*srcPointer != 0) {
*bufPointer = *srcPointer;
}
srcPointer++;
bufPointer++;
if (srcPointer == srcPointerFinish) {
break;
}
}
srcRowPointer += width;
if (srcRowPointer == srcPointerFinish2) {
break;
}
bufRowPointer += backBufferPitch;
}
}
_vm->_render->addDirtyRect(Common::Rect(xDstOffset, yDstOffset, xDstOffset + cWidth, yDstOffset + cHeight));
}
void Sprite::draw(SpriteList &spriteList, uint spriteNumber, const Point &screenCoord, int scale, bool clipToScene) {
const byte *spriteBuffer = nullptr;
int width = 0;
int height = 0;
int xAlign = 0;
int yAlign = 0;
Point spritePointer;
getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
spritePointer.x = screenCoord.x + xAlign;
spritePointer.y = screenCoord.y + yAlign;
drawClip(spritePointer, width, height, spriteBuffer, clipToScene, spriteList[spriteNumber].keepMask);
}
void Sprite::draw(SpriteList &spriteList, uint spriteNumber, const Rect &screenRect, int scale, bool clipToScene) {
const byte *spriteBuffer = nullptr;
int width = 0;
int height = 0;
int xAlign = 0;
int spw;
int yAlign = 0;
int sph;
Point spritePointer;
getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
spw = (screenRect.width() - width) / 2;
sph = (screenRect.height() - height) / 2;
if (spw < 0) {
spw = 0;
}
if (sph < 0) {
sph = 0;
}
spritePointer.x = screenRect.left + xAlign + spw;
spritePointer.y = screenRect.top + yAlign + sph;
drawClip(spritePointer, width, height, spriteBuffer, clipToScene, spriteList[spriteNumber].keepMask);
}
bool Sprite::hitTest(SpriteList &spriteList, uint spriteNumber, const Point &screenCoord, int scale, const Point &testPoint) {
const byte *spriteBuffer = nullptr;
int i, j;
const byte *srcRowPointer;
int width = 0;
int height = 0;
int xAlign = 0;
int yAlign = 0;
Point spritePointer;
getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
spritePointer.x = screenCoord.x + xAlign;
spritePointer.y = screenCoord.y + yAlign;
if ((testPoint.y < spritePointer.y) || (testPoint.y >= spritePointer.y + height)) {
return false;
}
if ((testPoint.x < spritePointer.x) || (testPoint.x >= spritePointer.x + width)) {
return false;
}
i = testPoint.y - spritePointer.y;
j = testPoint.x - spritePointer.x;
srcRowPointer = spriteBuffer + j + i * width;
return *srcRowPointer != 0;
}
void Sprite::drawOccluded(SpriteList &spriteList, uint spriteNumber, const Point &screenCoord, int scale, int depth) {
const byte *spriteBuffer = nullptr;
int x, y;
byte *destRowPointer;
const byte *sourceRowPointer;
const byte *sourcePointer;
byte *destPointer;
byte *maskPointer;
int width = 0;
int height = 0;
int xAlign = 0;
int yAlign = 0;
ClipData clipData;
// BG mask variables
int maskWidth;
int maskHeight;
byte *maskBuffer;
byte *maskRowPointer;
int maskZ;
if (!_vm->_scene->isBGMaskPresent()) {
draw(spriteList, spriteNumber, screenCoord, scale);
return;
}
_vm->_scene->getBGMaskInfo(maskWidth, maskHeight, maskBuffer);
getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer);
clipData.destPoint.x = screenCoord.x + xAlign;
clipData.destPoint.y = screenCoord.y + yAlign;
clipData.sourceRect.left = 0;
clipData.sourceRect.top = 0;
clipData.sourceRect.right = width;
clipData.sourceRect.bottom = height;
clipData.destRect = _vm->_scene->getSceneClip();
if (!clipData.calcClip()) {
return;
}
// Finally, draw the occluded sprite
sourceRowPointer = spriteBuffer + clipData.drawSource.x + (clipData.drawSource.y * width);
destRowPointer = _vm->_gfx->getBackBufferPixels() + clipData.drawDest.x + (clipData.drawDest.y * _vm->_gfx->getBackBufferPitch());
maskRowPointer = maskBuffer + clipData.drawDest.x + (clipData.drawDest.y * maskWidth);
for (y = 0; y < clipData.drawHeight; y++) {
sourcePointer = sourceRowPointer;
destPointer = destRowPointer;
maskPointer = maskRowPointer;
for (x = 0; x < clipData.drawWidth; x++) {
if (*sourcePointer != 0) {
maskZ = *maskPointer & SPRITE_ZMASK;
if (maskZ > depth) {
*destPointer = *sourcePointer;
}
}
sourcePointer++;
destPointer++;
maskPointer++;
}
destRowPointer += _vm->_gfx->getBackBufferPitch();
maskRowPointer += maskWidth;
sourceRowPointer += width;
}
_vm->_render->addDirtyRect(Common::Rect(clipData.drawSource.x, clipData.drawSource.y,
clipData.drawSource.x + width, clipData.drawSource.y + height));
}
void Sprite::decodeRLEBuffer(const byte *inputBuffer, size_t inLength, size_t outLength) {
int bg_runcount;
int fg_runcount;
byte *outPointer;
byte *outPointerEnd;
int c;
_decodeBuf.resize(outLength);
outPointer = &_decodeBuf.front();
outPointerEnd = &_decodeBuf.back();
memset(outPointer, 0, _decodeBuf.size());
Common::MemoryReadStream readS(inputBuffer, inLength);
while (!readS.eos() && (outPointer < outPointerEnd)) {
bg_runcount = readS.readByte();
if (readS.eos())
break;
fg_runcount = readS.readByte();
for (c = 0; c < bg_runcount && !readS.eos(); c++) {
*outPointer = (byte) 0;
if (outPointer < outPointerEnd)
outPointer++;
else
return;
}
for (c = 0; c < fg_runcount && !readS.eos(); c++) {
*outPointer = readS.readByte();
if (readS.eos())
break;
if (outPointer < outPointerEnd)
outPointer++;
else
return;
}
}
}
void Sprite::scaleBuffer(const byte *src, int width, int height, int scale, size_t outLength) {
byte skip = 256 - scale; // skip factor
byte vskip = 0x80, hskip;
_decodeBuf.resize(outLength);
byte *dst = &_decodeBuf.front();
memset(dst, 0, _decodeBuf.size());
for (int i = 0; i < height; i++) {
vskip += skip;
if (vskip < skip) { // We had an overflow
src += width;
} else {
hskip = 0x80;
for (int j = 0; j < width; j++) {
*dst++ = *src++;
hskip += skip;
if (hskip < skip) // overflow
dst--;
}
}
}
}
} // End of namespace Saga

82
engines/saga/sprite.h Normal file
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/>.
*
*/
// Sprite management module private header file
#ifndef SAGA_SPRITE_H
#define SAGA_SPRITE_H
namespace Saga {
#define SPRITE_ZMAX 16
#define SPRITE_ZMASK 0x0F
struct SpriteInfo {
ByteArray decodedBuffer;
int width;
int height;
int xAlign;
int yAlign;
byte keepMask;
SpriteInfo() : width(0), height(0), xAlign(0), yAlign(0), keepMask(0) {
}
};
typedef Common::Array<SpriteInfo> SpriteList;
class Sprite {
public:
SpriteList _mainSprites;
SpriteList _saveReminderSprites;
SpriteList _arrowSprites;
SpriteList _inventorySprites;
Sprite(SagaEngine *vm);
~Sprite();
// draw scaled sprite using background scene mask
void drawOccluded(SpriteList &spriteList, uint spriteNumber, const Point &screenCoord, int scale, int depth);
// draw scaled sprite using background scene mask
void draw(SpriteList &spriteList, uint spriteNumber, const Point &screenCoord, int scale, bool clipToScene = false);
// main function
void drawClip(const Point &spritePointer, int width, int height, const byte *spriteBuffer, bool clipToScene = false, byte keepMask = 0);
void draw(SpriteList &spriteList, uint spriteNumber, const Rect &screenRect, int scale, bool clipToScene = false);
void loadList(int resourceId, SpriteList &spriteList, byte keepMask = 0); // load or append spriteList
bool hitTest(SpriteList &spriteList, uint spriteNumber, const Point &screenCoord, int scale, const Point &testPoint);
void getScaledSpriteBuffer(SpriteList &spriteList, uint spriteNumber, int scale, int &width, int &height, int &xAlign, int &yAlign, const byte *&buffer);
private:
void decodeRLEBuffer(const byte *inputBuffer, size_t inLength, size_t outLength);
void scaleBuffer(const byte *src, int width, int height, int scale, size_t outLength);
SagaEngine *_vm;
ResourceContext *_spriteContext;
ByteArray _decodeBuf;
};
} // End of namespace Saga
#endif

235
engines/saga/sthread.cpp Normal file
View File

@@ -0,0 +1,235 @@
/* 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/>.
*
*/
// Scripting module thread management component
#include "saga/saga.h"
#include "saga/gfx.h"
#include "saga/actor.h"
#include "saga/console.h"
#include "saga/interface.h"
#include "saga/script.h"
#include "saga/scene.h"
namespace Saga {
ScriptThread &Script::createThread(uint16 scriptModuleNumber, uint16 scriptEntryPointNumber) {
loadModule(scriptModuleNumber);
if (_modules[scriptModuleNumber].entryPoints.size() <= scriptEntryPointNumber) {
error("Script::createThread wrong scriptEntryPointNumber");
}
ScriptThread tmp;
_threadList.push_front(tmp);
ScriptThread &newThread = _threadList.front();
newThread._instructionOffset = _modules[scriptModuleNumber].entryPoints[scriptEntryPointNumber].offset;
newThread._commonBase = _commonBuffer.getBuffer();
newThread._staticBase = _commonBuffer.getBuffer() + _modules[scriptModuleNumber].staticOffset;
newThread._moduleBase = _modules[scriptModuleNumber].moduleBase.getBuffer();
newThread._moduleBaseSize = _modules[scriptModuleNumber].moduleBase.size();
newThread._strings = &_modules[scriptModuleNumber].strings;
if (_vm->getGameId() == GID_IHNM)
newThread._voiceLUT = &_globalVoiceLUT;
else
newThread._voiceLUT = &_modules[scriptModuleNumber].voiceLUT;
newThread._stackBuf.resize(ScriptThread::THREAD_STACK_SIZE);
newThread._stackTopIndex = ScriptThread::THREAD_STACK_SIZE - 2;
debug(3, "createThread(). Total threads: %d", _threadList.size());
return newThread;
}
void Script::wakeUpActorThread(int waitType, void *threadObj) {
ScriptThreadList::iterator threadIterator;
for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
ScriptThread &thread = *threadIterator;
if ((thread._flags & kTFlagWaiting) && (thread._waitType == waitType) && (thread._threadObj == threadObj)) {
thread._flags &= ~kTFlagWaiting;
}
}
}
void Script::wakeUpThreads(int waitType) {
ScriptThreadList::iterator threadIterator;
debug(3, "wakeUpThreads(%d)", waitType);
for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
ScriptThread &thread = *threadIterator;
if ((thread._flags & kTFlagWaiting) && (thread._waitType == waitType)) {
thread._flags &= ~kTFlagWaiting;
}
}
}
void Script::wakeUpThreadsDelayed(int waitType, int sleepTime) {
ScriptThreadList::iterator threadIterator;
debug(3, "wakeUpThreads(%d, %d)", waitType, sleepTime);
for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) {
ScriptThread &thread = *threadIterator;
if ((thread._flags & kTFlagWaiting) && (thread._waitType == waitType)) {
thread._waitType = kWaitTypeDelay;
thread._sleepTime = sleepTime;
}
}
}
void Script::executeThreads(uint msec) {
ScriptThreadList::iterator threadIterator;
if (_vm->_interface->_statusTextInput)
return;
threadIterator = _threadList.begin();
while (threadIterator != _threadList.end()) {
ScriptThread &thread = *threadIterator;
if (thread._flags & (kTFlagFinished | kTFlagAborted)) {
if (thread._flags & kTFlagFinished)
setPointerVerb();
if (_vm->getGameId() == GID_IHNM) {
thread._flags &= ~kTFlagFinished;
thread._flags |= kTFlagAborted;
++threadIterator;
} else {
threadIterator = _threadList.erase(threadIterator);
}
continue;
}
if (thread._flags & kTFlagWaiting) {
switch (thread._waitType) {
case kWaitTypeDelay:
if (thread._sleepTime < msec)
thread._sleepTime = 0;
else
thread._sleepTime -= msec;
if (thread._sleepTime == 0)
thread._flags &= ~kTFlagWaiting;
break;
case kWaitTypeWalk:
{
ActorData *actor = (ActorData *)thread._threadObj;
if (actor->_currentAction == kActionWait)
thread._flags &= ~kTFlagWaiting;
}
break;
case kWaitTypeWaitFrames: // IHNM
if (thread._frameWait < _vm->_frameCount)
thread._flags &= ~kTFlagWaiting;
break;
default:
break;
}
}
if (!(thread._flags & kTFlagWaiting)) {
if (runThread(thread))
break;
}
++threadIterator;
}
}
void Script::abortAllThreads() {
ScriptThreadList::iterator threadIterator;
debug(3, "abortAllThreads()");
threadIterator = _threadList.begin();
while (threadIterator != _threadList.end()) {
ScriptThread &thread = *threadIterator;
thread._flags |= kTFlagAborted;
++threadIterator;
}
executeThreads(0);
}
void Script::completeThread() {
int limit = (_vm->getGameId() == GID_IHNM) ? 100 : 40;
for (int i = 0; i < limit && !_threadList.empty(); i++)
executeThreads(0);
}
bool Script::runThread(ScriptThread &thread) {
uint16 savedInstructionOffset;
bool stopParsing = false;
bool breakOut = false;
int operandChar;
Common::MemoryReadStream scriptS(thread._moduleBase, thread._moduleBaseSize);
scriptS.seek(thread._instructionOffset);
for (uint instructionCount = 0; instructionCount < STHREAD_TIMESLICE; instructionCount++) {
if (thread._flags & (kTFlagAsleep))
break;
savedInstructionOffset = thread._instructionOffset;
operandChar = scriptS.readByte();
debug(8, "Executing thread offset: %u (0x%X) stack: %d", thread._instructionOffset, operandChar, thread.pushedSize());
stopParsing = false;
debug(4, "Calling op %s", this->_scriptOpsList[operandChar].scriptOpName);
(this->*_scriptOpsList[operandChar].scriptOp)(&thread, &scriptS, stopParsing, breakOut);
if (stopParsing)
return breakOut;
if (thread._flags & (kTFlagFinished | kTFlagAborted)) {
error("Wrong flags %d in thread", thread._flags);
}
// Set instruction offset only if a previous instruction didn't branch
if (savedInstructionOffset == thread._instructionOffset) {
thread._instructionOffset = scriptS.pos();
} else {
if (thread._instructionOffset >= scriptS.size()) {
error("Script::runThread() Out of range script execution");
}
scriptS.seek(thread._instructionOffset);
}
if (breakOut)
break;
}
return false;
}
} // End of namespace Saga

139
engines/saga/xref.txt Normal file
View File

@@ -0,0 +1,139 @@
$Id$
Cross-reference for functions and variables for the original source code and
the ScummVM implementation.
Watcom C++ arguments order:
eax, edx, ebx, ecx, stack
Sceneres.h
==========
LOADREQ_FIGURE
LOADREQ_OBJECT
LOADREQ_BACKGROUND SAGA_BG_IMAGE
LOADREQ_ZBUF SAGA_BG_MASK
LOADREQ_SCENE_SCRIPT
LOADREQ_STRINGS SAGA_OBJECT_NAME_LIST
LOADREQ_HITZONES SAGA_OBJECT_MAP
LOADREQ_STEPZONES SAGA_ACTION_MAP
LOADREQ_TILE_IMAGES SAGA_ISO_TILESET
LOADREQ_TILE_MAP SAGA_ISO_METAMAP
LOADREQ_TILE_PLATFORMS SAGA_ISO_METATILESET
LOADREQ_TILE_METATILES
LOADREQ_ENTRY SAGA_ENTRY
LOADREQ_FRAMELIST
LOADREQ_ANIM_0 SAGA_ANIM_1
LOADREQ_ANIM_1 SAGA_ANIM_2
LOADREQ_ANIM_2 SAGA_ANIM_3
LOADREQ_ANIM_3 SAGA_ANIM_4
LOADREQ_ANIM_4 SAGA_ANIM_5
LOADREQ_ANIM_5 SAGA_ANIM_6
LOADREQ_ANIM_6 SAGA_ANIM_7
LOADREQ_ANIM_7
LOADREQ_TILE_MULTI
LOADREQ_CYCLES SAGA_PAL_ANIM
LOADREQ_FACES SAGA_FACES
LOADREQ_PALETTE
hitZone _objectMap
stepZone _actionMap
HZONEF_EXIT OBJECT_EXIT (in Verb.c), ACTION_EXIT (in Actor.c)
HZONEF_ENABLED OBJECT_ENABLED (in Verb.c), ACTION_ENABLED (in Actor.c)
HZONEF_NOWALK OBJECT_NOWALK
HZONEF_PROJECT OBJECT_PROJECT
HZONEF_AUTOWALK ACTION_AUTOWALK
HZONEF_TERMINUS ACTION_TERMINUS
FrameRange.startFrame ACTORACTIONITEM.frame_index
FrameRange.frameCount ACTORACTIONITEM.frame_count
FrameSequence.right ACTORACTION.dir[0]
FrameSequence.left ACTORACTION.dir[1]
FrameSequence.back ACTORACTION.dir[2]
FrameSequence.forward ACTORACTION.dir[3]
Scene.c
=======
ResToImage() _vm->decodeBGImage()
resInfo->sceneFlags _desc.flags
resInfo->loadList _desc.resListRN
resInfo->horizon _desc.endSlope
resInfo->nearFigureLimit _desc.beginSlope
resInfo->scriptModule _desc.scriptModuleNumber
resInfo->entryScript _desc.sceneScriptEntrypointNumber
resInfo->preScript _desc.startScriptEntrypointNumber
resInfo->backgroundMusic _desc.musicRN
thisScene->ID currentSceneNumber()
Interp.c
========
dispatchThreads() executeThreads()
runThread() SThreadCompleteThread()
moduleList _scriptLUT
ModuleEntry->codeID _scriptLUT->script_rn
ModuleEntry->strID _scriptLUT->diag_list_rn
ModuleEntry->vtableID _scriptLUT->voice_lut_rn
threadBase.theAction threadVars[kVarAction]
threadBase.theObject threadVars[kVarObject]
threadBase.withObject threadVars[kVarWithObject]
threadBase.theActor threadVars[kVarActor]
Actor.h
=======
GOF_PROTAGONIST kProtagonist
GOF_FOLLOWER kFollower
GOF_CYCLE kCycle
GOF_FASTER kFaster
GOF_FASTEST kFastest
GOF_EXTENDED kExtended
Actor.c
=======
abortAllSpeeches() abortAllSpeeches()
Main.c
======
sceneIndexTable _scene->getSceneLUT()
Main.h
======
BRIGHT_WHITE kITEColorBrightWhite
WHITE_02 kITEColorWhite
GREY_0A kITEColorGrey
DK_GREY_0B kITEColorDarkGrey
PITCH_BLACK kITEColorBlack
RED_65 kITEColorRed
BLUE_93 kITEColorBlue
GREEB_BA kITEColorGreen
Note that ScummVM's kITEColorLightGrey does not have any corresponding
constant in the original SAGA engine. We use it for the ITE mouse cursor. See
PtrData[] in Main.c and setCursor() in gfx.cpp
Tile.h
======
isoTile.height ISOTILE_ENTRY.tile_h
isoTile.attributes ISOTILE_ENTRY.mask_rule
isoTile.offset ISOTILE_ENTRY.tile_offset
isoTile.terrain_mask ISOTILE_ENTRY.terrain_mask
isoTile.fgd_bgd_attr ISOTILE_ENTRY.mask
tilePlatform.metaTile ISO_METATILE_ENTRY.mtile_n
tilePlatform.height ISO_METATILE_ENTRY.height
tilePlatform.highestPixel ISO_METATILE_ENTRY.highest_pixel
tilePlatform.vBits ISO_METATILE_ENTRY.v_bits
tilePlatform.uBits ISO_METATILE_ENTRY.u_bits
Resource.h
==========
PicHeader.width IMAGE_HEADER.width
PicHeader.height IMAGE_HEADER.height
Process.c
=========
mainPanelMode Interface::_inMainMode