Initial commit
This commit is contained in:
4
engines/saga/POTFILES
Normal file
4
engines/saga/POTFILES
Normal 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
1275
engines/saga/actor.cpp
Normal file
File diff suppressed because it is too large
Load Diff
688
engines/saga/actor.h
Normal file
688
engines/saga/actor.h
Normal 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
660
engines/saga/actor_path.cpp
Normal 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
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
1062
engines/saga/animation.cpp
Normal file
File diff suppressed because it is too large
Load Diff
208
engines/saga/animation.h
Normal file
208
engines/saga/animation.h
Normal 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_
|
||||
4
engines/saga/configure.engine
Normal file
4
engines/saga/configure.engine
Normal 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
302
engines/saga/console.cpp
Normal 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
72
engines/saga/console.h
Normal 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
7
engines/saga/credits.pl
Normal file
@@ -0,0 +1,7 @@
|
||||
begin_section("SAGA");
|
||||
add_person("Torbjö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();
|
||||
73
engines/saga/detection.cpp
Normal file
73
engines/saga/detection.cpp
Normal 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
143
engines/saga/detection.h
Normal 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
|
||||
1744
engines/saga/detection_tables.h
Normal file
1744
engines/saga/detection_tables.h
Normal file
File diff suppressed because it is too large
Load Diff
540
engines/saga/displayinfo.h
Normal file
540
engines/saga/displayinfo.h
Normal 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
657
engines/saga/events.cpp
Normal 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
202
engines/saga/events.h
Normal 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
1207
engines/saga/font.cpp
Normal file
File diff suppressed because it is too large
Load Diff
307
engines/saga/font.h
Normal file
307
engines/saga/font.h
Normal 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
164
engines/saga/font_map.cpp
Normal 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
706
engines/saga/gfx.cpp
Normal 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
279
engines/saga/gfx.h
Normal 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
505
engines/saga/image.cpp
Normal 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
177
engines/saga/input.cpp
Normal 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
2944
engines/saga/interface.cpp
Normal file
File diff suppressed because it is too large
Load Diff
465
engines/saga/interface.h
Normal file
465
engines/saga/interface.h
Normal 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
|
||||
307
engines/saga/introproc_ihnm.cpp
Normal file
307
engines/saga/introproc_ihnm.cpp
Normal 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
|
||||
834
engines/saga/introproc_ite.cpp
Normal file
834
engines/saga/introproc_ite.cpp
Normal 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
1708
engines/saga/isomap.cpp
Normal file
File diff suppressed because it is too large
Load Diff
286
engines/saga/isomap.h
Normal file
286
engines/saga/isomap.h
Normal 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
351
engines/saga/ite8.h
Normal 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
2231
engines/saga/itedata.cpp
Normal file
File diff suppressed because it is too large
Load Diff
150
engines/saga/itedata.h
Normal file
150
engines/saga/itedata.h
Normal 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
574
engines/saga/metaengine.cpp
Normal 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
53
engines/saga/module.mk
Normal 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
548
engines/saga/music.cpp
Normal 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
109
engines/saga/music.h
Normal 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
236
engines/saga/objectmap.cpp
Normal 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
118
engines/saga/objectmap.h
Normal 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
145
engines/saga/palanim.cpp
Normal 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
54
engines/saga/palanim.h
Normal 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
583
engines/saga/puzzle.cpp
Normal 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
121
engines/saga/puzzle.h
Normal 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
352
engines/saga/render.cpp
Normal 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
131
engines/saga/render.h
Normal 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
560
engines/saga/resource.cpp
Normal 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
305
engines/saga/resource.h
Normal 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
|
||||
233
engines/saga/resource_res.cpp
Normal file
233
engines/saga/resource_res.cpp
Normal 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
|
||||
57
engines/saga/resource_rsc.cpp
Normal file
57
engines/saga/resource_rsc.cpp
Normal 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
886
engines/saga/saga.cpp
Normal 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
727
engines/saga/saga.h
Normal 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
399
engines/saga/saveload.cpp
Normal 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
1453
engines/saga/scene.cpp
Normal file
File diff suppressed because it is too large
Load Diff
393
engines/saga/scene.h
Normal file
393
engines/saga/scene.h
Normal 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
1699
engines/saga/script.cpp
Normal file
File diff suppressed because it is too large
Load Diff
616
engines/saga/script.h
Normal file
616
engines/saga/script.h
Normal 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
1558
engines/saga/sfuncs.cpp
Normal file
File diff suppressed because it is too large
Load Diff
453
engines/saga/sfuncs_ihnm.cpp
Normal file
453
engines/saga/sfuncs_ihnm.cpp
Normal 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
|
||||
26
engines/saga/shared_detection_defines.h
Normal file
26
engines/saga/shared_detection_defines.h
Normal 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
323
engines/saga/small8.h
Normal 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
474
engines/saga/sndres.cpp
Normal 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
66
engines/saga/sndres.h
Normal 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
151
engines/saga/sound.cpp
Normal 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
95
engines/saga/sound.h
Normal 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
582
engines/saga/sprite.cpp
Normal 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
82
engines/saga/sprite.h
Normal 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
235
engines/saga/sthread.cpp
Normal 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
139
engines/saga/xref.txt
Normal 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
|
||||
Reference in New Issue
Block a user