diff options
author | Max Horn | 2006-02-11 22:45:04 +0000 |
---|---|---|
committer | Max Horn | 2006-02-11 22:45:04 +0000 |
commit | 26ee630756ebdd7c96bccede0881a8c8b98e8f2b (patch) | |
tree | 26e378d5cf990a2b81c2c96e9e683a7f333b62e8 /engines/saga | |
parent | 2a9a0d4211b1ea5723f1409d91cb95de8984429e (diff) | |
download | scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.gz scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.bz2 scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.zip |
Moved engines to the new engines/ directory
svn-id: r20582
Diffstat (limited to 'engines/saga')
56 files changed, 29180 insertions, 0 deletions
diff --git a/engines/saga/actor.cpp b/engines/saga/actor.cpp new file mode 100644 index 0000000000..cc89f0ef2a --- /dev/null +++ b/engines/saga/actor.cpp @@ -0,0 +1,3092 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "saga/saga.h" + +#include "saga/actor.h" +#include "saga/animation.h" +#include "saga/console.h" +#include "saga/events.h" +#include "saga/gfx.h" +#include "saga/interface.h" +#include "saga/isomap.h" +#include "saga/itedata.h" +#include "saga/objectmap.h" +#include "saga/resnames.h" +#include "saga/rscfile.h" +#include "saga/script.h" +#include "saga/sndres.h" +#include "saga/sprite.h" +#include "saga/stream.h" +#include "saga/font.h" +#include "saga/sound.h" +#include "saga/scene.h" + +#include "common/config-manager.h" + +namespace Saga { + +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 +}; + +static int commonObjectCompare(const CommonObjectDataPointer& obj1, const CommonObjectDataPointer& obj2) { + int p1 = obj1->_location.y - obj1->_location.z; + int p2 = obj2->_location.y - obj2->_location.z; + if (p1 == p2) + return 0; + if (p1 < p2) + return -1; + return 1; +} + +static int tileCommonObjectCompare(const CommonObjectDataPointer& obj1, const CommonObjectDataPointer& obj2) { + int p1 = -obj1->_location.u() - obj1->_location.v() - obj1->_location.z; + int p2 = -obj2->_location.u() - obj2->_location.v() - obj2->_location.z; + //TODO: for kObjNotFlat obj Height*3 of sprite should be added to p1 and p2 + //if (validObjId(obj1->id)) { + + if (p1 == p2) + return 0; + if (p1 < p2) + return -1; + return 1; +} + +// Lookup table to convert 8 cardinal directions to 4 +static const int actorDirectectionsLUT[8] = { + ACTOR_DIRECTION_BACK, // kDirUp + ACTOR_DIRECTION_RIGHT, // kDirUpRight + ACTOR_DIRECTION_RIGHT, // kDirRight + ACTOR_DIRECTION_RIGHT, // kDirDownRight + ACTOR_DIRECTION_FORWARD,// kDirDown + ACTOR_DIRECTION_LEFT, // kDirDownLeft + ACTOR_DIRECTION_LEFT, // kDirLeft + ACTOR_DIRECTION_LEFT, // kDirUpLeft +}; + +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 } +}; + +static const int angleLUT[16][2] = { + { 0, -256 }, + { 98, -237 }, + { 181, -181 }, + { 237, -98 }, + { 256, 0 }, + { 237, 98 }, + { 181, 181 }, + { 98, 237 }, + { 0, 256 }, + { -98, 237 }, + { -181, 181 }, + { -237, 98 }, + { -256, 0 }, + { -237, -98 }, + { -181, -181 }, + { -98, -237 } +}; + +static const int directionLUT[8][2] = { + { 0 * 2, -2 * 2 }, + { 2 * 2, -1 * 2 }, + { 3 * 2, 0 * 2 }, + { 2 * 2, 1 * 2 }, + { 0 * 2, 2 * 2 }, + { -2 * 2, 1 * 2 }, + { -4 * 2, 0 * 2 }, + { -2 * 2, -1 * 2 } +}; + +static const int tileDirectionLUT[8][2] = { + { 1, 1 }, + { 2, 0 }, + { 1, -1 }, + { 0, -2 }, + { -1, -1 }, + { -2, 0 }, + { -1, 1 }, + { 0, 2 } +}; + +struct DragonMove { + uint16 baseFrame; + int16 offset[4][2]; +}; + +static const DragonMove dragonMoveTable[12] = { + { 0, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } }, + { 0, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } }, + { 0, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } }, + { 0, { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } } }, + { 28, { { -0, 0 }, { -1, 6 }, { -5, 11 }, { -10, 15 } } }, + { 56, { { 0, 0 }, { 1, 6 }, { 5, 11 }, { 10, 15 } } }, + { 40, { { 0, 0 }, { 6, 1 }, { 11, 5 }, { 15, 10 } } }, + { 44, { { 0, 0 }, { 6, -1 }, { 11, -5 }, { 15, -10 } } }, + { 32, { { -0, -0 }, { -6, -1 }, { -11, -5 }, { -15, -10 } } }, + { 52, { { -0, 0 }, { -6, 1 }, { -11, 5 }, { -15, 10 } } }, + { 36, { { 0, -0 }, { 1, -6 }, { 5, -11 }, { 10, -15 } } }, + { 48, { { -0, -0 }, { -1, -6 }, { -5, -11 }, { -10, -15 } } } +}; + +Actor::Actor(SagaEngine *vm) : _vm(vm) { + int i; + byte *stringsPointer; + size_t stringsLength; + ActorData *actor; + ObjectData *obj; + debug(9, "Actor::Actor()"); + _handleActionDiv = 15; + + _actors = NULL; + _actorsCount = 0; + + _objs = NULL; + _objsCount = 0; + +#ifdef ACTOR_DEBUG + _debugPoints = NULL; + _debugPointsAlloced = _debugPointsCount = 0; +#endif + + _protagStates = 0; + _protagStatesCount = 0; + + _pathNodeList = _newPathNodeList = NULL; + _pathList = NULL; + _pathDirectionList = NULL; + _pathListAlloced = _pathNodeListAlloced = _newPathNodeListAlloced = 0; + _pathListIndex = _pathNodeListIndex = _newPathNodeListIndex = -1; + _pathDirectionListCount = 0; + _pathDirectionListAlloced = 0; + + _centerActor = _protagonist = NULL; + _protagState = 0; + _lastTickMsec = 0; + + _yCellCount = _vm->_scene->getHeight(); + _xCellCount = _vm->getDisplayWidth(); + + _pathCell = (int8 *)malloc(_yCellCount * _xCellCount * sizeof(*_pathCell)); + + _pathRect.left = 0; + _pathRect.right = _vm->getDisplayWidth(); + _pathRect.top = _vm->getDisplayInfo().pathStartY; + _pathRect.bottom = _vm->_scene->getHeight(); + + // Get actor resource file context + _actorContext = _vm->_resource->getContext(GAME_RESOURCEFILE); + if (_actorContext == NULL) { + error("Actor::Actor() resource context not found"); + } + + // Load ITE actor strings. (IHNM actor strings are loaded by + // loadGlobalResources() instead.) + + if (_vm->getGameType() == GType_ITE) { + + _vm->_resource->loadResource(_actorContext, _vm->getResourceDescription()->actorsStringsResourceId, stringsPointer, stringsLength); + + _vm->loadStrings(_actorsStrings, stringsPointer, stringsLength); + free(stringsPointer); + } + + if (_vm->getGameType() == GType_ITE) { + _actorsCount = ITE_ACTORCOUNT; + _actors = (ActorData **)malloc(_actorsCount * sizeof(*_actors)); + for (i = 0; i < _actorsCount; i++) { + actor = _actors[i] = new ActorData(); + actor->_id = actorIndexToId(i); + actor->_index = i; + debug(9, "init actor id=%d index=%d", actor->_id, actor->_index); + actor->_nameIndex = ITE_ActorTable[i].nameIndex; + actor->_scriptEntrypointNumber = ITE_ActorTable[i].scriptEntrypointNumber; + actor->_spriteListResourceId = ITE_ActorTable[i].spriteListResourceId; + actor->_frameListResourceId = ITE_ActorTable[i].frameListResourceId; + actor->_speechColor = ITE_ActorTable[i].speechColor; + actor->_sceneNumber = ITE_ActorTable[i].sceneIndex; + actor->_flags = ITE_ActorTable[i].flags; + actor->_currentAction = ITE_ActorTable[i].currentAction; + actor->_facingDirection = ITE_ActorTable[i].facingDirection; + actor->_actionDirection = ITE_ActorTable[i].actionDirection; + + actor->_location.x = ITE_ActorTable[i].x; + actor->_location.y = ITE_ActorTable[i].y; + actor->_location.z = ITE_ActorTable[i].z; + + actor->_disabled = !loadActorResources(actor); + if (actor->_disabled) { + warning("Disabling actor Id=%d index=%d", actor->_id, actor->_index); + } + } + _objsCount = ITE_OBJECTCOUNT; + _objs = (ObjectData **)malloc(_objsCount * sizeof(*_objs)); + for (i = 0; i < _objsCount; i++) { + obj = _objs[i] = new ObjectData(); + obj->_id = objIndexToId(i); + obj->_index = i; + debug(9, "init obj id=%d index=%d", obj->_id, obj->_index); + obj->_nameIndex = ITE_ObjectTable[i].nameIndex; + obj->_scriptEntrypointNumber = ITE_ObjectTable[i].scriptEntrypointNumber; + obj->_spriteListResourceId = ITE_ObjectTable[i].spriteListResourceId; + obj->_sceneNumber = ITE_ObjectTable[i].sceneIndex; + obj->_interactBits = ITE_ObjectTable[i].interactBits; + + obj->_location.x = ITE_ObjectTable[i].x; + obj->_location.y = ITE_ObjectTable[i].y; + obj->_location.z = ITE_ObjectTable[i].z; + } + } else { + // TODO. This is causing problems for SYMBIAN os as it doesn't like a static class here + ActorData dummyActor; + + dummyActor._frames = NULL; + dummyActor._walkStepsPoints = NULL; + + _protagonist = &dummyActor; + } + + _dragonHunt = true; +} + +Actor::~Actor() { + debug(9, "Actor::~Actor()"); + +#ifdef ACTOR_DEBUG + free(_debugPoints); +#endif + free(_pathDirectionList); + free(_pathNodeList); + free(_newPathNodeList); + free(_pathList); + free(_pathCell); + _actorsStrings.freeMem(); + //release resources + freeActorList(); + freeObjList(); +} + +bool Actor::loadActorResources(ActorData *actor) { + byte *resourcePointer; + size_t resourceLength; + int framesCount; + ActorFrameSequence *framesPointer; + bool gotSomething = false; + + if (actor->_frameListResourceId) { + debug(9, "Loading frame resource id %d", actor->_frameListResourceId); + _vm->_resource->loadResource(_actorContext, actor->_frameListResourceId, resourcePointer, resourceLength); + + framesCount = resourceLength / 16; + debug(9, "Frame resource contains %d frames (res length is %d)", framesCount, resourceLength); + + framesPointer = (ActorFrameSequence *)malloc(sizeof(ActorFrameSequence) * framesCount); + if (framesPointer == NULL && framesCount != 0) { + memoryError("Actor::loadActorResources"); + } + + MemoryReadStreamEndian readS(resourcePointer, resourceLength, _actorContext->isBigEndian); + + for (int i = 0; i < framesCount; i++) { + debug(9, "frameType %d", i); + for (int orient = 0; orient < ACTOR_DIRECTIONS_COUNT; orient++) { + // Load all four orientations + framesPointer[i].directions[orient].frameIndex = readS.readUint16(); + if (_vm->getGameType() == GType_ITE) { + framesPointer[i].directions[orient].frameCount = readS.readSint16(); + } else { + framesPointer[i].directions[orient].frameCount = readS.readByte(); + readS.readByte(); + } + if (framesPointer[i].directions[orient].frameCount < 0) + warning("frameCount < 0 (%d)", framesPointer[i].directions[orient].frameCount); + debug(9, "frameIndex %d frameCount %d", framesPointer[i].directions[orient].frameIndex, framesPointer[i].directions[orient].frameCount); + } + } + + free(resourcePointer); + + actor->_frames = framesPointer; + actor->_framesCount = framesCount; + + gotSomething = true; + } else { + warning("Frame List ID = 0 for actor index %d", actor->_index); + + //if (_vm->getGameType() == GType_ITE) + return true; + } + + if (actor->_spriteListResourceId) { + gotSomething = true; + } else { + warning("Sprite List ID = 0 for actor index %d", actor->_index); + } + + return gotSomething; +} + +void Actor::freeActorList() { + int i; + ActorData *actor; + for (i = 0; i < _actorsCount; i++) { + actor = _actors[i]; + delete actor; + } + free(_actors); + _actors = NULL; + _actorsCount = 0; +} + +void Actor::loadActorSpriteList(ActorData *actor) { + int lastFrame = 0; + int resourceId = actor->_spriteListResourceId; + + for (int i = 0; i < actor->_framesCount; i++) { + for (int orient = 0; orient < ACTOR_DIRECTIONS_COUNT; orient++) { + if (actor->_frames[i].directions[orient].frameIndex > lastFrame) { + lastFrame = actor->_frames[i].directions[orient].frameIndex; + } + } + } + + debug(9, "Loading actor sprite resource id %d", resourceId); + + _vm->_sprite->loadList(resourceId, actor->_spriteList); + + if (_vm->getGameType() == GType_ITE) { + if (actor->_flags & kExtended) { + while ((lastFrame >= actor->_spriteList.spriteCount)) { + resourceId++; + debug(9, "Appending to actor sprite list %d", resourceId); + _vm->_sprite->loadList(resourceId, actor->_spriteList); + } + } + } +} + +void Actor::loadActorList(int protagonistIdx, int actorCount, int actorsResourceID, int protagStatesCount, int protagStatesResourceID) { + int i, j; + ActorData *actor; + byte* actorListData; + size_t actorListLength; + byte walk[128]; + byte acv[6]; + int movementSpeed; + int walkStepIndex; + int walkStepCount; + + freeActorList(); + + _vm->_resource->loadResource(_actorContext, actorsResourceID, actorListData, actorListLength); + + _actorsCount = actorCount; + + if (actorListLength != (uint)_actorsCount * ACTOR_INHM_SIZE) { + error("Actor::loadActorList wrong actorlist length"); + } + + MemoryReadStream actorS(actorListData, actorListLength); + + _actors = (ActorData **)malloc(_actorsCount * sizeof(*_actors)); + for (i = 0; i < _actorsCount; i++) { + actor = _actors[i] = new ActorData(); + actor->_id = objectIndexToId(kGameObjectActor, i); //actorIndexToId(i); + actor->_index = i; + debug(4, "init actor id=0x%x index=%d", actor->_id, actor->_index); + actorS.readUint32LE(); //next displayed + actorS.readByte(); //type + actor->_flags = actorS.readByte(); + actor->_nameIndex = actorS.readUint16LE(); + actor->_sceneNumber = actorS.readUint32LE(); + actor->_location.fromStream(actorS); + actor->_screenPosition.x = actorS.readUint16LE(); + actor->_screenPosition.y = actorS.readUint16LE(); + actor->_screenScale = actorS.readUint16LE(); + actor->_screenDepth = actorS.readUint16LE(); + actor->_spriteListResourceId = actorS.readUint32LE(); + actor->_frameListResourceId = actorS.readUint32LE(); + debug(4, "%d: %d, %d [%d]", i, actor->_spriteListResourceId, actor->_frameListResourceId, actor->_nameIndex); + actor->_scriptEntrypointNumber = actorS.readUint32LE(); + actorS.readUint32LE(); // xSprite *dSpr; + actorS.readUint16LE(); //LEFT + actorS.readUint16LE(); //RIGHT + actorS.readUint16LE(); //TOP + actorS.readUint16LE(); //BOTTOM + actor->_speechColor = actorS.readByte(); + actor->_currentAction = actorS.readByte(); + actor->_facingDirection = actorS.readByte(); + actor->_actionDirection = actorS.readByte(); + actor->_actionCycle = actorS.readUint16LE(); + actor->_frameNumber = actorS.readUint16LE(); + actor->_finalTarget.fromStream(actorS); + actor->_partialTarget.fromStream(actorS); + movementSpeed = actorS.readUint16LE(); //movement speed + if (movementSpeed) { + error("Actor::loadActorList movementSpeed != 0"); + } + actorS.read(walk, 128); + for (j = 0; j < 128; j++) { + if (walk[j]) { + error("Actor::loadActorList walk[128] != 0"); + } + } + //actorS.seek(128, SEEK_CUR); + walkStepCount = actorS.readByte();//walkStepCount + if (walkStepCount) { + error("Actor::loadActorList walkStepCount != 0"); + } + walkStepIndex = actorS.readByte();//walkStepIndex + if (walkStepIndex) { + error("Actor::loadActorList walkStepIndex != 0"); + } + //no need to check pointers + actorS.readUint32LE(); //sprites + actorS.readUint32LE(); //frames + actorS.readUint32LE(); //last zone + actor->_targetObject = actorS.readUint16LE(); + actor->_actorFlags = actorS.readUint16LE(); + //no need to check pointers + actorS.readUint32LE(); //next in scene + actorS.read(acv, 6); + for (j = 0; j < 6; j++) { + if (acv[j]) { + error("Actor::loadActorList acv[%d] != 0", j); + } + } +// actorS.seek(6, SEEK_CUR); //action vars + } + free(actorListData); + + _actors[protagonistIdx]->_flags |= kProtagonist | kExtended; + + for (i = 0; i < _actorsCount; i++) { + actor = _actors[i]; + //if (actor->_flags & kProtagonist) { + loadActorResources(actor); + //break; + //} + } + + _centerActor = _protagonist = _actors[protagonistIdx]; + _protagState = 0; + + if (protagStatesResourceID) { + free(_protagStates); + + _protagStates = (ActorFrameSequence *)malloc(sizeof(ActorFrameSequence) * protagStatesCount); + + byte *resourcePointer; + size_t resourceLength; + + _vm->_resource->loadResource(_actorContext, protagStatesResourceID, + resourcePointer, resourceLength); + + + MemoryReadStream statesS(resourcePointer, resourceLength); + + for (i = 0; i < protagStatesCount; i++) { + for (j = 0; j < ACTOR_DIRECTIONS_COUNT; j++) { + _protagStates[i].directions[j].frameIndex = statesS.readUint16LE(); + _protagStates[i].directions[j].frameCount = statesS.readUint16LE(); + } + } + free(resourcePointer); + + _protagonist->_frames = &_protagStates[_protagState]; + } + + _protagStatesCount = protagStatesCount; +} + +void Actor::freeObjList() { + int i; + ObjectData *object; + for (i = 0; i < _objsCount; i++) { + object = _objs[i]; + delete object; + } + free(_objs); + _objs = NULL; + _objsCount = 0; +} + +void Actor::loadObjList(int objectCount, int objectsResourceID) { + int i; + int frameListResourceId; + ObjectData *object; + byte* objectListData; + size_t objectListLength; + freeObjList(); + + _vm->_resource->loadResource(_actorContext, objectsResourceID, objectListData, objectListLength); + + _objsCount = objectCount; + + MemoryReadStream objectS(objectListData, objectListLength); + + _objs = (ObjectData **)malloc(_objsCount * sizeof(*_objs)); + for (i = 0; i < _objsCount; i++) { + object = _objs[i] = new ObjectData(); + object->_id = objectIndexToId(kGameObjectObject, i); + object->_index = i; + debug(9, "init object id=%d index=%d", object->_id, object->_index); + objectS.readUint32LE(); //next displayed + objectS.readByte(); //type + object->_flags = objectS.readByte(); + object->_nameIndex = objectS.readUint16LE(); + object->_sceneNumber = objectS.readUint32LE(); + object->_location.fromStream(objectS); + object->_screenPosition.x = objectS.readUint16LE(); + object->_screenPosition.y = objectS.readUint16LE(); + object->_screenScale = objectS.readUint16LE(); + object->_screenDepth = objectS.readUint16LE(); + object->_spriteListResourceId = objectS.readUint32LE(); + frameListResourceId = objectS.readUint32LE(); // object->_frameListResourceId + if (frameListResourceId) { + error("Actor::loadObjList frameListResourceId != 0"); + } + object->_scriptEntrypointNumber = objectS.readUint32LE(); + objectS.readUint32LE(); // xSprite *dSpr; + objectS.readUint16LE(); //LEFT + objectS.readUint16LE(); //RIGHT + objectS.readUint16LE(); //TOP + objectS.readUint16LE(); //BOTTOM + object->_interactBits = objectS.readUint16LE(); + } + free(objectListData); +} + +void Actor::takeExit(uint16 actorId, const HitZone *hitZone) { + ActorData *actor; + actor = getActor(actorId); + actor->_lastZone = NULL; + + _vm->_scene->changeScene(hitZone->getSceneNumber(), hitZone->getActorsEntrance(), kTransitionNoFade); + if (_vm->_interface->getMode() != kPanelSceneSubstitute) { + _vm->_script->setNoPendingVerb(); + } +} + +void Actor::stepZoneAction(ActorData *actor, const HitZone *hitZone, bool exit, bool stopped) { + Event event; + + if (actor != _protagonist) { + return; + } + if (((hitZone->getFlags() & kHitZoneTerminus) && !stopped) || (!(hitZone->getFlags() & kHitZoneTerminus) && stopped)) { + return; + } + + if (!exit) { + if (hitZone->getFlags() & kHitZoneAutoWalk) { + actor->_currentAction = kActionWalkDir; + actor->_actionDirection = actor->_facingDirection = hitZone->getDirection(); + actor->_walkFrameSequence = getFrameType(kFrameWalk); + return; + } + } else if (!(hitZone->getFlags() & kHitZoneAutoWalk)) { + return; + } + if (hitZone->getFlags() & kHitZoneExit) { + takeExit(actor->_id, hitZone); + } else if (hitZone->getScriptNumber() > 0) { + event.type = kEvTOneshot; + event.code = kScriptEvent; + event.op = kEventExecNonBlocking; + event.time = 0; + event.param = _vm->_scene->getScriptModuleNumber(); // module number + event.param2 = hitZone->getScriptNumber(); // script entry point number + event.param3 = _vm->_script->getVerbType(kVerbEnter); // Action + event.param4 = ID_NOTHING; // Object + event.param5 = ID_NOTHING; // With Object + event.param6 = ID_PROTAG; // Actor + + _vm->_events->queue(&event); + } +} + +void Actor::realLocation(Location &location, uint16 objectId, uint16 walkFlags) { + int angle; + int distance; + ActorData *actor; + ObjectData *obj; + debug (8, "Actor::realLocation objectId=%i", objectId); + if (walkFlags & kWalkUseAngle) { + if (_vm->_scene->getFlags() & kSceneFlagISO) { + angle = (location.x + 2) & 15; + distance = location.y; + + location.u() = (angleLUT[angle][0] * distance) >> 8; + location.v() = -(angleLUT[angle][1] * distance) >> 8; + } else { + angle = location.x & 15; + distance = location.y; + + location.x = (angleLUT[angle][0] * distance) >> 6; + location.y = (angleLUT[angle][1] * distance) >> 6; + } + } + + if (objectId != ID_NOTHING) { + if (validActorId(objectId)) { + actor = getActor(objectId); + location.addXY(actor->_location); + } else if (validObjId(objectId)) { + obj = getObj(objectId); + location.addXY(obj->_location); + } + } +} + +void Actor::actorFaceTowardsPoint(uint16 actorId, const Location &toLocation) { + ActorData *actor; + Location delta; + //debug (8, "Actor::actorFaceTowardsPoint actorId=%i", actorId); + actor = getActor(actorId); + + toLocation.delta(actor->_location, delta); + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + if (delta.u() > 0) { + actor->_facingDirection = (delta.v() > 0) ? kDirUp : kDirRight; + } else { + actor->_facingDirection = (delta.v() > 0) ? kDirLeft : kDirDown; + } + } else { + if (ABS(delta.y) > ABS(delta.x * 2)) { + actor->_facingDirection = (delta.y > 0) ? kDirDown : kDirUp; + } else { + actor->_facingDirection = (delta.x > 0) ? kDirRight : kDirLeft; + } + } +} + +void Actor::actorFaceTowardsObject(uint16 actorId, uint16 objectId) { + ActorData *actor; + ObjectData *obj; + + if (validActorId(objectId)) { + actor = getActor(objectId); + actorFaceTowardsPoint(actorId, actor->_location); + } else if (validObjId(objectId)) { + obj = getObj(objectId); + actorFaceTowardsPoint(actorId, obj->_location); + } +} + + +ObjectData *Actor::getObj(uint16 objId) { + ObjectData *obj; + + if (!validObjId(objId)) + error("Actor::getObj Wrong objId 0x%X", objId); + + obj = _objs[objIdToIndex(objId)]; + + if (obj->_disabled) + error("Actor::getObj disabled objId 0x%X", objId); + + return obj; +} + +ActorData *Actor::getActor(uint16 actorId) { + ActorData *actor; + + if (!validActorId(actorId)) { + warning("Actor::getActor Wrong actorId 0x%X", actorId); + assert(0); + } + + if (actorId == ID_PROTAG) { + if (_protagonist == NULL) { + error("_protagonist == NULL"); + } + return _protagonist; + } + + actor = _actors[actorIdToIndex(actorId)]; + + if (actor->_disabled) + error("Actor::getActor disabled actorId 0x%X", actorId); + + return actor; +} + +bool Actor::validFollowerLocation(const Location &location) { + Point point; + location.toScreenPointXY(point); + + if ((point.x < 5) || (point.x >= _vm->getDisplayWidth() - 5) || + (point.y < 0) || (point.y > _vm->_scene->getHeight())) { + return false; + } + + return (_vm->_scene->canWalk(point)); +} + +void Actor::setProtagState(int state) { + _protagState = state; + + if (_vm->getGameType() == GType_IHNM) + _protagonist->_frames = &_protagStates[state]; +} + +void Actor::updateActorsScene(int actorsEntrance) { + int i, j; + int followerDirection; + ActorData *actor; + Location tempLocation; + Location possibleLocation; + Point delta; + const SceneEntry *sceneEntry; + + if (_vm->_scene->currentSceneNumber() == 0) { + error("Actor::updateActorsScene _vm->_scene->currentSceneNumber() == 0"); + } + + _vm->_sound->stopVoice(); + _activeSpeech.stringsCount = 0; + _activeSpeech.playing = false; + _protagonist = NULL; + + for (i = 0; i < _actorsCount; i++) { + actor = _actors[i]; + actor->_inScene = false; + actor->_spriteList.freeMem(); + if (actor->_disabled) { + continue; + } + if ((actor->_flags & (kProtagonist | kFollower)) || (i == 0)) { + if (actor->_flags & kProtagonist) { + actor->_finalTarget = actor->_location; + _centerActor = _protagonist = actor; + } else if (_vm->getGameType() == GType_ITE && + _vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) { + continue; + } + + actor->_sceneNumber = _vm->_scene->currentSceneNumber(); + } + if (actor->_sceneNumber == _vm->_scene->currentSceneNumber()) { + actor->_inScene = true; + actor->_actionCycle = (_vm->_rnd.getRandomNumber(7) & 0x7) * 4; // 1/8th chance + } + } + + assert(_protagonist); + + if ((actorsEntrance >= 0) && (_vm->_scene->_entryList.entryListCount > 0)) { + if (_vm->_scene->_entryList.entryListCount <= actorsEntrance) { + actorsEntrance = 0; //OCEAN bug + } + + sceneEntry = _vm->_scene->_entryList.getEntry(actorsEntrance); + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _protagonist->_location = sceneEntry->location; + } else { + _protagonist->_location.x = sceneEntry->location.x * ACTOR_LMULT; + _protagonist->_location.y = sceneEntry->location.y * ACTOR_LMULT; + _protagonist->_location.z = sceneEntry->location.z * ACTOR_LMULT; + } + // Workaround for bug #1328045: + // "When entering any of the houses at the start of the + // game if you click on anything inside the building you + // start walking through the door, turn around and leave." + // + // After steping of action zone - Rif trying to exit. + // This piece of code shift Rif's entry position to non action zone area. + if (_vm->getGameType() == GType_ITE) { + if ((_vm->_scene->currentSceneNumber() >= 53) && (_vm->_scene->currentSceneNumber() <= 66)) + _protagonist->_location.y += 10; + } + + _protagonist->_facingDirection = _protagonist->_actionDirection = sceneEntry->facing; + } + + _protagonist->_currentAction = kActionWait; + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + //nothing? + } else { + _vm->_scene->initDoorsState(); //TODO: move to _scene + } + + followerDirection = _protagonist->_facingDirection + 3; + calcScreenPosition(_protagonist); + + for (i = 0; i < _actorsCount; i++) { + actor = _actors[i]; + if (actor->_flags & (kFollower)) { + actor->_facingDirection = actor->_actionDirection = _protagonist->_facingDirection; + actor->_currentAction = kActionWait; + actor->_walkStepsCount = actor->_walkStepIndex = 0; + actor->_location.z = _protagonist->_location.z; + + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _vm->_isoMap->placeOnTileMap(_protagonist->_location, actor->_location, 3, followerDirection & 0x07); + } else { + followerDirection &= 0x07; + + possibleLocation = _protagonist->_location; + + + delta.x = directionLUT[followerDirection][0]; + delta.y = directionLUT[followerDirection][1]; + + + for (j = 0; j < 30; j++) { + tempLocation = possibleLocation; + tempLocation.x += delta.x; + tempLocation.y += delta.y; + + if (validFollowerLocation(tempLocation)) { + possibleLocation = tempLocation; + } else { + tempLocation = possibleLocation; + tempLocation.x += delta.x; + if (validFollowerLocation(tempLocation)) { + possibleLocation = tempLocation; + } else { + tempLocation = possibleLocation; + tempLocation.y += delta.y; + if (validFollowerLocation(tempLocation)) { + possibleLocation = tempLocation; + } else { + break; + } + } + } + } + + actor->_location = possibleLocation; + } + followerDirection += 2; + } + + } + + handleActions(0, true); + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _vm->_isoMap->adjustScroll(true); + } +} + +int Actor::getFrameType(ActorFrameTypes frameType) { + + if (_vm->getGameType() == GType_ITE) { + switch (frameType) { + case kFrameStand: + return kFrameITEStand; + case kFrameWalk: + return kFrameITEWalk; + case kFrameSpeak: + return kFrameITESpeak; + case kFrameGive: + return kFrameITEGive; + case kFrameGesture: + return kFrameITEGesture; + case kFrameWait: + return kFrameITEWait; + case kFramePickUp: + return kFrameITEPickUp; + case kFrameLook: + return kFrameITELook; + } + } + else { + switch (frameType) { + case kFrameStand: + return kFrameIHNMStand; + case kFrameWalk: + return kFrameIHNMWalk; + case kFrameSpeak: + return kFrameIHNMSpeak; + case kFrameGesture: + return kFrameIHNMGesture; + case kFrameWait: + return kFrameIHNMWait; + case kFrameGive: + case kFramePickUp: + case kFrameLook: + error("Actor::getFrameType() unknown frame type %d", frameType); + return kFrameIHNMStand; + } + } + error("Actor::getFrameType() unknown frame type %d", frameType); +} + +ActorFrameRange *Actor::getActorFrameRange(uint16 actorId, int frameType) { + ActorData *actor; + int fourDirection; + static ActorFrameRange def = {0, 0}; + + actor = getActor(actorId); + if (actor->_disabled) + error("Actor::getActorFrameRange Wrong actorId 0x%X", actorId); + + if ((actor->_facingDirection < kDirUp) || (actor->_facingDirection > kDirUpLeft)) + error("Actor::getActorFrameRange Wrong direction 0x%X actorId 0x%X", actor->_facingDirection, actorId); + + //if (_vm->getGameType() == GType_ITE) { + if (frameType >= actor->_framesCount) { + warning("Actor::getActorFrameRange Wrong frameType 0x%X (%d) actorId 0x%X", frameType, actor->_framesCount, actorId); + return &def; + } + + + fourDirection = actorDirectectionsLUT[actor->_facingDirection]; + return &actor->_frames[frameType].directions[fourDirection]; +/* + } else { + if (0 == actor->_framesCount) { + return &def; + } + + //TEST + if (actor->_id == 0x2000) { + if (actor->_framesCount <= _currentFrameIndex) { + _currentFrameIndex = 0; + } + fr = actor->_frames[_currentFrameIndex].directions; + return fr; + } + //TEST + if (frameType >= actor->_framesCount) { + frameType = actor->_framesCount - 1; + } + if (frameType < 0) { + frameType = 0; + } + + if (frameType == kFrameIHNMWalk ) { + switch (actor->_facingDirection) { + case kDirUpRight: + if (frameType > 0) + fr = &actor->_frames[frameType - 1].directions[ACTOR_DIRECTION_RIGHT]; + else + fr = &def; + if (!fr->frameCount) + fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_RIGHT]; + break; + case kDirDownRight: + if (frameType > 0) + fr = &actor->_frames[frameType - 1].directions[ACTOR_DIRECTION_FORWARD]; + else + fr = &def; + if (!fr->frameCount) + fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_RIGHT]; + break; + case kDirUpLeft: + if (frameType > 0) + fr = &actor->_frames[frameType - 1].directions[ACTOR_DIRECTION_LEFT]; + else + fr = &def; + if (!fr->frameCount) + fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_LEFT]; + break; + case kDirDownLeft: + if (frameType > 0) + fr = &actor->_frames[frameType - 1].directions[ACTOR_DIRECTION_BACK]; + else + fr = &def; + if (!fr->frameCount) + fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_LEFT]; + break; + case kDirRight: + fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_RIGHT]; + break; + case kDirLeft: + fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_LEFT]; + break; + case kDirUp: + fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_BACK]; + break; + case kDirDown: + fr = &actor->_frames[frameType].directions[ACTOR_DIRECTION_FORWARD]; + break; + } + return fr; + } + else { + if (frameType >= actor->_framesCount) { + error("Actor::getActorFrameRange Wrong frameType 0x%X (%d) actorId 0x%X", frameType, actor->_framesCount, actorId); + } + fourDirection = actorDirectectionsLUT[actor->_facingDirection]; + return &actor->_frames[frameType].directions[fourDirection]; + } + }*/ +} + +void Actor::handleSpeech(int msec) { + int stringLength; + int sampleLength; + bool removeFirst; + int i; + ActorData *actor; + int width, height, height2; + Point posPoint; + + if (_activeSpeech.playing) { + _activeSpeech.playingTime -= msec; + stringLength = strlen(_activeSpeech.strings[0]); + + removeFirst = false; + if (_activeSpeech.playingTime <= 0) { + if (_activeSpeech.speechFlags & kSpeakSlow) { + _activeSpeech.slowModeCharIndex++; + if (_activeSpeech.slowModeCharIndex >= stringLength) + removeFirst = true; + } else { + removeFirst = true; + } + _activeSpeech.playing = false; + if (_activeSpeech.actorIds[0] != 0) { + actor = getActor(_activeSpeech.actorIds[0]); + if (!(_activeSpeech.speechFlags & kSpeakNoAnimate)) { + actor->_currentAction = kActionWait; + } + } + } + + if (removeFirst) { + for (i = 1; i < _activeSpeech.stringsCount; i++) { + _activeSpeech.strings[i - 1] = _activeSpeech.strings[i]; + } + _activeSpeech.stringsCount--; + } + + if (_vm->_script->_skipSpeeches) { + _activeSpeech.stringsCount = 0; + _vm->_script->wakeUpThreads(kWaitTypeSpeech); + return; + } + + if (_activeSpeech.stringsCount == 0) { + _vm->_script->wakeUpThreadsDelayed(kWaitTypeSpeech, ticksToMSec(kScriptTimeTicksPerSecond / 3)); + } + + return; + } + + if (_vm->_script->_skipSpeeches) { + _activeSpeech.stringsCount = 0; + _vm->_script->wakeUpThreads(kWaitTypeSpeech); + } + + if (_activeSpeech.stringsCount == 0) { + return; + } + + stringLength = strlen(_activeSpeech.strings[0]); + + if (_activeSpeech.speechFlags & kSpeakSlow) { + if (_activeSpeech.slowModeCharIndex >= stringLength) + error("Wrong string index"); + + warning("Slow string encountered!"); + _activeSpeech.playingTime = stringLength * 1000 / 4; + + } else { + sampleLength = _vm->_sndRes->getVoiceLength(_activeSpeech.sampleResourceId); + + if (sampleLength < 0) { + _activeSpeech.playingTime = stringLength * 1000 / 22; + switch (_vm->_readingSpeed) { + case 1: + _activeSpeech.playingTime *= 2; + break; + case 2: + _activeSpeech.playingTime *= 4; + break; + case 3: + _activeSpeech.playingTime = 0x7fffff; + break; + } + } else { + _activeSpeech.playingTime = sampleLength; + } + } + + if (_activeSpeech.sampleResourceId != -1) { + _vm->_sndRes->playVoice(_activeSpeech.sampleResourceId); + _activeSpeech.sampleResourceId++; + } + + if (_activeSpeech.actorIds[0] != 0) { + actor = getActor(_activeSpeech.actorIds[0]); + if (!(_activeSpeech.speechFlags & kSpeakNoAnimate)) { + actor->_currentAction = kActionSpeak; + actor->_actionCycle = _vm->_rnd.getRandomNumber(63); + } + } + + if (_activeSpeech.actorsCount == 1) { + if (_speechBoxScript.width() > 0) { + _activeSpeech.drawRect.left = _speechBoxScript.left; + _activeSpeech.drawRect.right = _speechBoxScript.right; + _activeSpeech.drawRect.top = _speechBoxScript.top; + _activeSpeech.drawRect.bottom = _speechBoxScript.bottom; + } else { + width = _activeSpeech.speechBox.width(); + height = _vm->_font->getHeight(kKnownFontScript, _activeSpeech.strings[0], width - 2, _activeSpeech.getFontFlags(0)) + 1; + + if (height > 40 && width < _vm->getDisplayWidth() - 100) { + width = _vm->getDisplayWidth() - 100; + height = _vm->_font->getHeight(kKnownFontScript, _activeSpeech.strings[0], width - 2, _activeSpeech.getFontFlags(0)) + 1; + } + + _activeSpeech.speechBox.setWidth(width); + + if (_activeSpeech.actorIds[0] != 0) { + actor = getActor(_activeSpeech.actorIds[0]); + _activeSpeech.speechBox.setHeight(height); + + if (_activeSpeech.speechBox.right > _vm->getDisplayWidth() - 10) { + _activeSpeech.drawRect.left = _vm->getDisplayWidth() - 10 - width; + } else { + _activeSpeech.drawRect.left = _activeSpeech.speechBox.left; + } + + height2 = actor->_screenPosition.y - 50; + _activeSpeech.speechBox.top = _activeSpeech.drawRect.top = MAX(10, (height2 - height) / 2); + } else { + _activeSpeech.drawRect.left = _activeSpeech.speechBox.left; + _activeSpeech.drawRect.top = _activeSpeech.speechBox.top + (_activeSpeech.speechBox.height() - height) / 2; + } + _activeSpeech.drawRect.setWidth(width); + _activeSpeech.drawRect.setHeight(height); + } + } + + _activeSpeech.playing = true; +} + +void Actor::handleActions(int msec, bool setup) { + int i; + ActorData *actor; + ActorFrameRange *frameRange; + int state; + int speed; + int32 framesLeft; + Location delta; + Location addDelta; + int hitZoneIndex; + const HitZone *hitZone; + Point hitPoint; + Location pickLocation; + + for (i = 0; i < _actorsCount; i++) { + actor = _actors[i]; + if (!actor->_inScene) + continue; + + if ((_vm->getGameType() == GType_ITE) && (i == ACTOR_DRAGON_INDEX)) { + moveDragon(actor); + continue; + } + + switch (actor->_currentAction) { + case kActionWait: + if (!setup && (actor->_flags & kFollower)) { + followProtagonist(actor); + if (actor->_currentAction != kActionWait) + break; + } + + if (actor->_targetObject != ID_NOTHING) { + actorFaceTowardsObject(actor->_id, actor->_targetObject); + } + + if (actor->_flags & kCycle) { + frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameStand)); + if (frameRange->frameCount > 0) { + actor->_actionCycle++; + actor->_actionCycle = (actor->_actionCycle) % frameRange->frameCount; + } else { + actor->_actionCycle = 0; + } + actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle; + break; + } + + if ((actor->_actionCycle & 3) == 0) { + actor->cycleWrap(100); + + frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameWait)); + if ((frameRange->frameCount < 1 || actor->_actionCycle > 33)) + frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameStand)); + + if (frameRange->frameCount) { + actor->_frameNumber = frameRange->frameIndex + (uint16)_vm->_rnd.getRandomNumber(frameRange->frameCount - 1); + } else { + actor->_frameNumber = frameRange->frameIndex; + } + } + actor->_actionCycle++; + break; + + case kActionWalkToPoint: + case kActionWalkToLink: + if (_vm->_scene->getFlags() & kSceneFlagISO) { + actor->_partialTarget.delta(actor->_location, delta); + + while ((delta.u() == 0) && (delta.v() == 0)) { + + if ((actor == _protagonist) && (_vm->mouseButtonPressed())) { + _vm->_isoMap->screenPointToTileCoords(_vm->mousePos(), pickLocation); + + if (!actorWalkTo(_protagonist->_id, pickLocation)) { + break; + } + } else if (!_vm->_isoMap->nextTileTarget(actor) && !actorEndWalk(actor->_id, true)) { + break; + } + + actor->_partialTarget.delta(actor->_location, delta); + actor->_partialTarget.z = 0; + } + + if (actor->_flags & kFastest) { + speed = 8; + } else if (actor->_flags & kFaster) { + speed = 6; + } else { + speed = 4; + } + + if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) { + speed = 2; + } + + if ((actor->_actionDirection == 2) || (actor->_actionDirection == 6)) { + speed = speed / 2; + } + + if (ABS(delta.v()) > ABS(delta.u())) { + addDelta.v() = clamp(-speed, delta.v(), speed); + if (addDelta.v() == delta.v()) { + addDelta.u() = delta.u(); + } else { + addDelta.u() = delta.u() * addDelta.v(); + addDelta.u() += (addDelta.u() > 0) ? (delta.v() / 2) : (-delta.v() / 2); + addDelta.u() /= delta.v(); + } + } else { + addDelta.u() = clamp(-speed, delta.u(), speed); + if (addDelta.u() == delta.u()) { + addDelta.v() = delta.v(); + } else { + addDelta.v() = delta.v() * addDelta.u(); + addDelta.v() += (addDelta.v() > 0) ? (delta.u() / 2) : (-delta.u() / 2); + addDelta.v() /= delta.u(); + } + } + + actor->_location.add(addDelta); + } else { + actor->_partialTarget.delta(actor->_location, delta); + + while ((delta.x == 0) && (delta.y == 0)) { + + if (actor->_walkStepIndex >= actor->_walkStepsCount) { + actorEndWalk(actor->_id, true); + break; + } + + actor->_partialTarget.fromScreenPoint(actor->_walkStepsPoints[actor->_walkStepIndex++]); + if (_vm->getGameType() == GType_ITE) { + if (actor->_partialTarget.x > 224 * 2 * ACTOR_LMULT) { + actor->_partialTarget.x -= 256 * 2 * ACTOR_LMULT; + } + } else { + if (actor->_partialTarget.x > 224 * 4 * ACTOR_LMULT) { + actor->_partialTarget.x -= 256 * 4 * ACTOR_LMULT; + } + } + + actor->_partialTarget.delta(actor->_location, delta); + + if (ABS(delta.y) > ABS(delta.x)) { + actor->_actionDirection = delta.y > 0 ? kDirDown : kDirUp; + } else { + actor->_actionDirection = delta.x > 0 ? kDirRight : kDirLeft; + } + } + + speed = (ACTOR_LMULT * 2 * actor->_screenScale + 63) / 256; + if (speed < 1) { + speed = 1; + } + + if ((actor->_actionDirection == kDirUp) || (actor->_actionDirection == kDirDown)) { + addDelta.y = clamp(-speed, delta.y, speed); + if (addDelta.y == delta.y) { + addDelta.x = delta.x; + } else { + addDelta.x = delta.x * addDelta.y; + addDelta.x += (addDelta.x > 0) ? (delta.y / 2) : (-delta.y / 2); + addDelta.x /= delta.y; + actor->_facingDirection = actor->_actionDirection; + } + } else { + addDelta.x = clamp(-2 * speed, delta.x, 2 * speed); + if (addDelta.x == delta.x) { + addDelta.y = delta.y; + } else { + addDelta.y = delta.y * addDelta.x; + addDelta.y += (addDelta.y > 0) ? (delta.x / 2) : (-delta.x / 2); + addDelta.y /= delta.x; + actor->_facingDirection = actor->_actionDirection; + } + } + + actor->_location.add(addDelta); + } + + if (actor->_actorFlags & kActorBackwards) { + actor->_facingDirection = (actor->_actionDirection + 4) & 7; + actor->_actionCycle--; + } else { + actor->_actionCycle++; + } + + frameRange = getActorFrameRange(actor->_id, actor->_walkFrameSequence); + + if (actor->_actionCycle < 0) { + actor->_actionCycle = frameRange->frameCount - 1; + } else if (actor->_actionCycle >= frameRange->frameCount) { + actor->_actionCycle = 0; + } + + actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle; + break; + + case kActionWalkDir: + if (_vm->_scene->getFlags() & kSceneFlagISO) { + actor->_location.u() += tileDirectionLUT[actor->_actionDirection][0]; + actor->_location.v() += tileDirectionLUT[actor->_actionDirection][1]; + + frameRange = getActorFrameRange(actor->_id, actor->_walkFrameSequence); + + actor->_actionCycle++; + actor->cycleWrap(frameRange->frameCount); + actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle; + } else { + actor->_location.x += directionLUT[actor->_actionDirection][0] * 2; + actor->_location.y += directionLUT[actor->_actionDirection][1] * 2; + + frameRange = getActorFrameRange(actor->_id, actor->_walkFrameSequence); + actor->_actionCycle++; + actor->cycleWrap(frameRange->frameCount); + actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle; + } + break; + + case kActionSpeak: + actor->_actionCycle++; + actor->cycleWrap(64); + + frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameGesture)); + if (actor->_actionCycle >= frameRange->frameCount) { + if (actor->_actionCycle & 1) + break; + frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameSpeak)); + + state = (uint16)_vm->_rnd.getRandomNumber(frameRange->frameCount); + + if (state == 0) { + frameRange = getActorFrameRange(actor->_id, getFrameType(kFrameStand)); + } else { + state--; + } + } else { + state = actor->_actionCycle; + } + + actor->_frameNumber = frameRange->frameIndex + state; + break; + + case kActionAccept: + case kActionStoop: + break; + + case kActionCycleFrames: + case kActionPongFrames: + if (actor->_cycleTimeCount > 0) { + actor->_cycleTimeCount--; + break; + } + + actor->_cycleTimeCount = actor->_cycleDelay; + actor->_actionCycle++; + + frameRange = getActorFrameRange(actor->_id, actor->_cycleFrameSequence); + + if (actor->_currentAction == kActionPongFrames) { + if (actor->_actionCycle >= frameRange->frameCount * 2 - 2) { + if (actor->_actorFlags & kActorContinuous) { + actor->_actionCycle = 0; + } else { + actor->_currentAction = kActionFreeze; + break; + } + } + + state = actor->_actionCycle; + if (state >= frameRange->frameCount) { + state = frameRange->frameCount * 2 - 2 - state; + } + } else { + if (actor->_actionCycle >= frameRange->frameCount) { + if (actor->_actorFlags & kActorContinuous) { + actor->_actionCycle = 0; + } else { + actor->_currentAction = kActionFreeze; + break; + } + } + state = actor->_actionCycle; + } + + if (frameRange->frameCount && (actor->_actorFlags & kActorRandom)) { + state = _vm->_rnd.getRandomNumber(frameRange->frameCount - 1); + } + + if (actor->_actorFlags & kActorBackwards) { + actor->_frameNumber = frameRange->frameIndex + frameRange->frameCount - 1 - state; + } else { + actor->_frameNumber = frameRange->frameIndex + state; + } + break; + + case kActionFall: + if (actor->_actionCycle > 0) { + framesLeft = actor->_actionCycle--; + actor->_finalTarget.delta(actor->_location, delta); + delta.x /= framesLeft; + delta.y /= framesLeft; + actor->_location.addXY(delta); + actor->_fallVelocity += actor->_fallAcceleration; + actor->_fallPosition += actor->_fallVelocity; + actor->_location.z = actor->_fallPosition >> 4; + } else { + actor->_location = actor->_finalTarget; + actor->_currentAction = kActionFreeze; + _vm->_script->wakeUpActorThread(kWaitTypeWalk, actor); + } + break; + + case kActionClimb: + actor->_cycleDelay++; + if (actor->_cycleDelay & 3) { + break; + } + + if (actor->_location.z >= actor->_finalTarget.z + ACTOR_CLIMB_SPEED) { + actor->_location.z -= ACTOR_CLIMB_SPEED; + actor->_actionCycle--; + } else if (actor->_location.z <= actor->_finalTarget.z - ACTOR_CLIMB_SPEED) { + actor->_location.z += ACTOR_CLIMB_SPEED; + actor->_actionCycle++; + } else { + actor->_location.z = actor->_finalTarget.z; + actor->_currentAction = kActionFreeze; + _vm->_script->wakeUpActorThread(kWaitTypeWalk, actor); + } + + frameRange = getActorFrameRange(actor->_id, actor->_cycleFrameSequence); + + if (actor->_actionCycle < 0) { + actor->_actionCycle = frameRange->frameCount - 1; + } + actor->cycleWrap(frameRange->frameCount); + actor->_frameNumber = frameRange->frameIndex + actor->_actionCycle; + break; + } + + if ((actor->_currentAction >= kActionWalkToPoint) && (actor->_currentAction <= kActionWalkDir)) { + hitZone = NULL; + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + actor->_location.toScreenPointUV(hitPoint); + } else { + actor->_location.toScreenPointXY(hitPoint); + } + hitZoneIndex = _vm->_scene->_actionMap->hitTest(hitPoint); + if (hitZoneIndex != -1) { + hitZone = _vm->_scene->_actionMap->getHitZone(hitZoneIndex); + } + + if (hitZone != actor->_lastZone) { + if (actor->_lastZone) + stepZoneAction(actor, actor->_lastZone, true, false); + actor->_lastZone = hitZone; + if (hitZone) + stepZoneAction(actor, hitZone, false, false); + } + } + } +} + +void Actor::direct(int msec) { + + if (_vm->_scene->_entryList.entryListCount == 0) { + return; + } + + if (_vm->_interface->_statusTextInput) { + return; + } + + // FIXME: HACK. This should be turned into cycle event. + _lastTickMsec += msec; + + if (_lastTickMsec > 1000 / _handleActionDiv) { + _lastTickMsec = 0; + //process actions + handleActions(msec, false); + } + +//process speech + handleSpeech(msec); +} + + +bool Actor::calcScreenPosition(CommonObjectData *commonObjectData) { + int beginSlope, endSlope, middle; + bool result; + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _vm->_isoMap->tileCoordsToScreenPoint(commonObjectData->_location, commonObjectData->_screenPosition); + commonObjectData->_screenScale = 256; + } else { + middle = _vm->_scene->getHeight() - commonObjectData->_location.y / ACTOR_LMULT; + + _vm->_scene->getSlopes(beginSlope, endSlope); + + commonObjectData->_screenDepth = (14 * middle) / endSlope + 1; + + if (middle <= beginSlope) { + commonObjectData->_screenScale = 256; + } else if (middle >= endSlope) { + commonObjectData->_screenScale = 1; + } else { + middle -= beginSlope; + endSlope -= beginSlope; + commonObjectData->_screenScale = 256 - (middle * 256) / endSlope; + } + + commonObjectData->_location.toScreenPointXYZ(commonObjectData->_screenPosition); + } + + result = commonObjectData->_screenPosition.x > -64 && + commonObjectData->_screenPosition.x < _vm->getDisplayWidth() + 64 && + commonObjectData->_screenPosition.y > -64 && + commonObjectData->_screenPosition.y < _vm->_scene->getHeight() + 64; + + return result; +} + +uint16 Actor::hitTest(const Point &testPoint, bool skipProtagonist) { + // We can only interact with objects or actors that are inside the + // scene area. While this is usually the entire upper part of the + // screen, it could also be an inset. Note that other kinds of hit + // areas may be outside the inset, and that those are still perfectly + // fine to interact with. For example, the door entrance at the glass + // makers's house in ITE's ferret village. + + if (!_vm->_scene->getSceneClip().contains(testPoint)) + return ID_NOTHING; + + CommonObjectOrderList::iterator drawOrderIterator; + CommonObjectDataPointer drawObject; + int frameNumber; + SpriteList *spriteList; + + createDrawOrderList(); + + for (drawOrderIterator = _drawOrderList.begin(); drawOrderIterator != _drawOrderList.end(); ++drawOrderIterator) { + drawObject = drawOrderIterator.operator*(); + if (skipProtagonist && (drawObject == _protagonist)) { + continue; + } + if (!getSpriteParams(drawObject, frameNumber, spriteList)) { + continue; + } + if (_vm->_sprite->hitTest(*spriteList, frameNumber, drawObject->_screenPosition, drawObject->_screenScale, testPoint)) { + return drawObject->_id; + } + } + return ID_NOTHING; +} + +void Actor::createDrawOrderList() { + int i; + ActorData *actor; + ObjectData *obj; + CommonObjectOrderList::CompareFunction compareFunction; + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + compareFunction = &tileCommonObjectCompare; + } else { + compareFunction = &commonObjectCompare; + } + + _drawOrderList.clear(); + for (i = 0; i < _actorsCount; i++) { + actor = _actors[i]; + + if (!actor->_inScene) + continue; + + if (calcScreenPosition(actor)) { + _drawOrderList.pushBack(actor, compareFunction); + } + } + + for (i = 0; i < _objsCount; i++) { + obj = _objs[i]; + if (obj->_disabled) + continue; + + if (obj->_sceneNumber != _vm->_scene->currentSceneNumber()) + continue; + + if (calcScreenPosition(obj)) { + _drawOrderList.pushBack(obj, compareFunction); + } + } +} + +bool Actor::getSpriteParams(CommonObjectData *commonObjectData, int &frameNumber, SpriteList *&spriteList) { + if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) { + if (!(commonObjectData->_flags & kProtagonist)){ +// warning("not protagonist"); + return false; + } + frameNumber = 8; + spriteList = &_vm->_sprite->_mainSprites; + } else if (validActorId(commonObjectData->_id)) { + ActorData *actor = (ActorData *)commonObjectData; + spriteList = &(actor->_spriteList); + frameNumber = actor->_frameNumber; + if (spriteList->infoList == NULL) + loadActorSpriteList(actor); + + } else if (validObjId(commonObjectData->_id)) { + spriteList = &_vm->_sprite->_mainSprites; + frameNumber = commonObjectData->_spriteListResourceId; + } + + if ((frameNumber < 0) || (spriteList->spriteCount <= frameNumber)) { + debug(1, "Actor::getSpriteParams frameNumber invalid for %s id 0x%X (%d)", + validObjId(commonObjectData->_id) ? "object" : "actor", + commonObjectData->_id, frameNumber); + return false; + } + return true; +} + +void Actor::drawActors() { + if (_vm->_anim->hasCutaway()) { + drawSpeech(); + return; + } + + if (_vm->_scene->currentSceneNumber() <= 0) { + return; + } + + if (_vm->_scene->_entryList.entryListCount == 0) { + return; + } + + CommonObjectOrderList::iterator drawOrderIterator; + CommonObjectDataPointer drawObject; + int frameNumber; + SpriteList *spriteList; + + Surface *backBuffer; + + backBuffer = _vm->_gfx->getBackBuffer(); + + createDrawOrderList(); + + for (drawOrderIterator = _drawOrderList.begin(); drawOrderIterator != _drawOrderList.end(); ++drawOrderIterator) { + drawObject = drawOrderIterator.operator*(); + + if (!getSpriteParams(drawObject, frameNumber, spriteList)) { + continue; + } + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _vm->_isoMap->drawSprite(backBuffer, *spriteList, frameNumber, drawObject->_location, drawObject->_screenPosition, drawObject->_screenScale); + } else { + _vm->_sprite->drawOccluded(backBuffer, _vm->_scene->getSceneClip(),*spriteList, frameNumber, drawObject->_screenPosition, drawObject->_screenScale, drawObject->_screenDepth); + } + } + + drawSpeech(); +} + +void Actor::drawSpeech(void) { + if (!isSpeaking() || !_activeSpeech.playing || _vm->_script->_skipSpeeches + || (!_vm->_subtitlesEnabled && (_vm->getFeatures() & GF_CD_FX))) + return; + + int i; + Point textPoint; + ActorData *actor; + int width, height; + char oneChar[2]; + oneChar[1] = 0; + const char *outputString; + Surface *backBuffer; + + backBuffer = _vm->_gfx->getBackBuffer(); + + if (_activeSpeech.speechFlags & kSpeakSlow) { + outputString = oneChar; + oneChar[0] = _activeSpeech.strings[0][_activeSpeech.slowModeCharIndex]; + } else { + outputString = _activeSpeech.strings[0]; + } + + if (_activeSpeech.actorsCount > 1) { + height = _vm->_font->getHeight(kKnownFontScript); + width = _vm->_font->getStringWidth(kKnownFontScript, _activeSpeech.strings[0], 0, kFontNormal); + + for (i = 0; i < _activeSpeech.actorsCount; i++) { + actor = getActor(_activeSpeech.actorIds[i]); + calcScreenPosition(actor); + + textPoint.x = clamp(10, actor->_screenPosition.x - width / 2, _vm->getDisplayWidth() - 10 - width); + textPoint.y = clamp(10, actor->_screenPosition.y - 58, _vm->_scene->getHeight() - 10 - height); + + _vm->_font->textDraw(kKnownFontScript, backBuffer, _activeSpeech.strings[0], textPoint, + _activeSpeech.speechColor[i], _activeSpeech.outlineColor[i], _activeSpeech.getFontFlags(i)); + } + } else { + _vm->_font->textDrawRect(kKnownFontScript, backBuffer, _activeSpeech.strings[0], _activeSpeech.drawRect, _activeSpeech.speechColor[0], + _activeSpeech.outlineColor[0], _activeSpeech.getFontFlags(0)); + } +} + +bool Actor::followProtagonist(ActorData *actor) { + Location protagonistLocation; + Location newLocation; + Location delta; + int protagonistBGMaskType; + Point prefer1; + Point prefer2; + Point prefer3; + int16 prefU; + int16 prefV; + int16 newU; + int16 newV; + + assert(_protagonist); + + actor->_flags &= ~(kFaster | kFastest); + protagonistLocation = _protagonist->_location; + calcScreenPosition(_protagonist); + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + prefU = 60; + prefV = 60; + + + actor->_location.delta(protagonistLocation, delta); + + if (actor->_id == actorIndexToId(2)) { + prefU = prefV = 48; + } + + if ((delta.u() > prefU) || (delta.u() < -prefU) || (delta.v() > prefV) || (delta.v() < -prefV)) { + + if ((delta.u() > prefU * 2) || (delta.u() < -prefU * 2) || (delta.v() > prefV * 2) || (delta.v() < -prefV * 2)) { + actor->_flags |= kFaster; + + if ((delta.u() > prefU * 3) || (delta.u() < -prefU*3) || (delta.v() > prefV * 3) || (delta.v() < -prefV * 3)) { + actor->_flags |= kFastest; + } + } + + prefU /= 2; + prefV /= 2; + + newU = clamp(-prefU, delta.u(), prefU) + protagonistLocation.u(); + newV = clamp(-prefV, delta.v(), prefV) + protagonistLocation.v(); + + newLocation.u() = newU + _vm->_rnd.getRandomNumber(prefU - 1) - prefU / 2; + newLocation.v() = newV + _vm->_rnd.getRandomNumber(prefV - 1) - prefV / 2; + newLocation.z = 0; + + return actorWalkTo(actor->_id, newLocation); + } + + } else { + prefer1.x = (100 * _protagonist->_screenScale) >> 8; + prefer1.y = (50 * _protagonist->_screenScale) >> 8; + + if (_protagonist->_currentAction == kActionWalkDir) { + prefer1.x /= 2; + } + + if (prefer1.x < 8) { + prefer1.x = 8; + } + + if (prefer1.y < 8) { + prefer1.y = 8; + } + + prefer2.x = prefer1.x * 2; + prefer2.y = prefer1.y * 2; + prefer3.x = prefer1.x + prefer1.x / 2; + prefer3.y = prefer1.y + prefer1.y / 2; + + actor->_location.delta(protagonistLocation, delta); + + protagonistBGMaskType = 0; + if (_vm->_scene->isBGMaskPresent() && _vm->_scene->validBGMaskPoint(_protagonist->_screenPosition)) { + protagonistBGMaskType = _vm->_scene->getBGMaskType(_protagonist->_screenPosition); + } + + if ((_vm->_rnd.getRandomNumber(7) & 0x7) == 0) // 1/8th chance + actor->_actorFlags &= ~kActorNoFollow; + + if (actor->_actorFlags & kActorNoFollow) { + return false; + } + + if ((delta.x > prefer2.x) || (delta.x < -prefer2.x) || + (delta.y > prefer2.y) || (delta.y < -prefer2.y) || + ((_protagonist->_currentAction == kActionWait) && + (delta.x * 2 < prefer1.x) && (delta.x * 2 > -prefer1.x) && + (delta.y < prefer1.y) && (delta.y > -prefer1.y))) { + + if (ABS(delta.x) > ABS(delta.y)) { + + delta.x = (delta.x > 0) ? prefer3.x : -prefer3.x; + + newLocation.x = delta.x + protagonistLocation.x; + newLocation.y = clamp(-prefer2.y, delta.y, prefer2.y) + protagonistLocation.y; + } else { + delta.y = (delta.y > 0) ? prefer3.y : -prefer3.y; + + newLocation.x = clamp(-prefer2.x, delta.x, prefer2.x) + protagonistLocation.x; + newLocation.y = delta.y + protagonistLocation.y; + } + newLocation.z = 0; + + if (protagonistBGMaskType != 3) { + newLocation.x += _vm->_rnd.getRandomNumber(prefer1.x - 1) - prefer1.x / 2; + newLocation.y += _vm->_rnd.getRandomNumber(prefer1.y - 1) - prefer1.y / 2; + } + + newLocation.x = clamp(-31*4, newLocation.x, (_vm->getDisplayWidth() + 31) * 4); //fixme + + return actorWalkTo(actor->_id, newLocation); + } + } + return false; +} + +bool Actor::actorEndWalk(uint16 actorId, bool recurse) { + bool walkMore = false; + ActorData *actor; + const HitZone *hitZone; + int hitZoneIndex; + Point testPoint; + + actor = getActor(actorId); + actor->_actorFlags &= ~kActorBackwards; + + if (actor->_location.distance(actor->_finalTarget) > 8 && (actor->_flags & kProtagonist) && recurse && !(actor->_actorFlags & kActorNoCollide)) { + actor->_actorFlags |= kActorNoCollide; + return actorWalkTo(actorId, actor->_finalTarget); + } + + actor->_currentAction = kActionWait; + if (actor->_actorFlags & kActorFinalFace) { + actor->_facingDirection = actor->_actionDirection = (actor->_actorFlags >> 6) & 0x07; //? + } + + actor->_actorFlags &= ~(kActorNoCollide | kActorCollided | kActorFinalFace | kActorFacingMask); + actor->_flags &= ~(kFaster | kFastest); + + if (actor == _protagonist) { + _vm->_script->wakeUpActorThread(kWaitTypeWalk, actor); + if (_vm->_script->_pendingVerb == _vm->_script->getVerbType(kVerbWalkTo)) { + if (_vm->getGameType() == GType_ITE) + actor->_location.toScreenPointUV(testPoint); // it's wrong calculation, but it is used in ITE + else + actor->_location.toScreenPointXY(testPoint); + + hitZoneIndex = _vm->_scene->_actionMap->hitTest(testPoint); + if (hitZoneIndex != -1) { + hitZone = _vm->_scene->_actionMap->getHitZone(hitZoneIndex); + stepZoneAction(actor, hitZone, false, true); + } else { + _vm->_script->setNoPendingVerb(); + } + } else if (_vm->_script->_pendingVerb != _vm->_script->getVerbType(kVerbNone)) { + _vm->_script->doVerb(); + } + } else { + if (recurse && (actor->_flags & kFollower)) + walkMore = followProtagonist(actor); + + _vm->_script->wakeUpActorThread(kWaitTypeWalk, actor); + } + return walkMore; +} + +bool Actor::actorWalkTo(uint16 actorId, const Location &toLocation) { + ActorData *actor; + ActorData *anotherActor; + int i; + + Rect testBox; + Rect testBox2; + Point anotherActorScreenPosition; + Point collision; + Point pointFrom, pointTo, pointBest, pointAdd; + Point delta, bestDelta; + Point tempPoint; + bool extraStartNode; + bool extraEndNode; + + actor = getActor(actorId); + + if (actor == _protagonist) { + _vm->_scene->setDoorState(2, 0xff); + _vm->_scene->setDoorState(3, 0); + } else { + _vm->_scene->setDoorState(2, 0); + _vm->_scene->setDoorState(3, 0xff); + } + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + + if ((_vm->getGameType() == GType_ITE) && (actor->_index == ACTOR_DRAGON_INDEX)) { + return false; + } + + actor->_finalTarget = toLocation; + actor->_walkStepsCount = 0; + _vm->_isoMap->findTilePath(actor, actor->_location, toLocation); + + + if ((actor->_walkStepsCount == 0) && (actor->_flags & kProtagonist)) { + actor->_actorFlags |= kActorNoCollide; + _vm->_isoMap->findTilePath(actor, actor->_location, toLocation); + } + + actor->_walkStepIndex = 0; + if (_vm->_isoMap->nextTileTarget(actor)) { + actor->_currentAction = kActionWalkToPoint; + actor->_walkFrameSequence = getFrameType(kFrameWalk); + } else { + actorEndWalk(actorId, false); + return false; + } + } else { + + actor->_location.toScreenPointXY(pointFrom); + pointFrom.x &= ~1; + + extraStartNode = _vm->_scene->offscreenPath(pointFrom); + + toLocation.toScreenPointXY(pointTo); + pointTo.x &= ~1; + + extraEndNode = _vm->_scene->offscreenPath(pointTo); + + if (_vm->_scene->isBGMaskPresent()) { + + if ((((actor->_currentAction >= kActionWalkToPoint) && + (actor->_currentAction <= kActionWalkDir)) || (actor == _protagonist)) && + !_vm->_scene->canWalk(pointFrom)) { + for (i = 1; i < 8; i++) { + pointAdd = pointFrom; + pointAdd.y += i; + if (_vm->_scene->canWalk(pointAdd)) { + pointFrom = pointAdd; + break; + } + pointAdd = pointFrom; + pointAdd.y -= i; + if (_vm->_scene->canWalk(pointAdd)) { + pointFrom = pointAdd; + break; + } + pointAdd = pointFrom; + pointAdd.x += i; + if (_vm->_scene->canWalk(pointAdd)) { + pointFrom = pointAdd; + break; + } + pointAdd = pointFrom; + pointAdd.x -= i; + if (_vm->_scene->canWalk(pointAdd)) { + pointFrom = pointAdd; + break; + } + } + } + + _barrierCount = 0; + if (!(actor->_actorFlags & kActorNoCollide)) { + collision.x = ACTOR_COLLISION_WIDTH * actor->_screenScale / (256 * 2); + collision.y = ACTOR_COLLISION_HEIGHT * actor->_screenScale / (256 * 2); + + + for (i = 0; (i < _actorsCount) && (_barrierCount < ACTOR_BARRIERS_MAX); i++) { + anotherActor = _actors[i]; + if (!anotherActor->_inScene) + continue; + if (anotherActor == actor) + continue; + + anotherActorScreenPosition = anotherActor->_screenPosition; + testBox.left = (anotherActorScreenPosition.x - collision.x) & ~1; + testBox.right = (anotherActorScreenPosition.x + collision.x) & ~1 + 1; + testBox.top = anotherActorScreenPosition.y - collision.y; + testBox.bottom = anotherActorScreenPosition.y + collision.y + 1; + testBox2 = testBox; + testBox2.right += 2; + testBox2.left -= 2; + testBox2.top -= 1; + testBox2.bottom += 1; + + if (testBox2.contains(pointFrom)) { + if (pointFrom.x > anotherActorScreenPosition.x + 4) { + testBox.right = pointFrom.x - 1; + } else if (pointFrom.x < anotherActorScreenPosition.x - 4) { + testBox.left = pointFrom.x + 2; + } else if (pointFrom.y > anotherActorScreenPosition.y) { + testBox.bottom = pointFrom.y; + } else { + testBox.top = pointFrom.y + 1 ; + } + } + + if ((testBox.width() > 0) && (testBox.height() > 0)) { + _barrierList[_barrierCount++] = testBox; + } + } + } + + + pointBest = pointTo; + actor->_walkStepsCount = 0; + findActorPath(actor, pointFrom, pointTo); + + if (actor->_walkStepsCount == 0) { + error("actor->_walkStepsCount == 0"); + } + + if (extraStartNode) { + actor->_walkStepIndex = 0; + } else { + actor->_walkStepIndex = 1; + } + + if (extraEndNode) { + toLocation.toScreenPointXY(tempPoint); + actor->_walkStepsCount--; + actor->addWalkStepPoint(tempPoint); + } + + + pointBest = actor->_walkStepsPoints[actor->_walkStepsCount - 1]; + + pointBest.x &= ~1; + delta.x = ABS(pointFrom.x - pointTo.x); + delta.y = ABS(pointFrom.y - pointTo.y); + + bestDelta.x = ABS(pointBest.x - pointTo.x); + bestDelta.y = ABS(pointBest.y - pointTo.y); + + if ((delta.x + delta.y <= bestDelta.x + bestDelta.y) && (actor->_flags & kFollower)) { + actor->_actorFlags |= kActorNoFollow; + } + + if (pointBest == pointFrom) { + actor->_walkStepsCount = 0; + } + } else { + actor->_walkStepsCount = 0; + actor->addWalkStepPoint(pointTo); + actor->_walkStepIndex = 0; + } + + actor->_partialTarget = actor->_location; + actor->_finalTarget = toLocation; + if (actor->_walkStepsCount == 0) { + actorEndWalk(actorId, false); + return false; + } else { + if (actor->_flags & kProtagonist) { + _actors[1]->_actorFlags &= ~kActorNoFollow; // TODO: mark all actors with kFollower flag, not only 1 and 2 + _actors[2]->_actorFlags &= ~kActorNoFollow; + } + actor->_currentAction = (actor->_walkStepsCount >= ACTOR_MAX_STEPS_COUNT) ? kActionWalkToLink : kActionWalkToPoint; + actor->_walkFrameSequence = getFrameType(kFrameWalk); + } + } + return true; +} + +void Actor::actorSpeech(uint16 actorId, const char **strings, int stringsCount, int sampleResourceId, int speechFlags) { + ActorData *actor; + int i; + int16 dist; + + actor = getActor(actorId); + calcScreenPosition(actor); + for (i = 0; i < stringsCount; i++) { + _activeSpeech.strings[i] = strings[i]; + } + + _activeSpeech.stringsCount = stringsCount; + _activeSpeech.speechFlags = speechFlags; + _activeSpeech.actorsCount = 1; + _activeSpeech.actorIds[0] = actorId; + _activeSpeech.speechColor[0] = actor->_speechColor; + _activeSpeech.outlineColor[0] = (_vm->getGameType() == GType_ITE ? kITEColorBlack : kIHNMColorBlack); + _activeSpeech.sampleResourceId = sampleResourceId; + _activeSpeech.playing = false; + _activeSpeech.slowModeCharIndex = 0; + + dist = MIN(actor->_screenPosition.x - 10, _vm->getDisplayWidth() - 10 - actor->_screenPosition.x); + dist = clamp(60, dist, 150); + + _activeSpeech.speechBox.left = actor->_screenPosition.x - dist; + _activeSpeech.speechBox.right = actor->_screenPosition.x + dist; + + if (_activeSpeech.speechBox.left < 10) { + _activeSpeech.speechBox.right += 10 - _activeSpeech.speechBox.left; + _activeSpeech.speechBox.left = 10; + } + if (_activeSpeech.speechBox.right > _vm->getDisplayWidth() - 10) { + _activeSpeech.speechBox.left -= _activeSpeech.speechBox.right - _vm->getDisplayWidth() - 10; + _activeSpeech.speechBox.right = _vm->getDisplayWidth() - 10; + } +} + +void Actor::nonActorSpeech(const Common::Rect &box, const char **strings, int stringsCount, int sampleResourceId, int speechFlags) { + int i; + + _vm->_script->wakeUpThreads(kWaitTypeSpeech); + + for (i = 0; i < stringsCount; i++) { + _activeSpeech.strings[i] = strings[i]; + } + _activeSpeech.stringsCount = stringsCount; + _activeSpeech.speechFlags = speechFlags; + _activeSpeech.actorsCount = 1; + _activeSpeech.actorIds[0] = 0; + if (!(_vm->getFeatures() & GF_CD_FX)) + _activeSpeech.sampleResourceId = -1; + else + _activeSpeech.sampleResourceId = sampleResourceId; + _activeSpeech.playing = false; + _activeSpeech.slowModeCharIndex = 0; + _activeSpeech.speechBox = box; +} + +void Actor::simulSpeech(const char *string, uint16 *actorIds, int actorIdsCount, int speechFlags, int sampleResourceId) { + int i; + + for (i = 0; i < actorIdsCount; i++) { + ActorData *actor; + + actor = getActor(actorIds[i]); + _activeSpeech.actorIds[i] = actorIds[i]; + _activeSpeech.speechColor[i] = actor->_speechColor; + _activeSpeech.outlineColor[i] = 0; // disable outline + } + _activeSpeech.actorsCount = actorIdsCount; + _activeSpeech.strings[0] = string; + _activeSpeech.stringsCount = 1; + _activeSpeech.speechFlags = speechFlags; + _activeSpeech.sampleResourceId = sampleResourceId; + _activeSpeech.playing = false; + _activeSpeech.slowModeCharIndex = 0; + + // caller should call thread->wait(kWaitTypeSpeech) by itself +} + +void Actor::abortAllSpeeches() { + abortSpeech(); + + if (_vm->_script->_abortEnabled) + _vm->_script->_skipSpeeches = true; + + for (int i = 0; i < 10; i++) + _vm->_script->executeThreads(0); +} + +void Actor::abortSpeech() { + _vm->_sound->stopVoice(); + _activeSpeech.playingTime = 0; +} + +void Actor::moveDragon(ActorData *actor) { + int16 dir0, dir1, dir2, dir3; + int16 moveType; + Event event; + const DragonMove *dragonMove; + + if ((actor->_actionCycle < 0) || + ((actor->_actionCycle == 0) && (actor->_dragonMoveType >= ACTOR_DRAGON_TURN_MOVES))) { + + moveType = kDragonMoveInvalid; + if (actor->_location.distance(_protagonist->_location) < 24) { + if (_dragonHunt && (_protagonist->_currentAction != kActionFall)) { + event.type = kEvTOneshot; + event.code = kScriptEvent; + event.op = kEventExecNonBlocking; + event.time = 0; + event.param = _vm->_scene->getScriptModuleNumber(); // module number + event.param2 = ACTOR_EXP_KNOCK_RIF; // script entry point number + event.param3 = -1; // Action + event.param4 = -1; // Object + event.param5 = -1; // With Object + event.param6 = -1; // Actor + + _vm->_events->queue(&event); + _dragonHunt = false; + } + } else { + _dragonHunt = true; + } + + if (actor->_walkStepIndex + 2 > actor->_walkStepsCount) { + + _vm->_isoMap->findDragonTilePath(actor, actor->_location, _protagonist->_location, actor->_actionDirection); + + if (actor->_walkStepsCount == 0) { + _vm->_isoMap->findDragonTilePath(actor, actor->_location, _protagonist->_location, 0); + } + + if (actor->_walkStepsCount < 2) { + return; + } + + actor->_partialTarget = actor->_location; + actor->_finalTarget = _protagonist->_location; + actor->_walkStepIndex = 0; + } + + dir0 = actor->_actionDirection; + dir1 = actor->_tileDirections[actor->_walkStepIndex++]; + dir2 = actor->_tileDirections[actor->_walkStepIndex]; + dir3 = actor->_tileDirections[actor->_walkStepIndex + 1]; + + if (dir0 != dir1){ + actor->_actionDirection = dir0 = dir1; + } + + actor->_location = actor->_partialTarget; + + if ((dir1 != dir2) && (dir1 == dir3)) { + switch (dir1) { + case kDirUpLeft: + actor->_partialTarget.v() += 16; + moveType = kDragonMoveUpLeft; + break; + case kDirDownLeft: + actor->_partialTarget.u() -= 16; + moveType = kDragonMoveDownLeft; + break; + case kDirDownRight: + actor->_partialTarget.v() -= 16; + moveType = kDragonMoveDownRight; + break; + case kDirUpRight: + actor->_partialTarget.u() += 16; + moveType = kDragonMoveUpRight; + break; + } + + switch (dir2) { + case kDirUpLeft: + actor->_partialTarget.v() += 16; + break; + case kDirDownLeft: + actor->_partialTarget.u() -= 16; + break; + case kDirDownRight: + actor->_partialTarget.v() -= 16; + break; + case kDirUpRight: + actor->_partialTarget.u() += 16; + break; + } + + actor->_walkStepIndex++; + } else { + switch (dir1) { + case kDirUpLeft: + actor->_partialTarget.v() += 16; + switch (dir2) { + case kDirDownLeft: + moveType = kDragonMoveUpLeft_Left; + actor->_partialTarget.u() -= 16; + break; + case kDirUpLeft: + moveType = kDragonMoveUpLeft; + break; + case kDirUpRight: + actor->_partialTarget.u() += 16; + moveType = kDragonMoveUpLeft_Right; + break; + default: + actor->_actionDirection = dir1; + actor->_walkStepsCount = 0; + break; + } + break; + case kDirDownLeft: + actor->_partialTarget.u() -= 16; + switch (dir2) { + case kDirDownRight: + moveType = kDragonMoveDownLeft_Left; + actor->_partialTarget.v() -= 16; + break; + case kDirDownLeft: + moveType = kDragonMoveDownLeft; + break; + case kDirUpLeft: + moveType = kDragonMoveDownLeft_Right; + actor->_partialTarget.v() += 16; + break; + default: + actor->_actionDirection = dir1; + actor->_walkStepsCount = 0; + break; + } + break; + case kDirDownRight: + actor->_partialTarget.v() -= 16; + switch (dir2) { + case kDirUpRight: + moveType = kDragonMoveDownRight_Left; + actor->_partialTarget.u() += 16; + break; + case kDirDownRight: + moveType = kDragonMoveDownRight; + break; + case kDirDownLeft: + moveType = kDragonMoveDownRight_Right; + actor->_partialTarget.u() -= 16; + break; + default: + actor->_actionDirection = dir1; + actor->_walkStepsCount = 0; + break; + } + break; + case kDirUpRight: + actor->_partialTarget.u() += 16; + switch (dir2) { + case kDirUpLeft: + moveType = kDragonMoveUpRight_Left; + actor->_partialTarget.v() += 16; + break; + case kDirUpRight: + moveType = kDragonMoveUpRight; + break; + case kDirDownRight: + moveType = kDragonMoveUpRight_Right; + actor->_partialTarget.v() -= 16; + break; + default: + actor->_actionDirection = dir1; + actor->_walkStepsCount = 0; + break; + } + break; + + default: + actor->_actionDirection = dir1; + actor->_walkStepsCount = 0; + break; + } + } + + actor->_dragonMoveType = moveType; + + if (moveType >= ACTOR_DRAGON_TURN_MOVES) { + actor->_dragonStepCycle = 0; + actor->_actionCycle = 4; + actor->_walkStepIndex++; + } else { + actor->_actionCycle = 4; + } + } + + actor->_actionCycle--; + + if ((actor->_walkStepsCount < 1) || (actor->_actionCycle < 0)) { + return; + } + + if (actor->_dragonMoveType < ACTOR_DRAGON_TURN_MOVES) { + + actor->_dragonStepCycle++; + if (actor->_dragonStepCycle >= 7) { + actor->_dragonStepCycle = 0; + } + + actor->_dragonBaseFrame = actor->_dragonMoveType * 7; + + if (actor->_location.u() > actor->_partialTarget.u() + 3) { + actor->_location.u() -= 4; + } else if (actor->_location.u() < actor->_partialTarget.u() - 3) { + actor->_location.u() += 4; + } else { + actor->_location.u() = actor->_partialTarget.u(); + } + + if (actor->_location.v() > actor->_partialTarget.v() + 3) { + actor->_location.v() -= 4; + } else if (actor->_location.v() < actor->_partialTarget.v() - 3) { + actor->_location.v() += 4; + } else { + actor->_location.v() = actor->_partialTarget.v(); + } + } else { + dragonMove = &dragonMoveTable[actor->_dragonMoveType]; + actor->_dragonBaseFrame = dragonMove->baseFrame; + + + actor->_location.u() = actor->_partialTarget.u() - dragonMove->offset[actor->_actionCycle][0]; + actor->_location.v() = actor->_partialTarget.v() - dragonMove->offset[actor->_actionCycle][1]; + + actor->_dragonStepCycle++; + if (actor->_dragonStepCycle >= 3) { + actor->_dragonStepCycle = 3; + } + } + + actor->_frameNumber = actor->_dragonBaseFrame + actor->_dragonStepCycle; +} + +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 + + 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); + } 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; + bool interchange = false; + Point fDelta; + int errterm; + int s1; + int s2; + int i; + + point = point1; + delta.x = ABS(point1.x - point2.x); + delta.y = ABS(point1.y - point2.y); + s1 = integerCompare(point2.x, point1.x); + s2 = integerCompare(point2.y, point1.y); + + if (delta.y > delta.x) { + SWAP(delta.y, delta.x); + interchange = true; + } + + fDelta.x = delta.x * 2; + fDelta.y = delta.y * 2; + + errterm = fDelta.y - delta.x; + + for (i = 0; i < delta.x; i++) { + while (errterm >= 0) { + if (interchange) { + point.x += s1; + } else { + point.y += s2; + } + errterm -= fDelta.x; + } + + if (interchange) + point.y += s2; + else + point.x += s1; + + errterm += fDelta.y; + + if (!validPathCellPoint(point)) { + return false; + } + if (getPathCell(point) == kPathCellBarrier) { + return false; + } + } + return true; +} + +int Actor::fillPathArray(const Point &fromPoint, const Point &toPoint, Point &bestPoint) { + int bestRating; + int currentRating; + int i; + Point bestPath; + int pointCounter; + int startDirection; + PathDirectionData *pathDirection; + PathDirectionData *newPathDirection; + const PathDirectionData *samplePathDirection; + Point nextPoint; + int directionCount; + + _pathDirectionListCount = 0; + pointCounter = 0; + bestRating = quickDistance(fromPoint, toPoint); + bestPath = fromPoint; + + for (startDirection = 0; startDirection < 4; startDirection++) { + newPathDirection = addPathDirectionListData(); + newPathDirection->x = fromPoint.x; + newPathDirection->y = fromPoint.y; + newPathDirection->direction = startDirection; + } + + if (validPathCellPoint(fromPoint)) { + setPathCell(fromPoint, kDirUp); + +#ifdef ACTOR_DEBUG + addDebugPoint(fromPoint, 24+36); +#endif + } + + i = 0; + + do { + pathDirection = &_pathDirectionList[i]; + for (directionCount = 0; directionCount < 3; directionCount++) { + samplePathDirection = &pathDirectionLUT[pathDirection->direction][directionCount]; + nextPoint.x = samplePathDirection->x + pathDirection->x; + nextPoint.y = samplePathDirection->y + pathDirection->y; + + if (!validPathCellPoint(nextPoint)) { + continue; + } + + if (getPathCell(nextPoint) != kPathCellEmpty) { + continue; + } + + setPathCell(nextPoint, samplePathDirection->direction); + +#ifdef ACTOR_DEBUG + addDebugPoint(nextPoint, samplePathDirection->direction + 96); +#endif + newPathDirection = addPathDirectionListData(); + newPathDirection->x = nextPoint.x; + newPathDirection->y = nextPoint.y; + newPathDirection->direction = samplePathDirection->direction; + ++pointCounter; + if (nextPoint == toPoint) { + bestPoint = toPoint; + return pointCounter; + } + currentRating = quickDistance(nextPoint, toPoint); + if (currentRating < bestRating) { + bestRating = currentRating; + bestPath = nextPoint; + } + pathDirection = &_pathDirectionList[i]; + } + ++i; + } while (i < _pathDirectionListCount); + + bestPoint = bestPath; + return pointCounter; +} + +void Actor::setActorPath(ActorData *actor, const Point &fromPoint, const Point &toPoint) { + Point nextPoint; + int8 direction; + int i; + + _pathListIndex = -1; + addPathListPoint(toPoint); + nextPoint = toPoint; + + 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]; + addPathListPoint(nextPoint); + +#ifdef ACTOR_DEBUG + addDebugPoint(nextPoint, 0x8a); +#endif + } + + pathToNode(); + removeNodes(); + nodeToPath(); + removePathPoints(); + + for (i = 0; i <= _pathNodeListIndex; i++) { + actor->addWalkStepPoint(_pathNodeList[i].point); + } +} + +void Actor::pathToNode() { + Point point1, point2, delta; + int direction; + int i; + Point *point; + + point= &_pathList[_pathListIndex]; + direction = 0; + + _pathNodeListIndex = -1; + addPathNodeListPoint(*point); + + for (i = _pathListIndex; i > 0; i--) { + point1 = *point; + --point; + point2 = *point; + if (direction == 0) { + delta.x = integerCompare(point2.x, point1.x); + delta.y = integerCompare(point2.y, point1.y); + direction++; + } + if ((point1.x + delta.x != point2.x) || (point1.y + delta.y != point2.y)) { + addPathNodeListPoint(point1); + direction--; + i++; + point++; + } + } + addPathNodeListPoint(*_pathList); +} + +int pathLine(Point *pointList, const Point &point1, const Point &point2) { + Point point; + Point delta; + Point tempPoint; + int s1; + int s2; + bool interchange = false; + int errterm; + int i; + + delta.x = abs(point2.x - point1.x); + delta.y = abs(point2.y - point1.y); + point = point1; + s1 = integerCompare(point2.x, point1.x); + s2 = integerCompare(point2.y, point1.y); + + if (delta.y > delta.x) { + SWAP(delta.y, delta.x); + interchange = true; + } + + tempPoint.x = delta.x * 2; + tempPoint.y = delta.y * 2; + + errterm = tempPoint.y - delta.x; + + for (i = 0; i < delta.x; i++) { + while (errterm >= 0) { + if (interchange) { + point.x += s1; + } else { + point.y += s2; + } + errterm -= tempPoint.x; + } + if (interchange) { + point.y += s2; + } else { + point.x += s1; + } + errterm += tempPoint.y; + + pointList[i] = point; + } + return delta.x; +} + +void Actor::nodeToPath() { + int i; + Point point1, point2; + PathNode *node; + Point *point; + + for (i = 0, point = _pathList; i < _pathListAlloced; i++, point++) { + point->x = point->y = PATH_NODE_EMPTY; + } + + _pathListIndex = 1; + _pathList[0] = _pathNodeList[0].point; + _pathNodeList[0].link = 0; + for (i = 0, node = _pathNodeList; i < _pathNodeListIndex; i++) { + point1 = node->point; + node++; + point2 = node->point; + _pathListIndex += pathLine(&_pathList[_pathListIndex], point1, point2); + node->link = _pathListIndex - 1; + } + _pathListIndex--; + _pathNodeList[_pathNodeListIndex].link = _pathListIndex; + +} + +void Actor::removeNodes() { + int i, j, k; + PathNode *iNode, *jNode, *kNode, *fNode; + fNode = &_pathNodeList[_pathNodeListIndex]; + + if (scanPathLine(_pathNodeList[0].point, fNode->point)) { + _pathNodeList[1] = *fNode; + _pathNodeListIndex = 1; + } + + if (_pathNodeListIndex < 4) { + return; + } + + for (i = _pathNodeListIndex - 1, iNode = fNode-1; i > 1 ; i--, iNode--) { + if (iNode->point.x == PATH_NODE_EMPTY) { + continue; + } + + if (scanPathLine(_pathNodeList[0].point, iNode->point)) { + for (j = 1, jNode = _pathNodeList + 1; j < i; j++, jNode++) { + jNode->point.x = PATH_NODE_EMPTY; + } + } + } + + for (i = 1, iNode = _pathNodeList + 1; i < _pathNodeListIndex - 1; i++, iNode++) { + if (iNode->point.x == PATH_NODE_EMPTY) { + continue; + } + + if (scanPathLine(fNode->point, iNode->point)) { + for (j = i + 1, jNode = iNode + 1; j < _pathNodeListIndex; j++, jNode++) { + jNode->point.x = PATH_NODE_EMPTY; + } + } + } + condenseNodeList(); + + for (i = 1, iNode = _pathNodeList + 1; i < _pathNodeListIndex - 1; i++, iNode++) { + if (iNode->point.x == PATH_NODE_EMPTY) { + continue; + } + for (j = i + 2, jNode = iNode + 2; j < _pathNodeListIndex; j++, jNode++) { + if (jNode->point.x == PATH_NODE_EMPTY) { + continue; + } + + if (scanPathLine(iNode->point, jNode->point)) { + for (k = i + 1,kNode = iNode + 1; k < j; k++, kNode++) { + kNode->point.x = PATH_NODE_EMPTY; + } + } + } + } + condenseNodeList(); +} + +void Actor::condenseNodeList() { + int i, j, count; + PathNode *iNode, *jNode; + + count = _pathNodeListIndex; + + for (i = 1, iNode = _pathNodeList + 1; i < _pathNodeListIndex; i++, iNode++) { + if (iNode->point.x == PATH_NODE_EMPTY) { + j = i + 1; + jNode = iNode + 1; + while (jNode->point.x == PATH_NODE_EMPTY) { + j++; + jNode++; + } + *iNode = *jNode; + count = i; + jNode->point.x = PATH_NODE_EMPTY; + if (j == _pathNodeListIndex) { + break; + } + } + } + _pathNodeListIndex = count; +} + +void Actor::removePathPoints() { + int i, j, k, l; + PathNode *node; + int start; + int end; + Point point1, point2; + + if (_pathNodeListIndex < 2) + return; + + _newPathNodeListIndex = -1; + addNewPathNodeListPoint(_pathNodeList[0]); + + for (i = 1, node = _pathNodeList + 1; i < _pathNodeListIndex; i++, node++) { + addNewPathNodeListPoint(*node); + + for (j = 5; j > 0; j--) { + start = node->link - j; + end = node->link + j; + + if (start < 0 || end > _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 <= _newPathNodeListIndex; l++) { + if (start <= _newPathNodeList[l].link) { + _newPathNodeListIndex = l; + _newPathNodeList[_newPathNodeListIndex].point = point1; + _newPathNodeList[_newPathNodeListIndex].link = start; + incrementNewPathNodeListIndex(); + break; + } + } + _newPathNodeList[_newPathNodeListIndex].point = point2; + _newPathNodeList[_newPathNodeListIndex].link = end; + + for (k = start + 1; k < end; k++) { + _pathList[k].x = PATH_NODE_EMPTY; + } + break; + } + } + } + + addNewPathNodeListPoint(_pathNodeList[_pathNodeListIndex]); + + for (i = 0, j = 0; i <= _newPathNodeListIndex; i++) { + if (_newPathNodeListIndex == i || (_newPathNodeList[i].point != _newPathNodeList[i+1].point)) { + _pathNodeList[j++] = _newPathNodeList[i]; + } + } + _pathNodeListIndex = j - 1; +} + +void Actor::drawPathTest() { +#ifdef ACTOR_DEBUG + int i; + Surface *surface; + surface = _vm->_gfx->getBackBuffer(); + if (_debugPoints == NULL) { + return; + } + + for (i = 0; i < _debugPointsCount; i++) { + *((byte *)surface->pixels + (_debugPoints[i].point.y * surface->pitch) + _debugPoints[i].point.x) = _debugPoints[i].color; + } +#endif +} + +void Actor::saveState(Common::OutSaveFile *out) { + uint16 i; + + out->writeSint16LE(getProtagState()); + + for (i = 0; i < _actorsCount; i++) { + ActorData *a = _actors[i]; + a->saveState(out); + } + + for (i = 0; i < _objsCount; i++) { + ObjectData *o = _objs[i]; + o->saveState(out); + } +} + +void Actor::loadState(Common::InSaveFile *in) { + int32 i; + + setProtagState(in->readSint16LE()); + + for (i = 0; i < _actorsCount; i++) { + ActorData *a = _actors[i]; + a->loadState(_vm->getCurrentLoadVersion(), in); + + // Fix bug #1258633 "ITE: Second Rif appears in wall of dog castle prison" + // For some reason in some cases actor position is all wrong, so Rif + // crawls to his original poition + if (i == 122 && _vm->getGameType() == GType_ITE) { + a->_location.x = 130; + a->_location.y = 55; + } + } + + for (i = 0; i < _objsCount; i++) { + ObjectData *o = _objs[i]; + o->loadState(in); + } +} + +// Console wrappers - must be safe to run + +void Actor::cmdActorWalkTo(int argc, const char **argv) { + uint16 actorId = (uint16) atoi(argv[1]); + Location location; + Point movePoint; + + movePoint.x = atoi(argv[2]); + movePoint.y = atoi(argv[3]); + + location.fromScreenPoint(movePoint); + + if (!validActorId(actorId)) { + _vm->_console->DebugPrintf("Actor::cmActorWalkTo Invalid actorId 0x%X.\n", actorId); + return; + } + + actorWalkTo(actorId, location); +} + +} // End of namespace Saga diff --git a/engines/saga/actor.h b/engines/saga/actor.h new file mode 100644 index 0000000000..74f1abd689 --- /dev/null +++ b/engines/saga/actor.h @@ -0,0 +1,778 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// 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/list.h" +#include "saga/saga.h" +#include "saga/font.h" + +namespace Saga { + +class HitZone; + + +//#define ACTOR_DEBUG //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_CLIMB_SPEED 8 + +#define ACTOR_COLLISION_WIDTH 32 +#define ACTOR_COLLISION_HEIGHT 8 + +#define ACTOR_DIRECTIONS_COUNT 4 // for ActorFrameSequence +#define ACTOR_DIRECTION_RIGHT 0 +#define ACTOR_DIRECTION_LEFT 1 +#define ACTOR_DIRECTION_BACK 2 +#define ACTOR_DIRECTION_FORWARD 3 + +#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 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 SpeechFlags { + kSpeakNoAnimate = 1, + kSpeakAsync = 2, + kSpeakSlow = 4 +}; + +enum ActorFrameTypes { + kFrameStand, + kFrameWalk, + kFrameSpeak, + kFrameGive, + kFrameGesture, + kFrameWait, + kFramePickUp, + kFrameLook +}; + +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; + int16 x; + int16 y; +}; + +struct ActorFrameRange { + int frameIndex; + int frameCount; +}; + +struct ActorFrameSequence { + ActorFrameRange directions[ACTOR_DIRECTIONS_COUNT]; +}; + +int pathLine(Point *pointList, 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::MemoryReadStream &stream) { + x = stream.readUint16LE(); + y = stream.readUint16LE(); + z = stream.readUint16LE(); + } + + void debugPrint(int debuglevel = 0, const char *loc = "Loc:") const { + debug(debuglevel, "%s %d, %d, %d", loc, x, y, z); + } +}; + +class CommonObjectData { +public: +//constant + bool _disabled; // disabled in init section + 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(); + } +}; + +typedef CommonObjectData *CommonObjectDataPointer; + +typedef SortedList<CommonObjectDataPointer> CommonObjectOrderList; + +class ObjectData: public CommonObjectData { +public: + //constant + uint16 _interactBits; + ObjectData() { + memset(this, 0, sizeof(*this)); + } +}; + +class ActorData: public CommonObjectData { +public: + //constant + SpriteList _spriteList; // sprite list data + + ActorFrameSequence *_frames; // Actor's frames + int _framesCount; // Actor's frames count + 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 + + int32 _tileDirectionsAlloced; + byte *_tileDirections; + + int32 _walkStepsAlloced; + Point *_walkStepsPoints; + + int32 _walkStepsCount; + int32 _walkStepIndex; + + Location _finalTarget; + Location _partialTarget; + int32 _walkFrameSequence; + +public: + void saveState(Common::OutSaveFile *out) { + int i = 0; + CommonObjectData::saveState(out); + out->writeUint16LE(_actorFlags); + out->writeSint32LE(_currentAction); + out->writeSint32LE(_facingDirection); + out->writeSint32LE(_actionDirection); + out->writeSint32LE(_actionCycle); + out->writeUint16LE(_targetObject); + + out->writeSint32LE(_cycleFrameSequence); + out->writeByte(_cycleDelay); + out->writeByte(_cycleTimeCount); + out->writeByte(_cycleFlags); + out->writeSint16LE(_fallVelocity); + out->writeSint16LE(_fallAcceleration); + out->writeSint16LE(_fallPosition); + out->writeByte(_dragonBaseFrame); + out->writeByte(_dragonStepCycle); + out->writeByte(_dragonMoveType); + out->writeSint32LE(_frameNumber); + + out->writeSint32LE(_tileDirectionsAlloced); + for (i = 0; i < _tileDirectionsAlloced; i++) { + out->writeByte(_tileDirections[i]); + } + + out->writeSint32LE(_walkStepsAlloced); + for (i = 0; i < _walkStepsAlloced; i++) { + out->writeSint16LE(_walkStepsPoints[i].x); + out->writeSint16LE(_walkStepsPoints[i].y); + } + + out->writeSint32LE(_walkStepsCount); + out->writeSint32LE(_walkStepIndex); + _finalTarget.saveState(out); + _partialTarget.saveState(out); + out->writeSint32LE(_walkFrameSequence); + } + + void loadState(uint32 version, Common::InSaveFile *in) { + int i = 0; + CommonObjectData::loadState(in); + _actorFlags = in->readUint16LE(); + _currentAction = in->readSint32LE(); + _facingDirection = in->readSint32LE(); + _actionDirection = in->readSint32LE(); + _actionCycle = in->readSint32LE(); + _targetObject = in->readUint16LE(); + + _lastZone = NULL; + _cycleFrameSequence = in->readSint32LE(); + _cycleDelay = in->readByte(); + _cycleTimeCount = in->readByte(); + _cycleFlags = in->readByte(); + if (version > 1) { + _fallVelocity = in->readSint16LE(); + _fallAcceleration = in->readSint16LE(); + _fallPosition = in->readSint16LE(); + } else { + _fallVelocity = _fallAcceleration = _fallPosition = 0; + } + if (version > 2) { + _dragonBaseFrame = in->readByte(); + _dragonStepCycle = in->readByte(); + _dragonMoveType = in->readByte(); + } else { + _dragonBaseFrame = _dragonStepCycle = _dragonMoveType = 0; + } + + _frameNumber = in->readSint32LE(); + + + setTileDirectionsSize(in->readSint32LE(), true); + for (i = 0; i < _tileDirectionsAlloced; i++) { + _tileDirections[i] = in->readByte(); + } + + setWalkStepsPointsSize(in->readSint32LE(), true); + for (i = 0; i < _walkStepsAlloced; i++) { + _walkStepsPoints[i].x = in->readSint16LE(); + _walkStepsPoints[i].y = in->readSint16LE(); + } + + _walkStepsCount = in->readSint32LE(); + _walkStepIndex = in->readSint32LE(); + _finalTarget.loadState(in); + _partialTarget.loadState(in); + _walkFrameSequence = in->readSint32LE(); + } + + void setTileDirectionsSize(int size, bool forceRealloc) { + if ((size <= _tileDirectionsAlloced) && !forceRealloc) { + return; + } + _tileDirectionsAlloced = size; + _tileDirections = (byte*)realloc(_tileDirections, _tileDirectionsAlloced * sizeof(*_tileDirections)); + } + + void cycleWrap(int cycleLimit) { + if (_actionCycle >= cycleLimit) + _actionCycle = 0; + } + + void setWalkStepsPointsSize(int size, bool forceRealloc) { + if ((size <= _walkStepsAlloced) && !forceRealloc) { + return; + } + _walkStepsAlloced = size; + _walkStepsPoints = (Point*)realloc(_walkStepsPoints, _walkStepsAlloced * sizeof(*_walkStepsPoints)); + } + + void addWalkStepPoint(const Point &point) { + setWalkStepsPointsSize(_walkStepsCount + 1, false); + _walkStepsPoints[_walkStepsCount++] = point; + } + + void freeSpriteList() { + _spriteList.freeMem(); + } + + ActorData() { + memset(this, 0, sizeof(*this)); + } + ~ActorData() { + free(_frames); + free(_tileDirections); + free(_walkStepsPoints); + freeSpriteList(); + } +}; + + + +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() { + memset(this, 0, sizeof(*this)); + } + + FontEffectFlags getFontFlags(int i) { + if (outlineColor[i] != 0) { + return kFontOutline; + } else { + return kFontNormal; + } + } +}; + + + +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, _actorsCount))); } + 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, _objsCount)); } + 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(); + + void drawPathTest(); + + 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; + } + + void saveState(Common::OutSaveFile *out); + void loadState(Common::InSaveFile *in); + + void setProtagState(int state); + int getProtagState() { return _protagState; } + + void freeActorList(); + void loadActorList(int protagonistIdx, int actorCount, int actorsResourceID, + int protagStatesCount, int protagStatesResourceID); + void freeObjList(); + void loadObjList(int objectCount, int objectsResourceID); + + /* + uint16 _currentFrameIndex; + void frameTest() { + _currentFrameIndex++; + }*/ +protected: + friend class Script; + bool loadActorResources(ActorData *actor); + +private: + void stepZoneAction(ActorData *actor, const HitZone *hitZone, bool exit, bool stopped); + void loadActorSpriteList(ActorData *actor); + + 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) { + if (!validPathCellPoint(testPoint)) { + error("Actor::setPathCell wrong point"); + } + _pathCell[testPoint.x + testPoint.y * _xCellCount] = value; + } + int8 getPathCell(const Point &testPoint) { + if (!validPathCellPoint(testPoint)) { + error("Actor::getPathCell wrong point"); + } + 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 + int _actorsCount; + ActorData **_actors; + + int _objsCount; + ObjectData **_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: + ActorFrameSequence *_protagStates; + int _protagStatesCount; + +//path stuff + struct PathNode { + Point point; + int link; + }; + + Rect _barrierList[ACTOR_BARRIERS_MAX]; + int _barrierCount; + int8 *_pathCell; + + int _xCellCount; + int _yCellCount; + Rect _pathRect; + + PathDirectionData *_pathDirectionList; + int _pathDirectionListCount; + int _pathDirectionListAlloced; + PathDirectionData * addPathDirectionListData() { + if (_pathDirectionListCount + 1 >= _pathDirectionListAlloced) { + _pathDirectionListAlloced += 100; + _pathDirectionList = (PathDirectionData*) realloc(_pathDirectionList, _pathDirectionListAlloced * sizeof(*_pathDirectionList)); + } + return &_pathDirectionList[_pathDirectionListCount++]; + } + + Point *_pathList; + int _pathListIndex; + int _pathListAlloced; + void addPathListPoint(const Point &point) { + ++_pathListIndex; + if (_pathListIndex >= _pathListAlloced) { + _pathListAlloced += 100; + _pathList = (Point*) realloc(_pathList, _pathListAlloced * sizeof(*_pathList)); + + } + _pathList[_pathListIndex] = point; + } + + int _pathNodeListIndex; + int _pathNodeListAlloced; + PathNode *_pathNodeList; + void addPathNodeListPoint(const Point &point) { + ++_pathNodeListIndex; + if (_pathNodeListIndex >= _pathNodeListAlloced) { + _pathNodeListAlloced += 100; + _pathNodeList = (PathNode*) realloc(_pathNodeList, _pathNodeListAlloced * sizeof(*_pathNodeList)); + + } + _pathNodeList[_pathNodeListIndex].point = point; + } + + int _newPathNodeListIndex; + int _newPathNodeListAlloced; + PathNode *_newPathNodeList; + void incrementNewPathNodeListIndex() { + ++_newPathNodeListIndex; + if (_newPathNodeListIndex >= _newPathNodeListAlloced) { + _newPathNodeListAlloced += 100; + _newPathNodeList = (PathNode*) realloc(_newPathNodeList, _newPathNodeListAlloced * sizeof(*_newPathNodeList)); + + } + } + void addNewPathNodeListPoint(const PathNode &pathNode) { + incrementNewPathNodeListIndex(); + _newPathNodeList[_newPathNodeListIndex] = pathNode; + } + +public: +#ifdef ACTOR_DEBUG +//path debug - use with care + struct DebugPoint { + Point point; + byte color; + }; + DebugPoint *_debugPoints; + int _debugPointsCount; + int _debugPointsAlloced; + void addDebugPoint(const Point &point, byte color) { + if (_debugPointsCount + 1 > _debugPointsAlloced) { + _debugPointsAlloced += 1000; + _debugPoints = (DebugPoint*) realloc(_debugPoints, _debugPointsAlloced * sizeof(*_debugPoints)); + } + _debugPoints[_debugPointsCount].color = color; + _debugPoints[_debugPointsCount++].point = point; + } +#endif +}; + +inline int16 quickDistance(const Point &point1, const Point &point2) { + Point delta; + delta.x = ABS(point1.x - point2.x) / 2; + delta.y = ABS(point1.y - point2.y); + return ((delta.x < delta.y) ? (delta.y + delta.x / 2) : (delta.x + delta.y / 2)); +} +} // End of namespace Saga + +#endif diff --git a/engines/saga/animation.cpp b/engines/saga/animation.cpp new file mode 100644 index 0000000000..016070dc50 --- /dev/null +++ b/engines/saga/animation.cpp @@ -0,0 +1,720 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Background animation management module +#include "saga/saga.h" +#include "saga/gfx.h" + +#include "saga/console.h" +#include "saga/events.h" +#include "saga/interface.h" +#include "saga/render.h" +#include "saga/rscfile.h" +#include "saga/scene.h" + +#include "saga/animation.h" + +namespace Saga { + +Anim::Anim(SagaEngine *vm) : _vm(vm) { + uint16 i; + + _cutawayList = NULL; + _cutawayListLength = 0; + _cutawayActive = false; + + for (i = 0; i < MAX_ANIMATIONS; i++) + _animations[i] = NULL; + + for (i = 0; i < ARRAYSIZE(_cutawayAnimations); i++) + _cutawayAnimations[i] = NULL; +} + +Anim::~Anim(void) { + reset(); +} + +void Anim::loadCutawayList(const byte *resourcePointer, size_t resourceLength) { + free(_cutawayList); + _cutawayListLength = resourceLength / 8; + _cutawayList = (Cutaway *)malloc(_cutawayListLength * sizeof(Cutaway)); + + MemoryReadStream cutawayS(resourcePointer, resourceLength); + + for (int i = 0; i < _cutawayListLength; i++) { + _cutawayList[i].backgroundResourceId = cutawayS.readUint16LE(); + _cutawayList[i].animResourceId = cutawayS.readUint16LE(); + _cutawayList[i].cycles = cutawayS.readSint16LE(); + _cutawayList[i].frameRate = cutawayS.readSint16LE(); + } +} + +void Anim::freeCutawayList(void) { + free(_cutawayList); + _cutawayList = NULL; + _cutawayListLength = 0; +} + +void Anim::playCutaway(int cut, bool fade) { + debug(0, "playCutaway(%d, %d)", cut, fade); + + if (fade) { + // TODO: Fade down. Is this blocking or non-blocking? + } + + if (!_cutawayActive) { + _vm->_gfx->showCursor(false); + _vm->_interface->setStatusText(""); + _vm->_interface->setSaveReminderState(0); + _vm->_interface->rememberMode(); + _vm->_interface->setMode(kPanelCutaway); + _cutawayActive = true; + } + + // Set the initial background and palette for the cutaway + + ResourceContext *context = _vm->_resource->getContext(GAME_RESOURCEFILE); + + byte *resourceData; + size_t resourceDataLength; + + _vm->_resource->loadResource(context, _cutawayList[cut].backgroundResourceId, resourceData, resourceDataLength); + + byte *buf; + size_t buflen; + int width; + int height; + + _vm->decodeBGImage(resourceData, resourceDataLength, &buf, &buflen, &width, &height); + + const PalEntry *palette = (const PalEntry *)_vm->getImagePal(resourceData, resourceDataLength); + + Surface *bgSurface = _vm->_render->getBackGroundSurface(); + const Rect rect(width, height); + + bgSurface->blit(rect, buf); + _vm->_gfx->setPalette(palette); + + free(buf); + free(resourceData); + + // Play the animation + + int cutawaySlot = -1; + + for (int i = 0; i < ARRAYSIZE(_cutawayAnimations); i++) { + if (!_cutawayAnimations[i]) { + cutawaySlot = i; + } else if (_cutawayAnimations[i]->state == ANIM_PAUSE) { + delete _cutawayAnimations[i]; + _cutawayAnimations[i] = NULL; + cutawaySlot = i; + } else if (_cutawayAnimations[i]->state == ANIM_PLAYING) { + _cutawayAnimations[i]->state = ANIM_PAUSE; + } + } + + if (cutawaySlot == -1) { + warning("Could not allocate cutaway animation slot"); + return; + } + + _vm->_resource->loadResource(context, _cutawayList[cut].animResourceId, resourceData, resourceDataLength); + + load(MAX_ANIMATIONS + cutawaySlot, resourceData, resourceDataLength); + + free(resourceData); + + setCycles(MAX_ANIMATIONS + cutawaySlot, _cutawayList[cut].cycles); + setFrameTime(MAX_ANIMATIONS + cutawaySlot, 1000 / _cutawayList[cut].frameRate); + play(MAX_ANIMATIONS + cutawaySlot, 0); +} + +void Anim::endCutaway(void) { + // I believe this is called by scripts after running one cutaway. At + // this time, nothing needs to be done here. + + debug(0, "endCutaway()"); +} + +void Anim::returnFromCutaway(void) { + // I believe this is called by scripts after running a cutaway to + // ensure that we return to the scene as if nothing had happened. It's + // not called by the IHNM intro, presumably because there is no old + // scene to return to. + + debug(0, "returnFromCutaway()"); + + if (_cutawayActive) { + // Note that clearCutaway() sets _cutawayActive to false. + clearCutaway(); + + // TODO: Handle fade up, if we previously faded down + + // TODO: Restore the scene + + // TODO: Restore the animations + + for (int i = 0; i < MAX_ANIMATIONS; i++) { + if (_animations[i] && _animations[i]->state == ANIM_PLAYING) { + resume(i, 0); + } + } + } +} + +void Anim::clearCutaway(void) { + debug(0, "clearCutaway()"); + + if (_cutawayActive) { + _cutawayActive = false; + + for (int i = 0; i < ARRAYSIZE(_cutawayAnimations); i++) { + delete _cutawayAnimations[i]; + _cutawayAnimations[i] = NULL; + } + + _vm->_interface->restoreMode(); + _vm->_gfx->showCursor(true); + } +} + +void Anim::load(uint16 animId, const byte *animResourceData, size_t animResourceLength) { + AnimationData *anim; + uint16 temp; + + if (animId >= MAX_ANIMATIONS) { + if (animId >= MAX_ANIMATIONS + ARRAYSIZE(_cutawayAnimations)) + error("Anim::load could not find unused animation slot"); + anim = _cutawayAnimations[animId - MAX_ANIMATIONS] = new AnimationData(animResourceData, animResourceLength); + } else + anim = _animations[animId] = new AnimationData(animResourceData, animResourceLength); + + MemoryReadStreamEndian headerReadS(anim->resourceData, anim->resourceLength, _vm->isBigEndian()); + anim->magic = headerReadS.readUint16LE(); // cause ALWAYS LE + anim->screenWidth = headerReadS.readUint16(); + anim->screenHeight = headerReadS.readUint16(); + + anim->unknown06 = headerReadS.readByte(); + anim->unknown07 = headerReadS.readByte(); + anim->maxFrame = headerReadS.readByte() - 1; + anim->loopFrame = headerReadS.readByte() - 1; + temp = headerReadS.readUint16BE(); + anim->start = headerReadS.pos(); + if (temp == (uint16)(-1)) { + temp = 0; + } + anim->start += temp; + + // Cache frame offsets + anim->frameOffsets = (size_t *)malloc((anim->maxFrame + 1) * sizeof(*anim->frameOffsets)); + if (anim->frameOffsets == NULL) { + memoryError("Anim::load"); + } + + fillFrameOffsets(anim); + + /* char s[200]; + sprintf(s, "d:\\anim%i",animId); + long flen=anim->resourceLength; + char *buf=(char*)anim->resourceData; + FILE*f; + f=fopen(s,"wb"); + for(long i=0;i<flen;i++) + fputc(buf[i],f); + fclose(f);*/ + + // Set animation data + anim->currentFrame = 0; + anim->completed = 0; + anim->cycles = anim->maxFrame; + + anim->frameTime = DEFAULT_FRAME_TIME; + anim->flags = ANIM_FLAG_NONE; + anim->linkId = -1; + anim->state = ANIM_PAUSE; +} + +void Anim::link(int16 animId1, int16 animId2) { + AnimationData *anim1; + AnimationData *anim2; + + anim1 = getAnimation(animId1); + + anim1->linkId = animId2; + + if (animId2 == -1) { + return; + } + + anim2 = getAnimation(animId2); + anim2->frameTime = anim1->frameTime; +} + +void Anim::setCycles(uint16 animId, int cycles) { + AnimationData *anim; + + anim = getAnimation(animId); + + anim->cycles = cycles; +} + +void Anim::play(uint16 animId, int vectorTime, bool playing) { + Event event; + Surface *backGroundSurface; + + byte *displayBuffer; + + uint16 frame; + int frameTime; + + AnimationData *anim; + AnimationData *linkAnim; + + if (animId > MAX_ANIMATIONS && !_cutawayActive) + return; + + if (animId < MAX_ANIMATIONS && _cutawayActive) + return; + + anim = getAnimation(animId); + + backGroundSurface = _vm->_render->getBackGroundSurface(); + displayBuffer = (byte*)backGroundSurface->pixels; + + if (playing) { + anim->state = ANIM_PLAYING; + } + + if (anim->state == ANIM_PAUSE) { + return; + } + + if (anim->completed < anim->cycles) { + frame = anim->currentFrame; + // FIXME: if start > 0, then this works incorrectly + decodeFrame(anim, anim->frameOffsets[frame], displayBuffer, _vm->getDisplayWidth() * _vm->getDisplayHeight()); + + anim->currentFrame++; + if (anim->completed != 65535) { + anim->completed++; + } + + if (anim->currentFrame > anim->maxFrame) { + anim->currentFrame = anim->loopFrame; + + if (anim->state == ANIM_STOPPING || anim->currentFrame == -1) { + anim->state = ANIM_PAUSE; + } + } + } else { + // Animation done playing + anim->state = ANIM_PAUSE; + if (anim->linkId == -1) { + if (anim->flags & ANIM_FLAG_ENDSCENE) { + // This animation ends the scene + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = anim->frameTime + vectorTime; + _vm->_events->queue(&event); + } + return; + } else { + anim->currentFrame = 0; + anim->completed = 0; + } + } + + if (anim->state == ANIM_PAUSE && anim->linkId != -1) { + // If this animation has a link, follow it + linkAnim = getAnimation(anim->linkId); + + debug(5, "Animation ended going to %d", anim->linkId); + linkAnim->state = ANIM_PLAYING; + animId = anim->linkId; + frameTime = 0; + } else { + frameTime = anim->frameTime + vectorTime; + } + + event.type = kEvTOneshot; + event.code = kAnimEvent; + event.op = kEventFrame; + event.param = animId; + event.time = frameTime; + + _vm->_events->queue(&event); +} + +void Anim::stop(uint16 animId) { + AnimationData *anim; + + anim = getAnimation(animId); + + anim->state = ANIM_PAUSE; +} + +void Anim::finish(uint16 animId) { + AnimationData *anim; + + anim = getAnimation(animId); + + anim->state = ANIM_STOPPING; +} + +void Anim::resume(uint16 animId, int cycles) { + AnimationData *anim; + + anim = getAnimation(animId); + + anim->cycles += cycles; + play(animId, 0, true); +} + +void Anim::reset() { + uint16 i; + + for (i = 0; i < MAX_ANIMATIONS; i++) { + if (_animations[i] != NULL) { + delete _animations[i]; + _animations[i] = NULL; + } + } + + for (i = 0; i < ARRAYSIZE(_cutawayAnimations); i++) { + if (_cutawayAnimations[i] != NULL) { + delete _cutawayAnimations[i]; + _cutawayAnimations[i] = NULL; + } + } +} + +void Anim::setFlag(uint16 animId, uint16 flag) { + AnimationData *anim; + + anim = getAnimation(animId); + + anim->flags |= flag; +} + +void Anim::clearFlag(uint16 animId, uint16 flag) { + AnimationData *anim; + + anim = getAnimation(animId); + + anim->flags &= ~flag; +} + +void Anim::setFrameTime(uint16 animId, int time) { + AnimationData *anim; + + anim = getAnimation(animId); + + anim->frameTime = time; +} + +int16 Anim::getCurrentFrame(uint16 animId) { + AnimationData *anim; + + anim = getAnimation(animId); + + return anim->currentFrame; +} + +void Anim::decodeFrame(AnimationData *anim, size_t frameOffset, byte *buf, size_t bufLength) { + byte *writePointer = NULL; + + uint16 xStart = 0; + uint16 yStart = 0; + uint32 screenWidth; + uint32 screenHeight; + + int markByte; + byte dataByte; + int newRow; + + uint16 controlChar; + uint16 paramChar; + + uint16 runcount; + int xVector; + + uint16 i; + bool longData = isLongData(); + + screenWidth = anim->screenWidth; + screenHeight = anim->screenHeight; + + if ((screenWidth * screenHeight) > bufLength) { + // Buffer argument is too small to hold decoded frame, abort. + error("decodeFrame() Buffer size inadequate"); + } + + MemoryReadStream readS(anim->resourceData + frameOffset, anim->resourceLength - frameOffset); + + +#if 1 +#define VALIDATE_WRITE_POINTER \ + if ((writePointer < buf) || (writePointer >= (buf + screenWidth * screenHeight))) { \ + error("VALIDATE_WRITE_POINTER: writePointer=%x buf=%x", writePointer, buf); \ + } +#else +#define VALIDATE_WRITE_POINTER +#endif + + + // Begin RLE decompression to output buffer + do { + markByte = readS.readByte(); + switch (markByte) { + case SAGA_FRAME_START: + xStart = readS.readUint16BE(); + if (longData) + yStart = readS.readUint16BE(); + else + yStart = readS.readByte(); + readS.readByte(); /* Skip pad byte */ + /*xPos = */readS.readUint16BE(); + /*yPos = */readS.readUint16BE(); + /*width = */readS.readUint16BE(); + /*height = */readS.readUint16BE(); + + // Setup write pointer to the draw origin + writePointer = (buf + (yStart * screenWidth) + xStart); + VALIDATE_WRITE_POINTER; + continue; + break; + case SAGA_FRAME_NOOP: // Does nothing + readS.readByte(); + readS.readByte(); + readS.readByte(); + continue; + break; + case SAGA_FRAME_LONG_UNCOMPRESSED_RUN: // Long Unencoded Run + runcount = readS.readSint16BE(); + for (i = 0; i < runcount; i++) { + dataByte = readS.readByte(); + if (dataByte != 0) { + *writePointer = dataByte; + } + writePointer++; + VALIDATE_WRITE_POINTER; + } + continue; + break; + case SAGA_FRAME_LONG_COMPRESSED_RUN: // Long encoded run + runcount = readS.readSint16BE(); + dataByte = readS.readByte(); + for (i = 0; i < runcount; i++) { + *writePointer++ = dataByte; + VALIDATE_WRITE_POINTER; + } + continue; + break; + case SAGA_FRAME_ROW_END: // End of row + xVector = readS.readSint16BE(); + + if (longData) + newRow = readS.readSint16BE(); + else + newRow = readS.readByte(); + + // Set write pointer to the new draw origin + writePointer = buf + ((yStart + newRow) * screenWidth) + xStart + xVector; + VALIDATE_WRITE_POINTER; + continue; + break; + case SAGA_FRAME_REPOSITION: // Reposition command + xVector = readS.readSint16BE(); + writePointer += xVector; + VALIDATE_WRITE_POINTER; + continue; + break; + case SAGA_FRAME_END: // End of frame marker + return; + break; + default: + break; + } + + // Mask all but two high order control bits + controlChar = markByte & 0xC0U; + paramChar = markByte & 0x3FU; + switch (controlChar) { + case SAGA_FRAME_EMPTY_RUN: // 1100 0000 + // Run of empty pixels + runcount = paramChar + 1; + writePointer += runcount; + VALIDATE_WRITE_POINTER; + continue; + break; + case SAGA_FRAME_COMPRESSED_RUN: // 1000 0000 + // Run of compressed data + runcount = paramChar + 1; + dataByte = readS.readByte(); + for (i = 0; i < runcount; i++) { + *writePointer++ = dataByte; + VALIDATE_WRITE_POINTER; + } + continue; + break; + case SAGA_FRAME_UNCOMPRESSED_RUN: // 0100 0000 + // Uncompressed run + runcount = paramChar + 1; + for (i = 0; i < runcount; i++) { + dataByte = readS.readByte(); + if (dataByte != 0) { + *writePointer = dataByte; + } + writePointer++; + VALIDATE_WRITE_POINTER; + } + continue; + break; + default: + // Unknown marker found - abort + error("decodeFrame() Invalid RLE marker encountered"); + break; + } + } while (1); +} + +void Anim::fillFrameOffsets(AnimationData *anim) { + uint16 currentFrame; + byte markByte; + uint16 control; + uint16 runcount; + int i; + bool longData = isLongData(); + + MemoryReadStreamEndian readS(anim->resourceData, anim->resourceLength, _vm->isBigEndian()); + + readS.seek(12); + + readS._bigEndian = !_vm->isBigEndian(); // RLE has inversion BE<>LE + + for (currentFrame = 0; currentFrame <= anim->maxFrame; currentFrame++) { + anim->frameOffsets[currentFrame] = readS.pos(); + + // For some strange reason, the animation header is in little + // endian format, but the actual RLE encoded frame data, + // including the frame header, is in big endian format. */ + do { + markByte = readS.readByte(); +// debug(7, "_pos=%x currentFrame=%i markByte=%x", readS.pos(), currentFrame, markByte); + + switch (markByte) { + case SAGA_FRAME_START: // Start of frame + // skip header + if (longData) { + readS.seek(13, SEEK_CUR); + } else { + readS.seek(12, SEEK_CUR); + } + continue; + break; + + case SAGA_FRAME_END: // End of frame marker + continue; + break; + case SAGA_FRAME_REPOSITION: // Reposition command + readS.readSint16BE(); + continue; + break; + case SAGA_FRAME_ROW_END: // End of row marker + readS.readSint16BE(); + if (longData) + readS.readSint16BE(); + else + readS.readByte(); + continue; + break; + case SAGA_FRAME_LONG_COMPRESSED_RUN: // Long compressed run marker + readS.readSint16BE(); + readS.readByte(); + continue; + break; + case SAGA_FRAME_LONG_UNCOMPRESSED_RUN: // (16) 0001 0000 + // Long Uncompressed Run + runcount = readS.readSint16BE(); + for (i = 0; i < runcount; i++) + readS.readByte(); + continue; + break; + case SAGA_FRAME_NOOP: // Does nothing + readS.readByte(); + readS.readByte(); + readS.readByte(); + continue; + break; + default: + break; + } + + // Mask all but two high order (control) bits + control = markByte & 0xC0; + switch (control) { + case SAGA_FRAME_EMPTY_RUN: + // Run of empty pixels + continue; + break; + case SAGA_FRAME_COMPRESSED_RUN: + // Run of compressed data + readS.readByte(); // Skip data byte + continue; + break; + case SAGA_FRAME_UNCOMPRESSED_RUN: + // Uncompressed run + runcount = (markByte & 0x3f) + 1; + for (i = 0; i < runcount; i++) + readS.readByte(); + continue; + break; + default: + error("Encountered unknown RLE marker %i", markByte); + break; + } + } while (markByte != SAGA_FRAME_END); + } +} + +void Anim::animInfo() { + uint16 animCount; + uint16 i; + + animCount = getAnimationCount(); + + _vm->_console->DebugPrintf("There are %d animations loaded:\n", animCount); + + for (i = 0; i < MAX_ANIMATIONS; i++) { + if (_animations[i] == NULL) { + continue; + } + + _vm->_console->DebugPrintf("%02d: Frames: %u Flags: %u\n", i, _animations[i]->maxFrame, _animations[i]->flags); + } +} + +} // End of namespace Saga diff --git a/engines/saga/animation.h b/engines/saga/animation.h new file mode 100644 index 0000000000..191166732a --- /dev/null +++ b/engines/saga/animation.h @@ -0,0 +1,198 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Background animation management module private header + +#ifndef SAGA_ANIMATION_H_ +#define SAGA_ANIMATION_H_ + +#include "saga/stream.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 + +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 { + byte *resourceData; + size_t resourceLength; + + uint16 magic; + + uint16 screenWidth; + uint16 screenHeight; + + byte unknown06; + byte unknown07; + + int16 maxFrame; + int16 loopFrame; + + int16 start; + + int16 currentFrame; + size_t *frameOffsets; + + uint16 completed; + uint16 cycles; + + int frameTime; + + AnimationState state; + int16 linkId; + uint16 flags; + + AnimationData(const byte *animResourceData, size_t animResourceLength) { + memset(this, 0, sizeof(*this)); + resourceLength = animResourceLength; + resourceData = (byte*)malloc(animResourceLength); + memcpy(resourceData, animResourceData, animResourceLength); + } + ~AnimationData() { + free(frameOffsets); + free(resourceData); + } +}; + +class Anim { +public: + Anim(SagaEngine *vm); + ~Anim(void); + + void loadCutawayList(const byte *resourcePointer, size_t resourceLength); + void freeCutawayList(void); + void playCutaway(int cut, bool fade); + void endCutaway(void); + void returnFromCutaway(void); + void clearCutaway(void); + + void load(uint16 animId, const byte *animResourceData, size_t animResourceLength); + 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); + void animInfo(void); + void setCycles(uint16 animId, int cycles); + void stop(uint16 animId); + void finish(uint16 animId); + void resume(uint16 animId, int cycles); + int16 getCurrentFrame(uint16 animId); + bool hasCutaway(void) { + return _cutawayActive; + } + 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); + } +private: + void decodeFrame(AnimationData *anim, size_t frameOffset, byte *buf, size_t bufLength); + void fillFrameOffsets(AnimationData *anim); + + void validateAnimationId(uint16 animId) { + if (animId >= MAX_ANIMATIONS) { + if (animId >= MAX_ANIMATIONS + ARRAYSIZE(_cutawayAnimations)) + error("validateAnimationId: animId out of range"); + if (_cutawayAnimations[animId - MAX_ANIMATIONS] == NULL) { + error("validateAnimationId: animId=%i unassigned", animId); + } + } + if (_animations[animId] == NULL) { + error("validateAnimationId: animId=%i unassigned", animId); + } + } + + bool isLongData() const { + if ((_vm->getGameType() == GType_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]; + Cutaway *_cutawayList; + int _cutawayListLength; + bool _cutawayActive; +}; + +} // End of namespace Saga + +#endif /* ANIMATION_H_ */ diff --git a/engines/saga/console.cpp b/engines/saga/console.cpp new file mode 100644 index 0000000000..3455c8c1c3 --- /dev/null +++ b/engines/saga/console.cpp @@ -0,0 +1,158 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Console module + +#include "saga/saga.h" +#include "saga/actor.h" +#include "saga/animation.h" +#include "saga/scene.h" +#include "saga/script.h" + +#include "saga/console.h" + +#include "common/debugger.cpp" + +namespace Saga { + +Console::Console(SagaEngine *vm) : Common::Debugger<Console>() { + _vm = vm; + + DCmd_Register("continue", &Console::Cmd_Exit); + DCmd_Register("exit", &Console::Cmd_Exit); + DCmd_Register("quit", &Console::Cmd_Exit); + DCmd_Register("help", &Console::Cmd_Help); + + // CVAR_Register_I(&_soundEnabled, "sound", NULL, CVAR_CFG, 0, 1); + // CVAR_Register_I(&_musicEnabled, "music", NULL, CVAR_CFG, 0, 1); + + // Actor commands + DCmd_Register("actor_walk_to", &Console::cmdActorWalkTo); + + // Animation commands + DCmd_Register("anim_info", &Console::Cmd_AnimInfo); + + // Game stuff + +#if 0 + // Register "g_language" cfg cvar + strncpy(GameModule.game_language, "us", MAXPATH); + + CVAR_Register_S(GameModule.game_language, "g_language", NULL, CVAR_CFG, GAME_LANGSTR_LIMIT); + + // Register "g_skipintro" cfg cvar + CVAR_Register_I(&GameModule.g_skipintro, "g_skipintro", NULL, CVAR_CFG, 0, 1); +#endif + + // Scene commands + DCmd_Register("scene_change", &Console::cmdSceneChange); + DCmd_Register("action_map_info", &Console::cmdActionMapInfo); + DCmd_Register("object_map_info", &Console::cmdObjectMapInfo); +} + +Console::~Console() { +} + +void Console::preEnter() { +} + +void Console::postEnter() { +} + +bool Console::Cmd_Exit(int argc, const char **argv) { + _detach_now = true; + return false; +} + +bool Console::Cmd_Help(int argc, const char **argv) { + // console normally has 39 line width + // wrap around nicely + int width = 0, size, i; + + DebugPrintf("Commands are:\n"); + for (i = 0 ; i < _dcmd_count ; i++) { + size = strlen(_dcmds[i].name) + 1; + + if ((width + size) >= 39) { + DebugPrintf("\n"); + width = size; + } else + width += size; + + DebugPrintf("%s ", _dcmds[i].name); + } + + width = 0; + + DebugPrintf("\n\nVariables are:\n"); + for (i = 0 ; i < _dvar_count ; i++) { + size = strlen(_dvars[i].name) + 1; + + if ((width + size) >= 39) { + DebugPrintf("\n"); + width = size; + } else + width += size; + + DebugPrintf("%s ", _dvars[i].name); + } + + DebugPrintf("\n"); + + return true; +} + +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::Cmd_AnimInfo(int argc, const char **argv) { + _vm->_anim->animInfo(); + 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::cmdActionMapInfo(int argc, const char **argv) { + _vm->_scene->cmdActionMapInfo(); + return true; +} + +bool Console::cmdObjectMapInfo(int argc, const char **argv) { + _vm->_scene->cmdObjectMapInfo(); + return true; +} + +} // End of namespace Saga diff --git a/engines/saga/console.h b/engines/saga/console.h new file mode 100644 index 0000000000..c93373960f --- /dev/null +++ b/engines/saga/console.h @@ -0,0 +1,62 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + + // Console module header file + +#ifndef SAGA_CONSOLE_H_ +#define SAGA_CONSOLE_H_ + +#include "common/debugger.h" + +namespace Saga { + +class Console : public Common::Debugger<Console> { +public: + Console(SagaEngine *vm); + virtual ~Console(void); + +protected: + virtual void preEnter(); + virtual void postEnter(); + +private: + bool Cmd_Exit(int argc, const char **argv); + bool Cmd_Help(int argc, const char **argv); + + bool cmdActorWalkTo(int argc, const char **argv); + + bool Cmd_AnimInfo(int argc, const char **argv); + + bool cmdSceneChange(int argc, const char **argv); + bool cmdActionMapInfo(int argc, const char **argv); + bool cmdObjectMapInfo(int argc, const char **argv); + + +private: + SagaEngine *_vm; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/events.cpp b/engines/saga/events.cpp new file mode 100644 index 0000000000..0d1da65efb --- /dev/null +++ b/engines/saga/events.cpp @@ -0,0 +1,590 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// 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/music.h" +#include "saga/actor.h" + +#include "saga/events.h" + +namespace Saga { + +Events::Events(SagaEngine *vm) : _vm(vm), _initialized(false) { + debug(8, "Initializing event subsystem..."); + _initialized = true; +} + +Events::~Events(void) { + debug(8, "Shutting down event subsystem..."); + freeList(); +} + +// 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. +int Events::handleEvents(long msec) { + Event *event_p; + + 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_p = (Event *)eventi.operator->(); + + // 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 (event_p->chain == NULL) { + eventi = _eventList.eraseAndPrev(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; + Event *from_chain = event_p->chain; + memcpy(event_p, from_chain, sizeof(*event_p)); + free(from_chain); + + event_p->time += delta_time; + --eventi; + } + } else if (result == kEvStBreak) { + break; + } + } + + return SUCCESS; +} + +int Events::handleContinuous(Event *event) { + double event_pc = 0.0; // Event completion percentage + int event_done = 0; + + Surface *backGroundSurface; + 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; + default: + break; + } + break; + case kTransitionEvent: + switch (event->op) { + case kEventDissolve: + backGroundSurface = _vm->_render->getBackGroundSurface(); + _vm->_scene->getBGInfo(bgInfo); + rect.left = rect.top = 0; + rect.right = bgInfo.bounds.width(); + rect.bottom = bgInfo.bounds.height(); + backGroundSurface->transitionDissolve(bgInfo.buffer, rect, 0, event_pc); + 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; + size_t len; + + backGroundSurface = _vm->_render->getBackGroundSurface(); + _vm->_scene->getBGMaskInfo(w, h, maskBuffer, len); + rect.left = (_vm->getDisplayWidth() - w) / 2; + rect.top = (_vm->getDisplayHeight() - h) / 2; + rect.setWidth(w); + rect.setHeight(h); + + backGroundSurface->transitionDissolve( maskBuffer, rect, 1, event_pc); + 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; + default: + break; + } + break; + case kScriptEvent: + case kBgEvent: + case kInterfaceEvent: + handleOneShot(event); + event_done = true; + break; + default: + break; + + } + + if (event_done) { + return kEvStDelete; + } + + return kEvStBreak; +} + +int Events::handleOneShot(Event *event) { + Surface *backBuffer; + ScriptThread *sthread; + 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: + _vm->_scene->_textList.remove((TextListEntry *)event->data); + 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: + _vm->_music->stop(); + if (event->op == kEventPlay) + _vm->_music->play(event->param, (MusicFlags)event->param2); + break; + case kBgEvent: + { + Surface *backGroundSurface; + BGInfo bgInfo; + + if (!(_vm->_scene->getFlags() & kSceneFlagISO)) { + + backBuffer = _vm->_gfx->getBackBuffer(); + backGroundSurface = _vm->_render->getBackGroundSurface(); + _vm->_scene->getBGInfo(bgInfo); + + backGroundSurface->blit(bgInfo.bounds, bgInfo.buffer); + + // If it is inset scene then draw black border + if (bgInfo.bounds.width() < _vm->getDisplayWidth() || 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, kITEColorBlack); + backGroundSurface->drawRect(rect2, kITEColorBlack); + backGroundSurface->drawRect(rect3, kITEColorBlack); + backGroundSurface->drawRect(rect4, kITEColorBlack); + } + + if (event->param == kEvPSetPalette) { + PalEntry *palPointer; + _vm->_scene->getBGPal(palPointer); + _vm->_gfx->setPalette(palPointer); + } + } + } + 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; + default: + break; + } + break; + case kSceneEvent: + switch (event->op) { + case kEventEnd: + _vm->_scene->nextScene(); + return kEvStBreak; + break; + 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; + default: + break; + } + break; + case kScriptEvent: + switch (event->op) { + case kEventExecBlocking: + case kEventExecNonBlocking: + debug(6, "Exec module number %d script entry number %d", event->param, event->param2); + + sthread = _vm->_script->createThread(event->param, event->param2); + if (sthread == NULL) { + _vm->_console->DebugPrintf("Thread creation failed.\n"); + break; + } + + 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; + } + break; + case kCursorEvent: + switch (event->op) { + case kEventShow: + _vm->_gfx->showCursor(true); + break; + case kEventHide: + _vm->_gfx->showCursor(false); + 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; + ((Surface *)event->data)->drawRect(rect, event->param); + break; + case kEventSetFlag: + _vm->_render->setFlag(event->param); + break; + case kEventClearFlag: + _vm->_render->clearFlag(event->param); + break; + default: + break; + } + default: + break; + } + + return kEvStDelete; +} + +int Events::handleInterval(Event *event) { + return kEvStDelete; +} + +// Schedules an event in the event list; returns a pointer to the scheduled +// event suitable for chaining if desired. +Event *Events::queue(Event *event) { + Event *queuedEvent; + + queuedEvent = _eventList.pushBack(*event).operator->(); + initializeEvent(queuedEvent); + + return queuedEvent; +} + +// Places a 'add_event' on the end of an event chain given by 'head_event' +// (head_event may be in any position in the event chain) +Event *Events::chain(Event *headEvent, Event *addEvent) { + if (headEvent == NULL) { + return queue(addEvent); + } + + Event *walkEvent; + for (walkEvent = headEvent; walkEvent->chain != NULL; walkEvent = walkEvent->chain) { + continue; + } + + walkEvent->chain = (Event *)malloc(sizeof(*walkEvent->chain)); + *walkEvent->chain = *addEvent; + initializeEvent(walkEvent->chain); + + return walkEvent->chain; +} + +int Events::initializeEvent(Event *event) { + event->chain = NULL; + switch (event->type) { + case kEvTOneshot: + break; + case kEvTContinuous: + case kEvTImmediate: + event->time += event->duration; + break; + case kEvTInterval: + break; + default: + return FAILURE; + break; + } + + return SUCCESS; +} + +int Events::clearList() { + Event *chain_walk; + Event *next_chain; + Event *event_p; + + // Walk down event list + for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) { + event_p = (Event *)eventi.operator->(); + + // Only remove events not marked kEvFNoDestory (engine events) + if (!(event_p->code & kEvFNoDestory)) { + // Remove any events chained off this one */ + for (chain_walk = event_p->chain; chain_walk != NULL; chain_walk = next_chain) { + next_chain = chain_walk->chain; + free(chain_walk); + } + eventi = _eventList.eraseAndPrev(eventi); + } + } + + return SUCCESS; +} + +// Removes all events from the list (even kEvFNoDestory) +int Events::freeList() { + Event *chain_walk; + Event *next_chain; + Event *event_p; + + // Walk down event list + EventList::iterator eventi = _eventList.begin(); + while (eventi != _eventList.end()) { + event_p = (Event *)eventi.operator->(); + + // Remove any events chained off this one */ + for (chain_walk = event_p->chain; chain_walk != NULL; chain_walk = next_chain) { + next_chain = chain_walk->chain; + free(chain_walk); + } + eventi=_eventList.erase(eventi); + } + + return SUCCESS; +} + +// Walks down the event list, updating event times by 'msec'. +int Events::processEventTime(long msec) { + Event *event_p; + uint16 event_count = 0; + + for (EventList::iterator eventi = _eventList.begin(); eventi != _eventList.end(); ++eventi) { + event_p = (Event *)eventi.operator->(); + + event_p->time -= msec; + event_count++; + + if (event_p->type == kEvTImmediate) + break; + + if (event_count > EVENT_WARNINGCOUNT) { + warning("Event list exceeds %u", EVENT_WARNINGCOUNT); + } + } + + return SUCCESS; +} + +} // End of namespace Saga diff --git a/engines/saga/events.h b/engines/saga/events.h new file mode 100644 index 0000000000..d89b3d89f5 --- /dev/null +++ b/engines/saga/events.h @@ -0,0 +1,179 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Event management module header file + +#ifndef SAGA_EVENT_H +#define SAGA_EVENT_H + +#include "saga/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 +}; + +enum EventOps { + // INSTANTANEOUS events + // BG events + kEventDisplay = 1, + // ANIM events + // kEventPlay = 1, // reused + // kEventStop = 2, // reused + kEventFrame = 3, + kEventSetFlag = 4, + kEventClearFlag = 5, + // MUISC & SOUND events + kEventPlay = 1, + kEventStop = 2, + // SCENE events + kEventEnd = 2, + // TEXT events + kEventHide = 2, + kEventRemove = 3, + // PALANIM events + kEventCycleStart = 1, + kEventCycleStep = 2, + // INTERFACE events + kEventActivate = 1, + kEventDeactivate = 2, + kEventSetStatus = 3, + kEventClearStatus = 4, + kEventSetFadeMode = 5, + // ACTOR events + kEventMove = 1, + // SCRIPT events + kEventExecBlocking = 1, + kEventExecNonBlocking = 2, + kEventThreadWake = 3, + // CURSOR events + kEventShow = 1, + // kEventHide = 2, // reused + // GRAPHICS events + kEventFillRect = 1, + // kEventSetFlag = 4, // reused + // kEventClearFlag = 5, // reused + + // CONTINUOUS events + // PALETTE events + kEventPalToBlack = 1, + kEventBlackToPal = 2, + // TRANSITION events + kEventDissolve = 1, + kEventDissolveBGMask = 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 *chain; // Event chain (For consecutive events) + Event() { + memset(this, 0, sizeof(*this)); + } +}; + +typedef SortedList<Event> EventList; + +#define EVENT_WARNINGCOUNT 1000 +#define EVENT_MASK 0x00FF + +enum EventStatusCode { + kEvStInvalidCode = 0, + kEvStDelete, + kEvStContinue, + kEvStBreak +}; + +class Events { + public: + Events(SagaEngine *vm); + ~Events(void); + int handleEvents(long msec); + int clearList(); + int freeList(); + Event *queue(Event *event); + Event *chain(Event *headEvent, Event *addEvent); + + private: + int handleContinuous(Event *event); + int handleOneShot(Event *event); + int handleInterval(Event *event); + int handleImmediate(Event *event); + int processEventTime(long msec); + int initializeEvent(Event *event); + + private: + SagaEngine *_vm; + bool _initialized; + + EventList _eventList; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/font.cpp b/engines/saga/font.cpp new file mode 100644 index 0000000000..9a91bf4872 --- /dev/null +++ b/engines/saga/font.cpp @@ -0,0 +1,681 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Font management and font drawing module +#include "saga/saga.h" +#include "saga/gfx.h" +#include "saga/rscfile.h" + +#include "saga/font.h" +#include "saga/stream.h" + +namespace Saga { + +Font::Font(SagaEngine *vm) : _vm(vm), _initialized(false) { + int i; + + // Load font module resource context + + assert(_vm->getFontsCount() > 0); + + _fonts = (FontData **)calloc(_vm->getFontsCount(), sizeof(*_fonts)); + _loadedFonts = 0; + + for (i = 0; i < _vm->getFontsCount(); i++) { + loadFont(_vm->getFontDescription(i)->fontResourceId); + } + + _initialized = true; +} + +Font::~Font(void) { + debug(8, "Font::~Font(): Freeing fonts."); + int i; + + for (i = 0 ; i < _loadedFonts ; i++) { + if (_fonts[i] != NULL) { + free(_fonts[i]->normal.font); + free(_fonts[i]->outline.font); + } + + free(_fonts[i]); + } +} + + +void Font::loadFont(uint32 fontResourceId) { + FontData *font; + byte *fontResourcePointer; + size_t fontResourceLength; + int numBits; + int c; + ResourceContext *fontContext; + + debug(1, "Font::loadFont(): Reading fontResourceId %d...", fontResourceId); + + fontContext = _vm->_resource->getContext(GAME_RESOURCEFILE); + if (fontContext == NULL) { + error("Font::Font() resource context not found"); + } + + // Load font resource + _vm->_resource->loadResource(fontContext, fontResourceId, fontResourcePointer, fontResourceLength); + + if (fontResourceLength < FONT_DESCSIZE) { + error("Font::loadFont() Invalid font length (%i < %i)", fontResourceLength, FONT_DESCSIZE); + } + + MemoryReadStreamEndian readS(fontResourcePointer, fontResourceLength, fontContext->isBigEndian); + + // Create new font structure + font = (FontData *)malloc(sizeof(*font)); + + // Read font header + font->normal.header.charHeight = readS.readUint16(); + font->normal.header.charWidth = readS.readUint16(); + font->normal.header.rowLength = readS.readUint16(); + + + debug(2, "Character width: %d", font->normal.header.charWidth); + debug(2, "Character height: %d", font->normal.header.charHeight); + debug(2, "Row padding: %d", font->normal.header.rowLength); + + for (c = 0; c < FONT_CHARCOUNT; c++) { + font->normal.fontCharEntry[c].index = readS.readUint16(); + } + + for (c = 0; c < FONT_CHARCOUNT; c++) { + numBits = font->normal.fontCharEntry[c].width = readS.readByte(); + font->normal.fontCharEntry[c].byteWidth = getByteLen(numBits); + } + + for (c = 0; c < FONT_CHARCOUNT; c++) { + font->normal.fontCharEntry[c].flag = readS.readByte(); + } + + for (c = 0; c < FONT_CHARCOUNT; c++) { + font->normal.fontCharEntry[c].tracking = readS.readByte(); + } + + if (readS.pos() != FONT_DESCSIZE) { + error("Invalid font resource size."); + } + + font->normal.font = (byte*)malloc(fontResourceLength - FONT_DESCSIZE); + memcpy(font->normal.font, fontResourcePointer + FONT_DESCSIZE, fontResourceLength - FONT_DESCSIZE); + + free(fontResourcePointer); + + + // Create outline font style + createOutline(font); + + // Set font data + _fonts[_loadedFonts++] = font; +} + +void Font::createOutline(FontData *font) { + int i; + int row; + int newByteWidth; + int oldByteWidth; + int newRowLength = 0; + size_t indexOffset = 0; + int index; + int currentByte; + unsigned char *basePointer; + unsigned char *srcPointer; + unsigned char *destPointer1; + unsigned char *destPointer2; + unsigned char *destPointer3; + unsigned char charRep; + + + // Populate new font style character data + for (i = 0; i < FONT_CHARCOUNT; i++) { + newByteWidth = 0; + oldByteWidth = 0; + index = font->normal.fontCharEntry[i].index; + if ((index > 0) || (i == FONT_FIRSTCHAR)) { + index += indexOffset; + } + + font->outline.fontCharEntry[i].index = index; + font->outline.fontCharEntry[i].tracking = font->normal.fontCharEntry[i].tracking; + font->outline.fontCharEntry[i].flag = font->normal.fontCharEntry[i].flag; + + if (font->normal.fontCharEntry[i].width != 0) { + newByteWidth = getByteLen(font->normal.fontCharEntry[i].width + 2); + oldByteWidth = getByteLen(font->normal.fontCharEntry[i].width); + + if (newByteWidth > oldByteWidth) { + indexOffset++; + } + } + + font->outline.fontCharEntry[i].width = font->normal.fontCharEntry[i].width + 2; + font->outline.fontCharEntry[i].byteWidth = newByteWidth; + newRowLength += newByteWidth; + } + + debug(2, "New row length: %d", newRowLength); + + font->outline.header = font->normal.header; + font->outline.header.charWidth += 2; + font->outline.header.charHeight += 2; + font->outline.header.rowLength = newRowLength; + + // Allocate new font representation storage + font->outline.font = (unsigned char *)calloc(newRowLength, font->outline.header.charHeight); + + + // Generate outline font representation + for (i = 0; i < FONT_CHARCOUNT; i++) { + for (row = 0; row < font->normal.header.charHeight; row++) { + for (currentByte = 0; currentByte < font->outline.fontCharEntry[i].byteWidth; currentByte++) { + basePointer = font->outline.font + font->outline.fontCharEntry[i].index + currentByte; + destPointer1 = basePointer + newRowLength * row; + destPointer2 = basePointer + newRowLength * (row + 1); + destPointer3 = basePointer + newRowLength * (row + 2); + if (currentByte > 0) { + // Get last two columns from previous byte + srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + (currentByte - 1); + charRep = *srcPointer; + *destPointer1 |= ((charRep << 6) | (charRep << 7)); + *destPointer2 |= ((charRep << 6) | (charRep << 7)); + *destPointer3 |= ((charRep << 6) | (charRep << 7)); + } + + if (currentByte < font->normal.fontCharEntry[i].byteWidth) { + srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + currentByte; + charRep = *srcPointer; + *destPointer1 |= charRep | (charRep >> 1) | (charRep >> 2); + *destPointer2 |= charRep | (charRep >> 1) | (charRep >> 2); + *destPointer3 |= charRep | (charRep >> 1) | (charRep >> 2); + } + } + } + + // "Hollow out" character to prevent overdraw + for (row = 0; row < font->normal.header.charHeight; row++) { + for (currentByte = 0; currentByte < font->outline.fontCharEntry[i].byteWidth; currentByte++) { + destPointer2 = font->outline.font + font->outline.header.rowLength * (row + 1) + font->outline.fontCharEntry[i].index + currentByte; + if (currentByte > 0) { + // Get last two columns from previous byte + srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + (currentByte - 1); + *destPointer2 &= ((*srcPointer << 7) ^ 0xFFU); + } + + if (currentByte < font->normal.fontCharEntry[i].byteWidth) { + srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + currentByte; + *destPointer2 &= ((*srcPointer >> 1) ^ 0xFFU); + } + } + } + } +} + +// Returns the horizontal length in pixels of the graphical representation +// of at most 'count' characters of the string 'text', taking +// into account any formatting options specified by 'flags'. +// If 'count' is 0, all characters of 'test' are counted. +int Font::getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) { + FontData *font; + size_t ct; + int width = 0; + int ch; + const byte *txt; + + + font = getFont(fontId); + + txt = (const byte *) text; + + for (ct = count; *txt && (!count || ct > 0); txt++, ct--) { + ch = *txt & 0xFFU; + // Translate character + ch = _charMap[ch]; + assert(ch < FONT_CHARCOUNT); + width += font->normal.fontCharEntry[ch].tracking; + } + + if ((flags & kFontBold) || (flags & kFontOutline)) { + width += 1; + } + + return width; +} + + +void Font::draw(FontId fontId, Surface *ds, const char *text, size_t count, const Common::Point &point, + int color, int effectColor, FontEffectFlags flags) { + FontData *font; + Point offsetPoint(point); + + font = getFont(fontId); + + if (flags & kFontOutline) { + offsetPoint.x--; + offsetPoint.y--; + outFont(font->outline, ds, text, count, offsetPoint, effectColor, flags); + outFont(font->normal, ds, text, count, point, color, flags); + } else if (flags & kFontShadow) { + offsetPoint.x--; + offsetPoint.y++; + outFont(font->normal, ds, text, count, offsetPoint, effectColor, flags); + outFont(font->normal, ds, text, count, point, color, flags); + } else { // FONT_NORMAL + outFont(font->normal, ds, text, count, point, color, flags); + } +} + +void Font::outFont(const FontStyle &drawFont, Surface *ds, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags) { + const byte *textPointer; + byte *c_dataPointer; + int c_code; + int charRow; + Point textPoint(point); + + byte *outputPointer; + byte *outputPointer_min; + byte *outputPointer_max; + + int row; + int rowLimit; + + int c_byte_len; + int c_byte; + int c_bit; + int ct; + + if ((point.x > ds->w) || (point.y > ds->h)) { + // Output string can't be visible + return; + } + + textPointer = (const byte *)text; + ct = count; + + // Draw string one character at a time, maximum of 'draw_str'_ct + // characters, or no limit if 'draw_str_ct' is 0 + for (; *textPointer && (!count || ct); textPointer++, ct--) { + c_code = *textPointer & 0xFFU; + + // Translate character + if (!(flags & kFontDontmap)) + c_code = _charMap[c_code]; + assert(c_code < FONT_CHARCOUNT); + + // Check if character is defined + if ((drawFont.fontCharEntry[c_code].index == 0) && (c_code != FONT_FIRSTCHAR)) { +#if FONT_SHOWUNDEFINED + if (c_code == FONT_CH_SPACE) { + textPoint.x += drawFont.fontCharEntry[c_code].tracking; + continue; + } + c_code = FONT_CH_QMARK; +#else + // Character code is not defined, but advance tracking + // ( Not defined if offset is 0, except for 33 ('!') which + // is defined ) + textPoint.x += drawFont.fontCharEntry[c_code].tracking; + continue; +#endif + } + + // Get length of character in bytes + c_byte_len = ((drawFont.fontCharEntry[c_code].width - 1) / 8) + 1; + rowLimit = (ds->h < (textPoint.y + drawFont.header.charHeight)) ? ds->h : textPoint.y + drawFont.header.charHeight; + charRow = 0; + + for (row = textPoint.y; row < rowLimit; row++, charRow++) { + // Clip negative rows */ + if (row < 0) { + continue; + } + + outputPointer = (byte *)ds->pixels + (ds->pitch * row) + textPoint.x; + outputPointer_min = (byte *)ds->pixels + (ds->pitch * row) + (textPoint.x > 0 ? textPoint.x : 0); + outputPointer_max = outputPointer + (ds->pitch - textPoint.x); + + // If character starts off the screen, jump to next character + if (outputPointer < outputPointer_min) { + break; + } + + c_dataPointer = drawFont.font + charRow * drawFont.header.rowLength + drawFont.fontCharEntry[c_code].index; + + for (c_byte = 0; c_byte < c_byte_len; c_byte++, c_dataPointer++) { + // Check each bit, draw pixel if bit is set + for (c_bit = 7; c_bit >= 0 && (outputPointer < outputPointer_max); c_bit--) { + if ((*c_dataPointer >> c_bit) & 0x01) { + *outputPointer = (byte)color; + } + outputPointer++; + } // end per-bit processing + } // end per-byte processing + } // end per-row processing + + // Advance tracking position + textPoint.x += drawFont.fontCharEntry[c_code].tracking; + } // end per-character processing +} + + +void Font::textDraw(FontId fontId, Surface *ds, const char *text, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) { + int textWidth; + int textLength; + int fitWidth; + Common::Point textPoint(point); + + textLength = strlen(text); + + if (!(flags & kFontCentered)) { + // Text is not centered; No formatting required + draw(fontId, ds, text, textLength, point, color, effectColor, flags); + return; + } + + // Text is centered... format output + // Enforce minimum and maximum center points for centered text + if (textPoint.x < TEXT_CENTERLIMIT) { + textPoint.x = TEXT_CENTERLIMIT; + } + + if (textPoint.x > ds->w - TEXT_CENTERLIMIT) { + textPoint.x = ds->w - TEXT_CENTERLIMIT; + } + + if (textPoint.x < (TEXT_MARGIN * 2)) { + // Text can't be centered if it's too close to the margin + return; + } + + textWidth = getStringWidth(fontId, text, textLength, flags); + + if (textPoint.x < (ds->w / 2)) { + // Fit to right side + fitWidth = (textPoint.x - TEXT_MARGIN) * 2; + } else { + // Fit to left side + fitWidth = ((ds->w - TEXT_MARGIN) - textPoint.x) * 2; + } + + if (fitWidth < textWidth) { + warning("text too long to be displayed in one line"); + textWidth = fitWidth; + } + // Entire string fits, draw it + textPoint.x = textPoint.x - (textWidth / 2); + draw(fontId, ds, text, textLength, textPoint, color, effectColor, flags); +} + +int Font::getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags) { + int textWidth; + int textLength; + int fitWidth; + const char *startPointer; + const char *searchPointer; + const char *measurePointer; + const char *foundPointer; + int len; + int w; + const char *endPointer; + int h; + int wc; + int w_total; + int len_total; + Common::Point textPoint; + Common::Point textPoint2; + + textLength = strlen(text); + + textWidth = getStringWidth(fontId, text, textLength, flags); + h = getHeight(fontId); + fitWidth = width; + + textPoint.x = (fitWidth / 2); + textPoint.y = 0; + + if (fitWidth >= textWidth) { + return h; + } + + // String won't fit on one line + w_total = 0; + len_total = 0; + wc = 0; + + startPointer = text; + measurePointer = text; + searchPointer = text; + endPointer = text + textLength; + + for (;;) { + foundPointer = strchr(searchPointer, ' '); + if (foundPointer == NULL) { + // Ran to the end of the buffer + len = endPointer - measurePointer; + } else { + len = foundPointer - measurePointer; + } + + w = getStringWidth(fontId, measurePointer, len, flags); + measurePointer = foundPointer; + + if ((w_total + w) > fitWidth) { + // This word won't fit + if (wc == 0) { + // The first word in the line didn't fit. Still print it + searchPointer = measurePointer + 1; + } + // Wrap what we've got and restart + textPoint.y += h + TEXT_LINESPACING; + if (foundPointer == NULL) { + // Since word hit NULL but fit, we are done + return textPoint.y + h; + } + w_total = 0; + len_total = 0; + wc = 0; + measurePointer = searchPointer; + startPointer = searchPointer; + } else { + // Word will fit ok + w_total += w; + len_total += len; + wc++; + if (foundPointer == NULL) { + // Since word hit NULL but fit, we are done + return textPoint.y + h; + } + searchPointer = measurePointer + 1; + } + } +} + +void Font::textDrawRect(FontId fontId, Surface *ds, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) { + int textWidth; + int textLength; + int fitWidth; + const char *startPointer; + const char *searchPointer; + const char *measurePointer; + const char *foundPointer; + int len; + int w; + const char *endPointer; + int h; + int wc; + int w_total; + int len_total; + Common::Point textPoint; + Common::Point textPoint2; + + textLength = strlen(text); + + textWidth = getStringWidth(fontId, text, textLength, flags); + fitWidth = rect.width(); + + textPoint.x = rect.left + (fitWidth / 2); + textPoint.y = rect.top; + + if (fitWidth >= textWidth) { + // Entire string fits, draw it + textPoint.x -= (textWidth / 2); + draw(fontId, ds, text, textLength, textPoint, color, effectColor, flags); + return; + } + + // String won't fit on one line + h = getHeight(fontId); + w_total = 0; + len_total = 0; + wc = 0; + + startPointer = text; + measurePointer = text; + searchPointer = text; + endPointer = text + textLength; + + for (;;) { + foundPointer = strchr(searchPointer, ' '); + if (foundPointer == NULL) { + // Ran to the end of the buffer + len = endPointer - measurePointer; + } else { + len = foundPointer - measurePointer; + } + + w = getStringWidth(fontId, measurePointer, len, flags); + measurePointer = foundPointer; + + if ((w_total + w) > fitWidth) { + // This word won't fit + if (wc == 0) { + w_total = fitWidth; + len_total = len; + } + + // Wrap what we've got and restart + textPoint2.x = textPoint.x - (w_total / 2); + textPoint2.y = textPoint.y; + draw(fontId, ds, startPointer, len_total, textPoint2, color, effectColor, flags); + textPoint.y += h + TEXT_LINESPACING; + if (textPoint.y >= rect.bottom) { + return; + } + w_total = 0; + len_total = 0; + if (wc == 0) { + searchPointer = measurePointer + 1; + } + wc = 0; + + // Advance the search pointer to the next non-space. + // Otherwise, the first "word" to be measured will be + // an empty string. Measuring or drawing a string of + // length 0 is interpreted as measure/draw the entire + // buffer, which certainly is not what we want here. + // + // This happes because a string may contain several + // spaces in a row, e.g. after a period. + + while (*searchPointer == ' ') + searchPointer++; + + measurePointer = searchPointer; + startPointer = searchPointer; + } else { + // Word will fit ok + w_total += w; + len_total += len; + wc++; + if (foundPointer == NULL) { + // Since word hit NULL but fit, we are done + textPoint2.x = textPoint.x - (w_total / 2); + textPoint2.y = textPoint.y; + draw(fontId, ds, startPointer, len_total, textPoint2, color, + effectColor, flags); + return; + } + searchPointer = measurePointer + 1; + } + } +} + +Font::FontId Font::knownFont2FontIdx(KnownFont font) { + FontId fontId = kSmallFont; + + if (_vm->getGameType() == GType_ITE) { + switch (font) + { + case (kKnownFontSmall): + fontId = kSmallFont; + break; + case (kKnownFontMedium): + fontId = kMediumFont; + break; + case (kKnownFontBig): + fontId = kBigFont; + break; + + case (kKnownFontVerb): + fontId = kSmallFont; + break; + case (kKnownFontScript): + fontId = kMediumFont; + break; + case (kKnownFontPause): + fontId = _vm->_font->valid(kBigFont) ? kBigFont : kMediumFont; + break; + } + } else if (_vm->getGameType() == GType_IHNM) { + switch (font) + { + case (kKnownFontSmall): + fontId = kSmallFont; + break; + case (kKnownFontMedium): + fontId = kMediumFont; + break; + case (kKnownFontBig): + fontId = kBigFont; + break; + + case (kKnownFontVerb): + fontId = kIHNMFont8; + break; + case (kKnownFontScript): + fontId = kIHNMMainFont; + break; + case (kKnownFontPause): + fontId = kMediumFont; // unchecked + break; + } + } + return fontId; +} + +} // End of namespace Saga diff --git a/engines/saga/font.h b/engines/saga/font.h new file mode 100644 index 0000000000..f7f2113808 --- /dev/null +++ b/engines/saga/font.h @@ -0,0 +1,205 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Font management and font drawing header file + +#ifndef SAGA_FONT_H__ +#define SAGA_FONT_H__ + +#include "saga/list.h" +#include "saga/gfx.h" + +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_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() { + memset(this, 0, sizeof(*this)); + } +}; + +class TextList: public SortedList<TextListEntry> { +public: + + TextListEntry *addEntry(const TextListEntry &entry) { + return pushBack(entry).operator->(); + } +}; + +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]; + byte *font; +}; + +struct FontData { + FontStyle normal; + FontStyle outline; +}; + +class Font { + public: + Font(SagaEngine *vm); + ~Font(void); + 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, Surface *ds, const char *string, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) { + textDraw(knownFont2FontIdx(font), ds, string, point, color, effectColor, flags); + } + void textDrawRect(KnownFont font, Surface *ds, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) { + textDrawRect(knownFont2FontIdx(font), ds, text, rect, color, effectColor, flags); + } + + private: + enum FontId { + kSmallFont, + kMediumFont, + kBigFont, + kIHNMUnknown, + kIHNMFont8, + kIHNMUnknown2, + kIHNMMainFont + }; + + Font::FontId knownFont2FontIdx(KnownFont font); + + int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags); + int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags); + void textDrawRect(FontId fontId, Surface *ds, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags); + void textDraw(FontId fontId, Surface *ds, const char *string, const Common::Point &point, int color, int effectColor, FontEffectFlags flags); + + void loadFont(uint32 fontResourceId); + void createOutline(FontData *font); + void draw(FontId fontId, Surface *ds, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags); + void outFont(const FontStyle &drawFont, Surface *ds, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags); + + FontData *getFont(FontId fontId) { + validate(fontId); + return _fonts[fontId]; + } + + int getHeight(FontId fontId) { + return getFont(fontId)->normal.header.charHeight; + } + + void validate(FontId fontId) { + if (!valid(fontId)) { + error("Font::validate: Invalid font id."); + } + } + bool valid(FontId fontId) { + return ((fontId >= 0) && (fontId < _loadedFonts)); + } + int getByteLen(int numBits) const { + int byteLength = numBits / 8; + + if (numBits % 8) { + byteLength++; + } + + return byteLength; + } + + static const int _charMap[256]; + SagaEngine *_vm; + + bool _initialized; + + int _loadedFonts; + FontData **_fonts; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/font_map.cpp b/engines/saga/font_map.cpp new file mode 100644 index 0000000000..04e3400b2e --- /dev/null +++ b/engines/saga/font_map.cpp @@ -0,0 +1,293 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Font module character mapping table ( MS CP-850 to ISO 8859-1 ) + +// Translation table derived from http://www.kostis.net/charsets/ + +#include "saga/saga.h" +#include "saga/font.h" + +namespace Saga { + +const int Font::_charMap[256] = { + 0, // 0 + 1, // 1 + 2, // 2 + 3, // 3 + 4, // 4 + 5, // 5 + 6, // 6 + 7, // 7 + 8, // 8 + 9, // 9 + 10, // 10 + 11, // 11 + 12, // 12 + 13, // 13 + 14, // 14 + 15, // 15 + 16, // 16 + 17, // 17 + 18, // 18 + 19, // 19 + 20, // 20 + 21, // 21 + 22, // 22 + 23, // 23 + 24, // 24 + 25, // 25 + 26, // 26 + 27, // 27 + 28, // 28 + 29, // 29 + 30, // 30 + 31, // 31 + 32, // 32 + 33, // 33 + 34, // 34 + 35, // 35 + 36, // 36 + 37, // 37 + 38, // 38 + 39, // 39 + 40, // 40 + 41, // 41 + 42, // 42 + 43, // 43 + 44, // 44 + 45, // 45 + 46, // 46 + 47, // 47 + 48, // 48 + 49, // 49 + 50, // 50 + 51, // 51 + 52, // 52 + 53, // 53 + 54, // 54 + 55, // 55 + 56, // 56 + 57, // 57 + 58, // 58 + 59, // 59 + 60, // 60 + 61, // 61 + 62, // 62 + 63, // 63 + 64, // 64 + 65, // 65 + 66, // 66 + 67, // 67 + 68, // 68 + 69, // 69 + 70, // 70 + 71, // 71 + 72, // 72 + 73, // 73 + 74, // 74 + 75, // 75 + 76, // 76 + 77, // 77 + 78, // 78 + 79, // 79 + 80, // 80 + 81, // 81 + 82, // 82 + 83, // 83 + 84, // 84 + 85, // 85 + 86, // 86 + 87, // 87 + 88, // 88 + 89, // 89 + 90, // 90 + 91, // 91 + 92, // 92 + 93, // 93 + 94, // 94 + 95, // 95 + 96, // 96 + 97, // 97 + 98, // 98 + 99, // 99 + 100, // 100 + 101, // 101 + 102, // 102 + 103, // 103 + 104, // 104 + 105, // 105 + 106, // 106 + 107, // 107 + 108, // 108 + 109, // 109 + 110, // 110 + 111, // 111 + 112, // 112 + 113, // 113 + 114, // 114 + 115, // 115 + 116, // 116 + 117, // 117 + 118, // 118 + 119, // 119 + 120, // 120 + 121, // 121 + 122, // 122 + 123, // 123 + 124, // 124 + 125, // 125 + 126, // 126 + 127, // 127 + 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 diff --git a/engines/saga/game.cpp b/engines/saga/game.cpp new file mode 100644 index 0000000000..86526fe267 --- /dev/null +++ b/engines/saga/game.cpp @@ -0,0 +1,1790 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Game detection, general game parameters + +#include "saga/saga.h" + +#include "common/file.h" +#include "common/md5.h" +#include "common/map.h" +#include "common/config-manager.h" +#include "base/plugins.h" +#include "base/gameDetector.h" +#include "backends/fs/fs.h" + +#include "saga/rscfile.h" +#include "saga/interface.h" +#include "saga/scene.h" +#include "saga/resnames.h" + +#define ITE_CONVERSE_MAX_TEXT_WIDTH (256 - 60) +#define ITE_CONVERSE_TEXT_HEIGHT 10 +#define ITE_CONVERSE_TEXT_LINES 4 + +//TODO: ihnm +#define IHNM_CONVERSE_MAX_TEXT_WIDTH (256 - 60) +#define IHNM_CONVERSE_TEXT_HEIGHT 10 +#define IHNM_CONVERSE_TEXT_LINES 10 + +namespace Saga { + +static int detectGame(const FSList &fslist, bool mode = false, int start = -1); + +// ITE section +static PanelButton ITE_MainPanelButtons[] = { + {kPanelButtonVerb, 52,4, 57,10, kVerbITEWalkTo,'w',0, 0,1,0}, + {kPanelButtonVerb, 52,15, 57,10, kVerbITELookAt,'l',0, 2,3,0}, + {kPanelButtonVerb, 52,26, 57,10, kVerbITEPickUp,'p',0, 4,5,0}, + {kPanelButtonVerb, 52,37, 57,10, kVerbITETalkTo,'t',0, 0,1,0}, + {kPanelButtonVerb, 110,4, 56,10, kVerbITEOpen,'o',0, 6,7,0}, + {kPanelButtonVerb, 110,15, 56,10, kVerbITEClose,'c',0, 8,9,0}, + {kPanelButtonVerb, 110,26, 56,10, kVerbITEUse,'u',0, 10,11,0}, + {kPanelButtonVerb, 110,37, 56,10, kVerbITEGive,'g',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_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,'r',0, 0,0,0}, //read speed + {kPanelButtonOption, 113,37, 45,17, kTextMusic,'m',0, 0,0,0}, //music + {kPanelButtonOption, 113,56, 45,17, kTextSound,'n',0, 0,0,0}, //sound-noise + {kPanelButtonOption, 13,79, 135,17, kTextQuitGame,'q',0, 0,0,0}, //quit + {kPanelButtonOption, 13,98, 135,17, kTextContinuePlaying,'c',0, 0,0,0}, //continue + {kPanelButtonOption, 164,98, 57,17, kTextLoad,'l',0, 0,0,0}, //load + {kPanelButtonOption, 241,98, 57,17, kTextSave,'s',0, 0,0,0}, //save + {kPanelButtonOptionSaveFiles, 166,20, 112,74, 0,'-',0, 0,0,0}, //savefiles + + {kPanelButtonOptionText,106,4, 0,0, kTextGameOptions,'-',0, 0,0,0}, // text: game options + {kPanelButtonOptionText,11,22, 0,0, kTextReadingSpeed,'-',0, 0,0,0}, // text: read speed + {kPanelButtonOptionText,28,22, 0,0, kTextShowDialog,'-',0, 0,0,0}, // text: read speed + {kPanelButtonOptionText,73,41, 0,0, kTextMusic,'-',0, 0,0,0}, // text: music + {kPanelButtonOptionText,69,60, 0,0, kTextSound,'-',0, 0,0,0}, // text: noise +}; + +static PanelButton ITE_QuitPanelButtons[] = { + {kPanelButtonQuit, 11,17, 60,16, kTextQuit,'q',0, 0,0,0}, + {kPanelButtonQuit, 121,17, 60,16, kTextCancel,'c',0, 0,0,0}, + {kPanelButtonQuitText, -1,5, 0,0, kTextQuitTheGameQuestion,'-',0, 0,0,0}, +}; + +static PanelButton ITE_LoadPanelButtons[] = { + {kPanelButtonLoad, 101,19, 60,16, kTextOK,'o',0, 0,0,0}, + {kPanelButtonLoadText, -1,5, 0,0, kTextLoadSuccessful,'-',0, 0,0,0}, +}; + +static PanelButton ITE_SavePanelButtons[] = { + {kPanelButtonSave, 11,37, 60,16, kTextSave,'s',0, 0,0,0}, + {kPanelButtonSave, 101,37, 60,16, kTextCancel,'c',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 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 + 186, // status text color + 15, // status BG color + 308,137, // save reminder pos + 12,12, // save reminder w & h + 6,7, // save reminder sprite numbers + + 5, 4, // left portrait x, y offset + 274, 4, // right portrait x, y offset + + 8, 9, // inventory Up & Down button indexies + 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 indexies + 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 GameResourceDescription ITE_Resources = { + RID_ITE_SCENE_LUT, // Scene lookup table RN + RID_ITE_SCRIPT_LUT, // Script lookup table RN + RID_ITE_MAIN_PANEL, + RID_ITE_CONVERSE_PANEL, + RID_ITE_OPTION_PANEL, + RID_ITE_MAIN_SPRITES, + RID_ITE_MAIN_PANEL_SPRITES, + RID_ITE_DEFAULT_PORTRAITS, + RID_ITE_MAIN_STRINGS, + RID_ITE_ACTOR_NAMES +}; + +static GameResourceDescription ITEDemo_Resources = { + RID_ITEDEMO_SCENE_LUT, // Scene lookup table RN + RID_ITEDEMO_SCRIPT_LUT, // Script lookup table RN + RID_ITEDEMO_MAIN_PANEL, + RID_ITEDEMO_CONVERSE_PANEL, + RID_ITEDEMO_OPTION_PANEL, + RID_ITEDEMO_MAIN_SPRITES, + RID_ITEDEMO_MAIN_PANEL_SPRITES, + RID_ITEDEMO_DEFAULT_PORTRAITS, + RID_ITEDEMO_MAIN_STRINGS, + RID_ITEDEMO_ACTOR_NAMES +}; + +// Inherit the Earth - DOS Demo version +static GameFileDescription ITEDEMO_GameFiles[] = { + {"ite.rsc", GAME_RESOURCEFILE}, + //{"ite.dmo", GAME_DEMOFILE}, + {"scripts.rsc", GAME_SCRIPTFILE}, + {"voices.rsc", GAME_SOUNDFILE | GAME_VOICEFILE} +}; + +static GameFontDescription ITEDEMO_GameFonts[] = { + {0}, + {1} +}; + +static GameSoundInfo ITEDEMO_GameSound = { + kSoundVOC, + -1, + -1, + false, + false, + true +}; + +// Inherit the Earth - Wyrmkeep Win32 Demo version +static GameFileDescription ITEWINDEMO_GameFiles[] = { + {"ited.rsc", GAME_RESOURCEFILE}, + {"scriptsd.rsc", GAME_SCRIPTFILE}, + {"soundsd.rsc", GAME_SOUNDFILE}, + {"voicesd.rsc", GAME_VOICEFILE} +}; + +static GameFontDescription ITEWINDEMO_GameFonts[] = { + {2}, + {0} +}; + +static GameSoundInfo ITEWINDEMO1_GameSound = { + kSoundPCM, + 22050, + 8, + false, + false, + false +}; + +static GameSoundInfo ITEWINDEMO2_GameVoice = { + kSoundVOX, + 22050, + 16, + false, + false, + true +}; + +static GameSoundInfo ITEWINDEMO2_GameSound = { + kSoundPCM, + 22050, + 16, + false, + false, + true +}; + +// Inherit the Earth - Wyrmkeep Mac Demo version +static GameFileDescription ITEMACDEMO_GameFiles[] = { + {"ited.rsc", GAME_RESOURCEFILE}, + {"scriptsd.rsc", GAME_SCRIPTFILE}, + {"soundsd.rsc", GAME_SOUNDFILE}, + {"voicesd.rsc", GAME_VOICEFILE}, + {"musicd.rsc", GAME_MUSICFILE} +}; + +static GameSoundInfo ITEMACDEMO_GameVoice = { + kSoundVOX, + 22050, + 16, + false, + false, + true +}; + +static GameSoundInfo ITEMACDEMO_GameSound = { + kSoundPCM, + 22050, + 16, + false, + true, + true +}; + +static GameSoundInfo ITEMACDEMO_GameMusic = { + kSoundPCM, + 11025, + 16, + false, + false, + true +}; + +// Inherit the Earth - Wyrmkeep Linux Demo version +static GameFileDescription ITELINDEMO_GameFiles[] = { + {"ited.rsc", GAME_RESOURCEFILE}, + {"scriptsd.rsc", GAME_SCRIPTFILE}, + {"soundsd.rsc", GAME_SOUNDFILE}, + {"voicesd.rsc", GAME_VOICEFILE}, + {"musicd.rsc", GAME_MUSICFILE} +}; + +static GameSoundInfo ITELINDEMO_GameMusic = { + kSoundPCM, + 11025, + 16, + true, + false, + true +}; + +// Inherit the Earth - Wyrmkeep Linux version +static GameFileDescription ITELINCD_GameFiles[] = { + {"ite.rsc", GAME_RESOURCEFILE}, + {"scripts.rsc", GAME_SCRIPTFILE}, + {"sounds.rsc", GAME_SOUNDFILE}, + {"voices.rsc", GAME_VOICEFILE}, + {"music.rsc", GAME_MUSICFILE} +}; + +// Inherit the Earth - Wyrmkeep combined Windows/Mac/Linux version. This +// version is different from the other Wyrmkeep re-releases in that it does +// not have any substitute files. Presumably the ite.rsc file has been +// modified to include the Wyrmkeep changes. The resource files are little- +// endian, except for the voice file which is big-endian. + +static GameFileDescription ITEMULTICD_GameFiles[] = { + {"ite.rsc", GAME_RESOURCEFILE}, + {"scripts.rsc", GAME_SCRIPTFILE}, + {"sounds.rsc", GAME_SOUNDFILE}, + {"Inherit the Earth Voices", GAME_VOICEFILE | GAME_SWAPENDIAN}, + {"music.rsc", GAME_MUSICFILE} +}; + +static GameFileDescription ITEMACCD_G_GameFiles[] = { + {"ITE Resources.bin", GAME_RESOURCEFILE | GAME_MACBINARY}, + {"ITE Scripts.bin", GAME_SCRIPTFILE | GAME_MACBINARY}, + {"ITE Sounds.bin", GAME_SOUNDFILE | GAME_MACBINARY}, + {"ITE Music.bin", GAME_MUSICFILE_GM | GAME_MACBINARY}, + {"ITE Voices.bin", GAME_VOICEFILE | GAME_MACBINARY} +}; + +static GameSoundInfo ITEMACCD_G_GameSound = { + kSoundMacPCM, + 22050, + 8, + false, + false, + false +}; + +// Inherit the Earth - Mac Wyrmkeep version +static GameFileDescription ITEMACCD_GameFiles[] = { + {"ite.rsc", GAME_RESOURCEFILE}, + {"scripts.rsc", GAME_SCRIPTFILE}, + {"sounds.rsc", GAME_SOUNDFILE}, + {"Inherit the Earth Voices", GAME_VOICEFILE}, + {"music.rsc", GAME_MUSICFILE} +}; + +static GameSoundInfo ITEMACCD_GameSound = { + kSoundPCM, + 22050, + 16, + false, + true, + true +}; + +static GameSoundInfo ITEMACCD_GameMusic = { + kSoundPCM, + 11025, + 16, + true, + false, + true +}; + +// Inherit the Earth - Diskette version +static GameFileDescription ITEDISK_GameFiles[] = { + {"ite.rsc", GAME_RESOURCEFILE}, + {"scripts.rsc", GAME_SCRIPTFILE}, + {"voices.rsc", GAME_SOUNDFILE | GAME_VOICEFILE} +}; + +static GameFileDescription ITEDISK2_GameFiles[] = { + {"ite.rsc", GAME_RESOURCEFILE}, + {"scripts.rsc", GAME_SCRIPTFILE}, + {"voices.rsc", GAME_SOUNDFILE | GAME_VOICEFILE}, + {"music.rsc", GAME_MUSICFILE} +}; + +static GameFontDescription ITEDISK_GameFonts[] = { + {2}, + {0}, + {1} +}; + +static GameSoundInfo ITEDISK_GameSound = { + kSoundVOC, + -1, + -1, + false, + false, + true +}; + +// Inherit the Earth - CD Enhanced version +static GameFileDescription ITECD_GameFiles[] = { + {"ite.rsc", GAME_RESOURCEFILE}, + {"scripts.rsc", GAME_SCRIPTFILE}, + {"sounds.rsc", GAME_SOUNDFILE}, + {"voices.rsc", GAME_VOICEFILE} +}; + +static GameFileDescription ITECD2_GameFiles[] = { + {"ite.rsc", GAME_RESOURCEFILE}, + {"scripts.rsc", GAME_SCRIPTFILE}, + {"sounds.rsc", GAME_SOUNDFILE}, + {"voices.rsc", GAME_VOICEFILE}, + {"music.rsc", GAME_MUSICFILE} +}; + +static GameFontDescription ITECD_GameFonts[] = { + {2}, + {0}, + {1} +}; + +static GameSoundInfo ITECD_GameSound = { + kSoundPCM, + 22050, + 16, + false, + false, + true +}; + +static GamePatchDescription ITEWinPatch1_Files[] = { + { "cave.mid", GAME_RESOURCEFILE, 9, NULL}, + { "intro.mid", GAME_RESOURCEFILE, 10, NULL}, + { "fvillage.mid", GAME_RESOURCEFILE, 11, NULL}, + { "elkhall.mid", GAME_RESOURCEFILE, 12, NULL}, + { "mouse.mid", GAME_RESOURCEFILE, 13, NULL}, + { "darkclaw.mid", GAME_RESOURCEFILE, 14, NULL}, + { "birdchrp.mid", GAME_RESOURCEFILE, 15, NULL}, + { "orbtempl.mid", GAME_RESOURCEFILE, 16, NULL}, + { "spooky.mid", GAME_RESOURCEFILE, 17, NULL}, + { "catfest.mid", GAME_RESOURCEFILE, 18, NULL}, + { "elkfanfare.mid", GAME_RESOURCEFILE, 19, NULL}, + { "bcexpl.mid", GAME_RESOURCEFILE, 20, NULL}, + { "boargtnt.mid", GAME_RESOURCEFILE, 21, NULL}, + { "boarking.mid", GAME_RESOURCEFILE, 22, NULL}, + { "explorea.mid", GAME_RESOURCEFILE, 23, NULL}, + { "exploreb.mid", GAME_RESOURCEFILE, 24, NULL}, + { "explorec.mid", GAME_RESOURCEFILE, 25, NULL}, + { "sunstatm.mid", GAME_RESOURCEFILE, 26, NULL}, + { "nitstrlm.mid", GAME_RESOURCEFILE, 27, NULL}, + { "humruinm.mid", GAME_RESOURCEFILE, 28, NULL}, + { "damexplm.mid", GAME_RESOURCEFILE, 29, NULL}, + { "tychom.mid", GAME_RESOURCEFILE, 30, NULL}, + { "kitten.mid", GAME_RESOURCEFILE, 31, NULL}, + { "sweet.mid", GAME_RESOURCEFILE, 32, NULL}, + { "brutalmt.mid", GAME_RESOURCEFILE, 33, NULL}, + { "shiala.mid", GAME_RESOURCEFILE, 34, NULL}, + + { "wyrm.pak", GAME_RESOURCEFILE, 1529, NULL}, + { "wyrm1.dlt", GAME_RESOURCEFILE, 1530, NULL}, + { "wyrm2.dlt", GAME_RESOURCEFILE, 1531, NULL}, + { "wyrm3.dlt", GAME_RESOURCEFILE, 1532, NULL}, + { "wyrm4.dlt", GAME_RESOURCEFILE, 1533, NULL}, + { "credit3n.dlt", GAME_RESOURCEFILE, 1796, NULL}, + { "credit4n.dlt", GAME_RESOURCEFILE, 1797, NULL}, + { "p2_a.voc", GAME_VOICEFILE, 4, NULL} +}; + +static GamePatchDescription ITEWinPatch2_Files[] = { + { "cave.mid", GAME_RESOURCEFILE, 9, NULL}, + { "intro.mid", GAME_RESOURCEFILE, 10, NULL}, + { "fvillage.mid", GAME_RESOURCEFILE, 11, NULL}, + { "elkfanfare.mid", GAME_RESOURCEFILE, 19, NULL}, + { "bcexpl.mid", GAME_RESOURCEFILE, 20, NULL}, + { "boargtnt.mid", GAME_RESOURCEFILE, 21, NULL}, + { "explorea.mid", GAME_RESOURCEFILE, 23, NULL}, + { "sweet.mid", GAME_RESOURCEFILE, 32, NULL}, + + { "wyrm.pak", GAME_RESOURCEFILE, 1529, NULL}, + { "wyrm1.dlt", GAME_RESOURCEFILE, 1530, NULL}, + { "wyrm2.dlt", GAME_RESOURCEFILE, 1531, NULL}, + { "wyrm3.dlt", GAME_RESOURCEFILE, 1532, NULL}, + { "p2_a.iaf", GAME_VOICEFILE, 4, &ITECD_GameSound} +/* boarhall.bbm + elkenter.bbm + ferrets.bbm + ratdoor.bbm + sanctuar.bbm + tycho.bbm*/ +}; + +static GamePatchDescription ITEMacPatch_Files[] = { + { "wyrm.pak", GAME_RESOURCEFILE, 1529, NULL}, + { "wyrm1.dlt", GAME_RESOURCEFILE, 1530, NULL}, + { "wyrm2.dlt", GAME_RESOURCEFILE, 1531, NULL}, + { "wyrm3.dlt", GAME_RESOURCEFILE, 1532, NULL}, + { "wyrm4.dlt", GAME_RESOURCEFILE, 1533, NULL}, + { "credit3m.dlt", GAME_RESOURCEFILE, 1796, NULL}, + { "credit4m.dlt", GAME_RESOURCEFILE, 1797, NULL}, + { "p2_a.iaf", GAME_VOICEFILE, 4, &ITEMACCD_GameSound} +}; + +static GamePatchDescription ITELinPatch_Files[] = { + { "wyrm.pak", GAME_RESOURCEFILE, 1529, NULL}, + { "wyrm1.dlt", GAME_RESOURCEFILE, 1530, NULL}, + { "wyrm2.dlt", GAME_RESOURCEFILE, 1531, NULL}, + { "wyrm3.dlt", GAME_RESOURCEFILE, 1532, NULL}, + { "credit3n.dlt", GAME_RESOURCEFILE, 1796, NULL}, + { "credit4n.dlt", GAME_RESOURCEFILE, 1797, NULL}, + { "P2_A.iaf", GAME_VOICEFILE, 4, &ITECD_GameSound} +}; + +// IHNM section + +static PanelButton IHNM_MainPanelButtons[] = { + {kPanelButtonVerb, 106,12, 114,30, kVerbIHNMWalk,'w',0, 0,1,0}, + {kPanelButtonVerb, 106,44, 114,30, kVerbIHNMLookAt,'l',0, 2,3,0}, + {kPanelButtonVerb, 106,76, 114,30, kVerbIHNMTake,'k',0, 4,5,0}, + {kPanelButtonVerb, 106,108, 114,30, kVerbIHNMUse,'u',0, 6,7,0}, + {kPanelButtonVerb, 223,12, 114,30, kVerbIHNMTalkTo,'t',0, 8,9,0}, + {kPanelButtonVerb, 223,44, 114,30, kVerbIHNMSwallow,'s',0, 10,11,0}, + {kPanelButtonVerb, 223,76, 114,30, kVerbIHNMGive,'g',0, 12,13,0}, + {kPanelButtonVerb, 223,108, 114,30, kVerbIHNMPush,'p',0, 14,15,0}, + {kPanelButtonArrow, 606,22, 20,25, -1,'[',0, 0,0,0}, //TODO: arrow Sprite Numbers + {kPanelButtonArrow, 606,108, 20,25, 1,']',0, 0,0,0}, + + {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, 52,18 + IHNM_CONVERSE_TEXT_HEIGHT * 1, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 1,'2',0, 0,0,0}, + {kPanelButtonConverseText, 52,18 + IHNM_CONVERSE_TEXT_HEIGHT * 2, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 2,'3',0, 0,0,0}, + {kPanelButtonConverseText, 52,18 + IHNM_CONVERSE_TEXT_HEIGHT * 3, IHNM_CONVERSE_MAX_TEXT_WIDTH,IHNM_CONVERSE_TEXT_HEIGHT, 3,'4',0, 0,0,0}, + //..... + {kPanelButtonArrow, 606,22, 20,25, -1,'[',0, 0,0,0}, //TODO: arrow Sprite Numbers + {kPanelButtonArrow, 606,108, 20,25, 1,']',0, 0,0,0} +}; + +static PanelButton IHNM_OptionPanelButtons[] = { + {kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO +}; + +static PanelButton IHNM_QuitPanelButtons[] = { + {kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO +}; + +static PanelButton IHNM_LoadPanelButtons[] = { + {kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO +}; + +static PanelButton IHNM_SavePanelButtons[] = { + {kPanelButtonArrow, 0,0, 0,0, 0,'-',0, 0,0,0}, //TODO +}; + + +static GameDisplayInfo IHNM_DisplayInfo = { //TODO: fill it all + 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, 303, // save reminder pos + 24, 24, // save reminder w&h + 0,1, // save reminder sprite numbers + + 11, 12, // left portrait x, y offset + -1, -1, // right portrait x, y offset + + -1, -1, // inventory Up & Down button indexies + 2, 4, // inventory rows, columns + + 0, 328, // main panel offsets + ARRAYSIZE(IHNM_MainPanelButtons), + IHNM_MainPanelButtons, + + -1, -1, // converse Up & Down button indexies + + IHNM_CONVERSE_MAX_TEXT_WIDTH, + IHNM_CONVERSE_TEXT_HEIGHT, + IHNM_CONVERSE_TEXT_LINES, + 0, 328, // converse panel offsets + ARRAYSIZE(IHNM_ConversePanelButtons), + IHNM_ConversePanelButtons, + + -1, -1, // save file index + 0, // optionSaveFileVisible + 0, 0, // option panel offsets + ARRAYSIZE(IHNM_OptionPanelButtons), + IHNM_OptionPanelButtons, + + 0,0, // quit panel offsets + 0,0, // quit panel width & height + ARRAYSIZE(IHNM_QuitPanelButtons), + IHNM_QuitPanelButtons, + + 0, 0, // load panel offsets + 0, 0, // load panel width & height + ARRAYSIZE(IHNM_LoadPanelButtons), + IHNM_LoadPanelButtons, + + -1, // save edit index + 0, 0, // save panel offsets + 0, 0, // 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 +}; + +static GameResourceDescription IHNM_Resources = { + RID_IHNM_SCENE_LUT, // Scene lookup table RN + RID_IHNM_SCRIPT_LUT, // Script lookup table RN + RID_IHNM_MAIN_PANEL, + RID_IHNM_CONVERSE_PANEL, + RID_IHNM_OPTION_PANEL, + RID_IHNM_MAIN_SPRITES, + RID_IHNM_MAIN_PANEL_SPRITES, + 0, + RID_IHNM_MAIN_STRINGS, + 0 +}; + +// I Have No Mouth and I Must Scream - Demo version +static GameFileDescription IHNMDEMO_GameFiles[] = { + {"scream.res", GAME_RESOURCEFILE}, + {"scripts.res", GAME_SCRIPTFILE}, + {"sfx.res", GAME_SOUNDFILE}, + {"voicesd.res", GAME_VOICEFILE} +}; + +// I Have No Mouth and I Must Scream - Retail CD version +static GameFileDescription IHNMCD_GameFiles[] = { + {"musicfm.res", GAME_MUSICFILE_FM}, + {"musicgm.res", GAME_MUSICFILE_GM}, + {"scream.res", GAME_RESOURCEFILE}, + {"patch.re_", GAME_PATCHFILE | GAME_RESOURCEFILE}, + {"scripts.res", GAME_SCRIPTFILE}, + {"sfx.res", GAME_SOUNDFILE}, + {"voicess.res", GAME_VOICEFILE}, //order of voice bank file is important + {"voices1.res", GAME_VOICEFILE}, + {"voices2.res", GAME_VOICEFILE}, + {"voices3.res", GAME_VOICEFILE}, + {"voices4.res", GAME_VOICEFILE}, + {"voices5.res", GAME_VOICEFILE}, + {"voices6.res", GAME_VOICEFILE} +}; + +// I Have No Mouth and I Must Scream - Censored CD version (without Nimdok) +static GameFileDescription IHNMCD_Censored_GameFiles[] = { + {"musicfm.res", GAME_MUSICFILE_FM}, + {"musicgm.res", GAME_MUSICFILE_GM}, + {"scream.res", GAME_RESOURCEFILE}, + {"scripts.res", GAME_SCRIPTFILE}, + {"patch.re_", GAME_PATCHFILE | GAME_RESOURCEFILE}, + {"sfx.res", GAME_SOUNDFILE}, + {"voicess.res", GAME_VOICEFILE}, //order of voice bank file is important + {"voices1.res", GAME_VOICEFILE}, + {"voices2.res", GAME_VOICEFILE}, + {"voices3.res", GAME_VOICEFILE}, + {"voices5.res", GAME_VOICEFILE}, + {"voices6.res", GAME_VOICEFILE} +}; + +static GameFontDescription IHNMDEMO_GameFonts[] = { + {2}, + {3}, + {4} +}; + +static GameFontDescription IHNMCD_GameFonts[] = { + {2}, + {3}, + {4}, + {5}, + {6}, // kIHNMFont8 + {7}, + {8} // kIHNMMainFont +}; + +static GameSoundInfo IHNM_GameSound = { + kSoundWAV, + -1, + -1, + false, + false, + true +}; + +struct GameMD5 { + GameIds id; + const char *md5; + const char *filename; + bool caseSensitive; +}; + +#define FILE_MD5_BYTES 5000 + +static GameMD5 gameMD5[] = { + { GID_ITE_DISK_G, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false }, + { GID_ITE_DISK_G, "516f7330f8410057b834424ea719d1ef", "scripts.rsc", false }, + { GID_ITE_DISK_G, "c46e4392fcd2e89bc91e5567db33b62d", "voices.rsc", false }, + + { GID_ITE_DISK_G2, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false }, + { GID_ITE_DISK_G2, "516f7330f8410057b834424ea719d1ef", "scripts.rsc", false }, + { GID_ITE_DISK_G2, "c46e4392fcd2e89bc91e5567db33b62d", "voices.rsc", false }, + { GID_ITE_DISK_G2, "d6454756517f042f01210458abe8edd4", "music.rsc", false }, + + { GID_ITE_CD_G, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false }, + { GID_ITE_CD_G, "50a0d2d7003c926a3832d503c8534e90", "scripts.rsc", false }, + { GID_ITE_CD_G, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false }, + { GID_ITE_CD_G, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false }, + + { GID_ITE_CD_G2, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false }, + { GID_ITE_CD_G2, "50a0d2d7003c926a3832d503c8534e90", "scripts.rsc", false }, + { GID_ITE_CD_G2, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false }, + { GID_ITE_CD_G2, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false }, + { GID_ITE_CD_G2, "d6454756517f042f01210458abe8edd4", "music.rsc", false }, + + { GID_ITE_CD, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false }, + { GID_ITE_CD, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false }, + { GID_ITE_CD, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false }, + { GID_ITE_CD, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false }, + + // reported by mld. Bestsellergamers cover disk + { GID_ITE_CD_DE, "869fc23c8f38f575979ec67152914fee", "ite.rsc", false }, + { GID_ITE_CD_DE, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false }, + { GID_ITE_CD_DE, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false }, + { GID_ITE_CD_DE, "2fbad5d10b9b60a3415dc4aebbb11718", "voices.rsc", false }, + + { GID_ITE_CD_DE2, "869fc23c8f38f575979ec67152914fee", "ite.rsc", false }, + { GID_ITE_CD_DE2, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false }, + { GID_ITE_CD_DE2, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false }, + { GID_ITE_CD_DE2, "2fbad5d10b9b60a3415dc4aebbb11718", "voices.rsc", false }, + { GID_ITE_CD_DE2, "d6454756517f042f01210458abe8edd4", "music.rsc", false }, + + { GID_ITE_DEMO_G, "986c79c4d2939dbe555576529fd37932", "ite.rsc", false }, + { GID_ITE_DEMO_G, "d5697dd3240a3ceaddaa986c47e1a2d7", "scripts.rsc", false }, + { GID_ITE_DEMO_G, "c58e67c506af4ffa03fd0aac2079deb0", "voices.rsc", false }, + { GID_ITE_DEMO_G, "0b9a70eb4e120b6f00579b46c8cae29e", "ite.dmo", false }, + + { GID_ITE_WINCD, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false }, + { GID_ITE_WINCD, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false }, + { GID_ITE_WINCD, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false }, + { GID_ITE_WINCD, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false }, + + { GID_ITE_MACCD, "4f7fa11c5175980ed593392838523060", "ite.rsc", false }, + { GID_ITE_MACCD, "adf1f46c1d0589083996a7060c798ad0", "scripts.rsc", false }, + { GID_ITE_MACCD, "1a91cd60169f367ecb6c6e058d899b2f", "music.rsc", false }, + { GID_ITE_MACCD, "95863b89a0916941f6c5e1789843ba14", "sounds.rsc", false }, + { GID_ITE_MACCD, "c14c4c995e7a0d3828e3812a494301b7", "Inherit the Earth Voices", true }, + + { GID_ITE_MACCD_G, "0bd506aa887bfc7965f695c6bd28237d", "ITE Resources.bin", true }, + { GID_ITE_MACCD_G, "af0d7a2588e09ad3ecbc5b474ea238bf", "ITE Scripts.bin", true }, + { GID_ITE_MACCD_G, "c1d20324b7cdf1650e67061b8a93251c", "ITE Music.bin", true }, + { GID_ITE_MACCD_G, "441426c6bb2a517f65c7e49b57f7a345", "ITE Sounds.bin", true }, + { GID_ITE_MACCD_G, "dba92ae7d57e942250fe135609708369", "ITE Voices.bin", true }, + + { GID_ITE_LINCD, "8f4315a9bb10ec839253108a032c8b54", "ite.rsc", false }, + { GID_ITE_LINCD, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false }, + { GID_ITE_LINCD, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false }, + { GID_ITE_LINCD, "41bb6b95d792dde5196bdb78740895a6", "voices.rsc", false }, + { GID_ITE_LINCD, "d6454756517f042f01210458abe8edd4", "music.rsc", false }, + + { GID_ITE_MULTICD, "a6433e34b97b15e64fe8214651012db9", "ite.rsc", false }, + { GID_ITE_MULTICD, "a891405405edefc69c9d6c420c868b84", "scripts.rsc", false }, + { GID_ITE_MULTICD, "e2ccb61c325d6d1ead3be0e731fe29fe", "sounds.rsc", false }, + { GID_ITE_MULTICD, "c14c4c995e7a0d3828e3812a494301b7", "Inherit the Earth Voices", true }, + { GID_ITE_MULTICD, "d6454756517f042f01210458abe8edd4", "music.rsc", false }, + + { GID_ITE_DISK_DE, "869fc23c8f38f575979ec67152914fee", "ite.rsc", false }, + { GID_ITE_DISK_DE, "516f7330f8410057b834424ea719d1ef", "scripts.rsc", false }, + { GID_ITE_DISK_DE, "0c9113e630f97ef0996b8c3114badb08", "voices.rsc", false }, + + { GID_ITE_DISK_DE2, "869fc23c8f38f575979ec67152914fee", "ite.rsc", false }, + { GID_ITE_DISK_DE2, "516f7330f8410057b834424ea719d1ef", "scripts.rsc", false }, + { GID_ITE_DISK_DE2, "0c9113e630f97ef0996b8c3114badb08", "voices.rsc", false }, + { GID_ITE_DISK_DE2, "d6454756517f042f01210458abe8edd4", "music.rsc", false }, + + { GID_ITE_WINDEMO2, "3a450852cbf3c80773984d565647e6ac", "ited.rsc", false }, + { GID_ITE_WINDEMO2, "3f12b67fa93e56e1a6be39d2921d80bb", "scriptsd.rsc", false }, + { GID_ITE_WINDEMO2, "95a6c148e22e99a8c243f2978223583c", "soundsd.rsc", false }, + { GID_ITE_WINDEMO2, "e139d86bab2ee8ba3157337f894a92d4", "voicesd.rsc", false }, + + { GID_ITE_LINDEMO, "3a450852cbf3c80773984d565647e6ac", "ited.rsc", false }, + { GID_ITE_LINDEMO, "3f12b67fa93e56e1a6be39d2921d80bb", "scriptsd.rsc", false }, + { GID_ITE_LINDEMO, "d6454756517f042f01210458abe8edd4", "musicd.rsc", false }, + { GID_ITE_LINDEMO, "95a6c148e22e99a8c243f2978223583c", "soundsd.rsc", false }, + { GID_ITE_LINDEMO, "e139d86bab2ee8ba3157337f894a92d4", "voicesd.rsc", false }, + + { GID_ITE_MACDEMO2, "addfc9d82bc2fa1f4cab23743c652c08", "ited.rsc", false }, + { GID_ITE_MACDEMO2, "fded5c59b8b7c5976229f960d21e6b0b", "scriptsd.rsc", false }, + { GID_ITE_MACDEMO2, "495bdde51fd9f4bea2b9c911091b1ab2", "musicd.rsc", false }, + { GID_ITE_MACDEMO2, "b3a831fbed337d1f1300fee1dd474f6c", "soundsd.rsc", false }, + { GID_ITE_MACDEMO2, "e139d86bab2ee8ba3157337f894a92d4", "voicesd.rsc", false }, + + { GID_ITE_WINDEMO1, "3a450852cbf3c80773984d565647e6ac", "ited.rsc", false }, + { GID_ITE_WINDEMO1, "3f12b67fa93e56e1a6be39d2921d80bb", "scriptsd.rsc", false }, + { GID_ITE_WINDEMO1, "a741139dd7365a13f463cd896ff9969a", "soundsd.rsc", false }, + { GID_ITE_WINDEMO1, "0759eaf5b64ae19fd429920a70151ad3", "voicesd.rsc", false }, + + { GID_ITE_MACDEMO1, "addfc9d82bc2fa1f4cab23743c652c08", "ited.rsc", false }, + { GID_ITE_MACDEMO1, "fded5c59b8b7c5976229f960d21e6b0b", "scriptsd.rsc", false }, + { GID_ITE_MACDEMO1, "1a91cd60169f367ecb6c6e058d899b2f", "musicd.rsc", false }, + { GID_ITE_MACDEMO1, "b3a831fbed337d1f1300fee1dd474f6c", "soundsd.rsc", false }, + { GID_ITE_MACDEMO1, "e139d86bab2ee8ba3157337f894a92d4", "voicesd.rsc", false }, + + { GID_IHNM_CD, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false }, + { GID_IHNM_CD, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false }, + { GID_IHNM_CD, "46bbdc65d164ba7e89836a0935eec8e6", "scream.res", false }, + { GID_IHNM_CD, "be38bbc5a26be809dbf39f13befebd01", "scripts.res", false }, + { GID_IHNM_CD, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false }, + { GID_IHNM_CD, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false }, + { GID_IHNM_CD, "fc6440b38025f4b2cc3ff55c3da5c3eb", "voices1.res", false }, + { GID_IHNM_CD, "b37f10fd1696ade7d58704ccaaebceeb", "voices2.res", false }, + { GID_IHNM_CD, "3bbc16a8f741dbb511da506c660a0b54", "voices3.res", false }, + { GID_IHNM_CD, "ebfa160122d2247a676ca39920e5d481", "voices4.res", false }, + { GID_IHNM_CD, "1f501ce4b72392bdd1d9ec38f6eec6da", "voices5.res", false }, + { GID_IHNM_CD, "f580ed7568c7d6ef34e934ba20adf834", "voices6.res", false }, + { GID_IHNM_CD, "54b1f2013a075338ceb0e258d97808bd", "voicess.res", false }, + + // Reported by mld. German Retail + { GID_IHNM_CD_DE, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false }, + { GID_IHNM_CD_DE, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false }, + { GID_IHNM_CD_DE, "c92370d400e6f2a3fc411c3729d09224", "scream.res", false }, + { GID_IHNM_CD_DE, "32aa01a89937520fe0ea513950117292", "scripts.res", false }, + { GID_IHNM_CD_DE, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false }, + { GID_IHNM_CD_DE, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false }, + { GID_IHNM_CD_DE, "424971e1e2373187c3f5734fe36071a2", "voices1.res", false }, + { GID_IHNM_CD_DE, "c270e0980782af43641a86e4a14e2a32", "voices2.res", false }, + { GID_IHNM_CD_DE, "49e42befea883fd101ec3d0f5d0647b9", "voices3.res", false }, + { GID_IHNM_CD_DE, "c477443c52a0aa56e686ebd8d051e4ab", "voices5.res", false }, + { GID_IHNM_CD_DE, "2b9aea838f74b4eecfb29a8f205a2bd4", "voices6.res", false }, + { GID_IHNM_CD_DE, "8b09a196a52627cacb4eab13bfe0b2c3", "voicess.res", false }, + + { GID_IHNM_CD_ES, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false }, + { GID_IHNM_CD_ES, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false }, + { GID_IHNM_CD_ES, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false }, + { GID_IHNM_CD_ES, "c92370d400e6f2a3fc411c3729d09224", "scream.res", false }, + { GID_IHNM_CD_ES, "be38bbc5a26be809dbf39f13befebd01", "scripts.res", false }, + { GID_IHNM_CD_ES, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false }, + { GID_IHNM_CD_ES, "dc6a34e3d1668730ea46815a92c7847f", "voices1.res", false }, + { GID_IHNM_CD_ES, "dc6a5fa7a4cdc2ca5a6fd924e969986c", "voices2.res", false }, + { GID_IHNM_CD_ES, "dc6a5fa7a4cdc2ca5a6fd924e969986c", "voices3.res", false }, + { GID_IHNM_CD_ES, "0f87400b804232a58dd22e404420cc45", "voices4.res", false }, + { GID_IHNM_CD_ES, "172668cfc5d8c305cb5b1a9b4d995fc0", "voices5.res", false }, + { GID_IHNM_CD_ES, "96c9bda9a5f41d6bc232ed7bf6d371d9", "voices6.res", false }, + { GID_IHNM_CD_ES, "d869de9883c8faea7f687217a9ec7057", "voicess.res", false }, + + { GID_IHNM_CD_RU, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false }, + { GID_IHNM_CD_RU, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false }, + { GID_IHNM_CD_RU, "46bbdc65d164ba7e89836a0935eec8e6", "scream.res", false }, + { GID_IHNM_CD_RU, "be38bbc5a26be809dbf39f13befebd01", "scripts.res", false }, + { GID_IHNM_CD_RU, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false }, + { GID_IHNM_CD_RU, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false }, + { GID_IHNM_CD_RU, "d6100d2dc3b2b9f2e1ad247f613dce9b", "voices1.res", false }, + { GID_IHNM_CD_RU, "84f6f48ecc2832841ea6417a9a379430", "voices2.res", false }, + { GID_IHNM_CD_RU, "ebb9501283047f27a0f54e27b3c8ba1e", "voices3.res", false }, + { GID_IHNM_CD_RU, "4c145da5fa6d1306162a7ca8ce5a4f2e", "voices4.res", false }, + { GID_IHNM_CD_RU, "871a559644281917677eca4af1b05620", "voices5.res", false }, + { GID_IHNM_CD_RU, "211be5c24f066d69a2f6cfa953acfba6", "voices6.res", false }, + { GID_IHNM_CD_RU, "9df7cd3b18ddaa16b5291b3432567036", "voicess.res", false }, + + { GID_IHNM_CD_FR, "0439083e3dfdc51b486071d45872ae52", "musicfm.res", false }, + { GID_IHNM_CD_FR, "80f875a1fb384160d1f4b27166eef583", "musicgm.res", false }, + { GID_IHNM_CD_FR, "58b79e61594779513c7f2d35509fa89e", "patch.re_", false }, + { GID_IHNM_CD_FR, "c92370d400e6f2a3fc411c3729d09224", "scream.res", false }, + { GID_IHNM_CD_FR, "32aa01a89937520fe0ea513950117292", "scripts.res", false }, + { GID_IHNM_CD_FR, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false }, + { GID_IHNM_CD_FR, "424971e1e2373187c3f5734fe36071a2", "voices1.res", false }, + { GID_IHNM_CD_FR, "c2d93a35d2c2def9c3d6d242576c794b", "voices2.res", false }, + { GID_IHNM_CD_FR, "49e42befea883fd101ec3d0f5d0647b9", "voices3.res", false }, + { GID_IHNM_CD_FR, "f4c415de7c03de86b73f9a12b8bd632f", "voices5.res", false }, + { GID_IHNM_CD_FR, "3fc5358a5d8eee43bdfab2740276572e", "voices6.res", false }, + { GID_IHNM_CD_FR, "b8642e943bbebf89cef2f48b31cb4305", "voicess.res", false }, + + { GID_IHNM_DEMO, "46bbdc65d164ba7e89836a0935eec8e6", "scream.res", false }, + { GID_IHNM_DEMO, "9626bda8978094ff9b29198bc1ed5f9a", "scripts.res", false }, + { GID_IHNM_DEMO, "1c610d543f32ec8b525e3f652536f269", "sfx.res", false }, + { GID_IHNM_DEMO, "3bbc16a8f741dbb511da506c660a0b54", "voicesd.res", false }, +}; + +static GameDescription gameDescriptions[] = { + // Inherit the earth - DOS Demo version + // sound unchecked + { + "ite", + GType_ITE, + GID_ITE_DEMO_G, // Game id + "Inherit the Earth: Quest for the Orb (DOS Demo)", // Game title + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, // Starting scene number + &ITEDemo_Resources, + ARRAYSIZE(ITEDEMO_GameFiles), // Game datafiles + ITEDEMO_GameFiles, + ARRAYSIZE(ITEDEMO_GameFonts), + ITEDEMO_GameFonts, + &ITEDEMO_GameSound, + &ITEDEMO_GameSound, + NULL, + 0, + NULL, + 0, // features + Common::EN_USA, + Common::kPlatformPC, + }, + + // Inherit the earth - MAC Demo version + { + "ite", + GType_ITE, + GID_ITE_MACDEMO2, + "Inherit the Earth: Quest for the Orb (MAC Demo)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEMACDEMO_GameFiles), + ITEMACDEMO_GameFiles, + ARRAYSIZE(ITEWINDEMO_GameFonts), + ITEWINDEMO_GameFonts, + &ITEMACDEMO_GameVoice, + &ITEMACDEMO_GameSound, + &ITEMACDEMO_GameMusic, + ARRAYSIZE(ITEMacPatch_Files), + ITEMacPatch_Files, + GF_BIG_ENDIAN_DATA | GF_WYRMKEEP | GF_CD_FX | GF_SCENE_SUBSTITUTES, + Common::EN_USA, + Common::kPlatformMacintosh, + }, + + // Inherit the earth - early MAC Demo version + { + "ite", + GType_ITE, + GID_ITE_MACDEMO1, + "Inherit the Earth: Quest for the Orb (early MAC Demo)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEMACDEMO_GameFiles), + ITEMACDEMO_GameFiles, + ARRAYSIZE(ITEWINDEMO_GameFonts), + ITEWINDEMO_GameFonts, + &ITEMACDEMO_GameVoice, + &ITEMACDEMO_GameSound, + &ITEMACCD_GameMusic, + ARRAYSIZE(ITEMacPatch_Files), + ITEMacPatch_Files, + GF_BIG_ENDIAN_DATA | GF_WYRMKEEP | GF_CD_FX, + Common::EN_USA, + Common::kPlatformMacintosh, + }, + + // Inherit the earth - MAC CD Guild version + { + "ite", + GType_ITE, + GID_ITE_MACCD_G, + "Inherit the Earth: Quest for the Orb (MAC CD)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEMACCD_G_GameFiles), + ITEMACCD_G_GameFiles, + ARRAYSIZE(ITEWINDEMO_GameFonts), + ITEWINDEMO_GameFonts, + &ITEMACCD_G_GameSound, + &ITEMACCD_G_GameSound, + NULL, + 0, + NULL, + GF_BIG_ENDIAN_DATA | GF_CD_FX, + Common::EN_USA, + Common::kPlatformMacintosh, + }, + + // Inherit the earth - MAC CD Wyrmkeep version + { + "ite", + GType_ITE, + GID_ITE_MACCD, + "Inherit the Earth: Quest for the Orb (Wyrmkeep MAC CD)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEMACCD_GameFiles), + ITEMACCD_GameFiles, + ARRAYSIZE(ITEWINDEMO_GameFonts), + ITEWINDEMO_GameFonts, + &ITEMACCD_GameSound, + &ITEMACCD_GameSound, + &ITEMACCD_GameMusic, + ARRAYSIZE(ITEMacPatch_Files), + ITEMacPatch_Files, + GF_BIG_ENDIAN_DATA | GF_WYRMKEEP | GF_CD_FX, + Common::EN_USA, + Common::kPlatformMacintosh, + }, + + // Inherit the earth - Linux Demo version + // Note: it should be before GID_ITE_WINDEMO2 version + { + "ite", + GType_ITE, + GID_ITE_LINDEMO, + "Inherit the Earth: Quest for the Orb (Linux Demo)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITELINDEMO_GameFiles), + ITELINDEMO_GameFiles, + ARRAYSIZE(ITEWINDEMO_GameFonts), + ITEWINDEMO_GameFonts, + &ITEWINDEMO2_GameVoice, + &ITEWINDEMO2_GameSound, + &ITELINDEMO_GameMusic, + ARRAYSIZE(ITELinPatch_Files), + ITELinPatch_Files, + GF_WYRMKEEP | GF_CD_FX | GF_SCENE_SUBSTITUTES, + Common::EN_USA, + Common::kPlatformLinux, + }, + + // Inherit the earth - Win32 Demo version + { + "ite", + GType_ITE, + GID_ITE_WINDEMO2, + "Inherit the Earth: Quest for the Orb (Win32 Demo)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEWINDEMO_GameFiles), + ITEWINDEMO_GameFiles, + ARRAYSIZE(ITEWINDEMO_GameFonts), + ITEWINDEMO_GameFonts, + &ITEWINDEMO2_GameVoice, + &ITEWINDEMO2_GameSound, + NULL, + ARRAYSIZE(ITEWinPatch2_Files), + ITEWinPatch2_Files, + GF_WYRMKEEP | GF_CD_FX | GF_SCENE_SUBSTITUTES, + Common::EN_USA, + Common::kPlatformWindows, + }, + + // Inherit the earth - early Win32 Demo version + { + "ite", + GType_ITE, + GID_ITE_WINDEMO1, + "Inherit the Earth: Quest for the Orb (early Win32 Demo)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEWINDEMO_GameFiles), + ITEWINDEMO_GameFiles, + ARRAYSIZE(ITEWINDEMO_GameFonts), + ITEWINDEMO_GameFonts, + &ITEWINDEMO1_GameSound, + &ITEWINDEMO1_GameSound, + NULL, + ARRAYSIZE(ITEWinPatch1_Files), + ITEWinPatch1_Files, + GF_WYRMKEEP | GF_CD_FX, + Common::EN_USA, + Common::kPlatformWindows, + }, + + // Inherit the earth - Wyrmkeep combined Windows/Mac/Linux CD + { + "ite", + GType_ITE, + GID_ITE_MULTICD, + "Inherit the Earth: Quest for the Orb (Multi-OS CD Version)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEMULTICD_GameFiles), + ITEMULTICD_GameFiles, + ARRAYSIZE(ITECD_GameFonts), + ITECD_GameFonts, + &ITEMACCD_GameSound, + &ITECD_GameSound, + &ITEMACCD_GameMusic, + 0, + NULL, + GF_WYRMKEEP | GF_CD_FX, + Common::EN_USA, + Common::kPlatformUnknown, + }, + + // Inherit the earth - Wyrmkeep Linux CD version + { + "ite", + GType_ITE, + GID_ITE_LINCD, + "Inherit the Earth: Quest for the Orb (Linux CD Version)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITELINCD_GameFiles), + ITELINCD_GameFiles, + ARRAYSIZE(ITECD_GameFonts), + ITECD_GameFonts, + &ITECD_GameSound, + &ITECD_GameSound, + &ITEMACCD_GameMusic, + ARRAYSIZE(ITELinPatch_Files), + ITELinPatch_Files, + GF_WYRMKEEP | GF_CD_FX, + Common::EN_USA, + Common::kPlatformLinux, + }, + + // Inherit the earth - Wyrmkeep Windows CD version + { + "ite", + GType_ITE, + GID_ITE_WINCD, + "Inherit the Earth: Quest for the Orb (Win32 CD Version)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITECD_GameFiles), + ITECD_GameFiles, + ARRAYSIZE(ITECD_GameFonts), + ITECD_GameFonts, + &ITECD_GameSound, + &ITECD_GameSound, + NULL, + ARRAYSIZE(ITEWinPatch1_Files), + ITEWinPatch1_Files, + GF_WYRMKEEP | GF_CD_FX, + Common::EN_USA, + Common::kPlatformWindows, + }, + + // Inherit the earth - DOS CD version + { + "ite", + GType_ITE, + GID_ITE_CD_G, + "Inherit the Earth: Quest for the Orb (DOS CD Version)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITECD_GameFiles), + ITECD_GameFiles, + ARRAYSIZE(ITECD_GameFonts), + ITECD_GameFonts, + &ITECD_GameSound, + &ITECD_GameSound, + NULL, + 0, + NULL, + GF_CD_FX, + Common::EN_USA, + Common::kPlatformPC, + }, + + // Inherit the earth - DOS CD version with digital music + { + "ite", + GType_ITE, + GID_ITE_CD_G2, + "Inherit the Earth: Quest for the Orb (DOS CD Version)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITECD2_GameFiles), + ITECD2_GameFiles, + ARRAYSIZE(ITECD_GameFonts), + ITECD_GameFonts, + &ITECD_GameSound, + &ITECD_GameSound, + &ITEMACCD_GameMusic, + 0, + NULL, + GF_CD_FX, + Common::EN_USA, + Common::kPlatformPC, + }, + + // Inherit the earth - DOS CD German version + { + "ite", + GType_ITE, + GID_ITE_CD_DE, + "Inherit the Earth: Quest for the Orb (De DOS CD Version)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITECD_GameFiles), + ITECD_GameFiles, + ARRAYSIZE(ITECD_GameFonts), + ITECD_GameFonts, + &ITECD_GameSound, + &ITECD_GameSound, + NULL, + 0, + NULL, + GF_CD_FX, + Common::DE_DEU, + Common::kPlatformPC, + }, + + // Inherit the earth - DOS CD German version with digital music + { + "ite", + GType_ITE, + GID_ITE_CD_DE2, + "Inherit the Earth: Quest for the Orb (De DOS CD Version)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITECD2_GameFiles), + ITECD2_GameFiles, + ARRAYSIZE(ITECD_GameFonts), + ITECD_GameFonts, + &ITECD_GameSound, + &ITECD_GameSound, + &ITEMACCD_GameMusic, + 0, + NULL, + GF_CD_FX, + Common::DE_DEU, + Common::kPlatformPC, + }, + + // Inherit the earth - CD version + { + "ite", + GType_ITE, + GID_ITE_CD, + "Inherit the Earth: Quest for the Orb (DOS CD Version)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITECD_GameFiles), + ITECD_GameFiles, + ARRAYSIZE(ITECD_GameFonts), + ITECD_GameFonts, + &ITECD_GameSound, + &ITECD_GameSound, + NULL, + 0, + NULL, + GF_CD_FX, + Common::EN_USA, + Common::kPlatformPC, + }, + + // Inherit the earth - German Floppy version + { + "ite", + GType_ITE, + GID_ITE_DISK_DE, + "Inherit the Earth: Quest for the Orb (De DOS Floppy)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEDISK_GameFiles), + ITEDISK_GameFiles, + ARRAYSIZE(ITEDISK_GameFonts), + ITEDISK_GameFonts, + &ITEDISK_GameSound, + &ITEDISK_GameSound, + NULL, + 0, + NULL, + 0, + Common::DE_DEU, + Common::kPlatformPC, + }, + + // Inherit the earth - German Floppy version with digital music + { + "ite", + GType_ITE, + GID_ITE_DISK_DE2, + "Inherit the Earth: Quest for the Orb (De DOS Floppy)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEDISK2_GameFiles), + ITEDISK2_GameFiles, + ARRAYSIZE(ITEDISK_GameFonts), + ITEDISK_GameFonts, + &ITEDISK_GameSound, + &ITEDISK_GameSound, + &ITEMACCD_GameMusic, + 0, + NULL, + 0, + Common::DE_DEU, + Common::kPlatformPC, + }, + + // Inherit the earth - Disk version + { + "ite", + GType_ITE, + GID_ITE_DISK_G, + "Inherit the Earth: Quest for the Orb (DOS Floppy)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEDISK_GameFiles), + ITEDISK_GameFiles, + ARRAYSIZE(ITEDISK_GameFonts), + ITEDISK_GameFonts, + &ITEDISK_GameSound, + &ITEDISK_GameSound, + NULL, + 0, + NULL, + 0, + Common::EN_USA, + Common::kPlatformPC, + }, + + // Inherit the earth - Disk version with digital music + { + "ite", + GType_ITE, + GID_ITE_DISK_G2, + "Inherit the Earth: Quest for the Orb (DOS Floppy)", + &ITE_DisplayInfo, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITEDISK2_GameFiles), + ITEDISK2_GameFiles, + ARRAYSIZE(ITEDISK_GameFonts), + ITEDISK_GameFonts, + &ITEDISK_GameSound, + &ITEDISK_GameSound, + &ITEMACCD_GameMusic, + 0, + NULL, + 0, + Common::EN_USA, + Common::kPlatformPC, + }, + + // I Have No Mouth And I Must Scream - Demo version + { + "ihnm", + GType_IHNM, + GID_IHNM_DEMO, + "I Have No Mouth and I Must Scream (DOS Demo)", + &IHNM_DisplayInfo, + 0, + &IHNM_Resources, + ARRAYSIZE(IHNMDEMO_GameFiles), + IHNMDEMO_GameFiles, + ARRAYSIZE(IHNMDEMO_GameFonts), + IHNMDEMO_GameFonts, + &IHNM_GameSound, + &IHNM_GameSound, + NULL, + 0, + NULL, + GF_DEFAULT_TO_1X_SCALER, + Common::EN_USA, + Common::kPlatformPC, + }, + + // I Have No Mouth And I Must Scream - CD version + { + "ihnm", + GType_IHNM, + GID_IHNM_CD, + "I Have No Mouth and I Must Scream (DOS)", + &IHNM_DisplayInfo, + IHNM_DEFAULT_SCENE, + &IHNM_Resources, + ARRAYSIZE(IHNMCD_GameFiles), + IHNMCD_GameFiles, + ARRAYSIZE(IHNMCD_GameFonts), + IHNMCD_GameFonts, + &IHNM_GameSound, + &IHNM_GameSound, + NULL, + 0, + NULL, + GF_DEFAULT_TO_1X_SCALER, + Common::EN_USA, + Common::kPlatformPC, + }, + + // I Have No Mouth And I Must Scream - De CD version + { + "ihnm", + GType_IHNM, + GID_IHNM_CD_DE, + "I Have No Mouth and I Must Scream (DE DOS)", + &IHNM_DisplayInfo, + IHNM_DEFAULT_SCENE, + &IHNM_Resources, + ARRAYSIZE(IHNMCD_Censored_GameFiles), + IHNMCD_Censored_GameFiles, + ARRAYSIZE(IHNMCD_GameFonts), + IHNMCD_GameFonts, + &IHNM_GameSound, + &IHNM_GameSound, + NULL, + 0, + NULL, + GF_DEFAULT_TO_1X_SCALER, + Common::DE_DEU, + Common::kPlatformPC, + }, + // I Have No Mouth And I Must Scream - Sp CD version + { + "ihnm", + GType_IHNM, + GID_IHNM_CD_ES, + "I Have No Mouth and I Must Scream (Sp DOS)", + &IHNM_DisplayInfo, + IHNM_DEFAULT_SCENE, + &IHNM_Resources, + ARRAYSIZE(IHNMCD_GameFiles), + IHNMCD_GameFiles, + ARRAYSIZE(IHNMCD_GameFonts), + IHNMCD_GameFonts, + &IHNM_GameSound, + &IHNM_GameSound, + NULL, + 0, + NULL, + GF_DEFAULT_TO_1X_SCALER, + Common::ES_ESP, + Common::kPlatformPC, + }, + // I Have No Mouth And I Must Scream - Ru CD version + { + "ihnm", + GType_IHNM, + GID_IHNM_CD_RU, + "I Have No Mouth and I Must Scream (Ru DOS)", + &IHNM_DisplayInfo, + IHNM_DEFAULT_SCENE, + &IHNM_Resources, + ARRAYSIZE(IHNMCD_GameFiles), + IHNMCD_GameFiles, + ARRAYSIZE(IHNMCD_GameFonts), + IHNMCD_GameFonts, + &IHNM_GameSound, + &IHNM_GameSound, + NULL, + 0, + NULL, + GF_DEFAULT_TO_1X_SCALER, + Common::RU_RUS, + Common::kPlatformPC, + }, + // I Have No Mouth And I Must Scream - Fr CD version + { + "ihnm", + GType_IHNM, + GID_IHNM_CD_FR, + "I Have No Mouth and I Must Scream (Fr DOS)", + &IHNM_DisplayInfo, + IHNM_DEFAULT_SCENE, + &IHNM_Resources, + ARRAYSIZE(IHNMCD_Censored_GameFiles), + IHNMCD_Censored_GameFiles, + ARRAYSIZE(IHNMCD_GameFonts), + IHNMCD_GameFonts, + &IHNM_GameSound, + &IHNM_GameSound, + NULL, + 0, + NULL, + GF_DEFAULT_TO_1X_SCALER, + Common::FR_FRA, + Common::kPlatformPC, + }, +}; + +bool SagaEngine::initGame() { + uint16 gameCount = ARRAYSIZE(gameDescriptions); + int gameNumber = -1; + FSList dummy; + DetectedGameList detectedGames; + int *matches; + Common::Language language = Common::UNK_LANG; + Common::Platform platform = Common::kPlatformUnknown; + + if (ConfMan.hasKey("language")) + language = Common::parseLanguage(ConfMan.get("language")); + if (ConfMan.hasKey("platform")) + platform = Common::parsePlatform(ConfMan.get("platform")); + + + detectedGames = GAME_ProbeGame(dummy, &matches); + + if (detectedGames.size() == 0) { + warning("No valid games were found in the specified directory."); + return false; + } + + // If we have more than one match then try to match by platform and + // language + int count = 0; + if (detectedGames.size() > 1) { + for (int i = 0; i < ARRAYSIZE(gameDescriptions); i++) + if (matches[i] != -1) { + if ((gameDescriptions[matches[i]].language != language && + language != Common::UNK_LANG) || + (gameDescriptions[matches[i]].platform != platform && + platform != Common::kPlatformUnknown)) { + debug(2, "Purged (pass 2) %s", gameDescriptions[matches[i]].title); + matches[i] = -1; + } + else + count++; + } + } else + count = 1; + + if (count != 1) + warning("Conflicting targets detected (%d)", count); + + for (int i = 0; i < ARRAYSIZE(gameDescriptions); i++) + if (matches[i] != -1) { + gameNumber = matches[i]; + break; + } + + free(matches); + + if (gameNumber >= gameCount || gameNumber == -1) { + error("SagaEngine::loadGame wrong gameNumber"); + } + + debug(2, "Running %s", gameDescriptions[gameNumber].title); + + _gameNumber = gameNumber; + _gameDescription = &gameDescriptions[gameNumber]; + _gameDisplayInfo = *_gameDescription->gameDisplayInfo; + _displayClip.right = _gameDisplayInfo.logicalWidth; + _displayClip.bottom = _gameDisplayInfo.logicalHeight; + + if (!_resource->createContexts()) { + return false; + } + return true; +} + +DetectedGameList GAME_ProbeGame(const FSList &fslist, int **retmatches) { + DetectedGameList detectedGames; + int game_n; + int index = 0, i, j; + int matches[ARRAYSIZE(gameDescriptions)]; + bool mode = retmatches ? false : true; + + game_n = -1; + for (i = 0; i < ARRAYSIZE(gameDescriptions); i++) + matches[i] = -1; + + while (1) { + game_n = detectGame(fslist, mode, game_n); + if (game_n == -1) + break; + matches[index++] = game_n; + } + + // We have some resource sets which are superpositions of other + // Particularly it is ite-demo-linux vs ite-demo-win + // Now remove lesser set if bigger matches too + + if (index > 1) { + // Search max number + int maxcount = 0; + for (i = 0; i < index; i++) { + int count = 0; + for (j = 0; j < ARRAYSIZE(gameMD5); j++) + if (gameMD5[j].id == gameDescriptions[matches[i]].gameId) + count++; + maxcount = MAX(maxcount, count); + } + + // Now purge targets with number of files lesser than max + for (i = 0; i < index; i++) { + int count = 0; + for (j = 0; j < ARRAYSIZE(gameMD5); j++) + if (gameMD5[j].id == gameDescriptions[matches[i]].gameId) + count++; + if (count < maxcount) { + debug(2, "Purged: %s", gameDescriptions[matches[i]].title); + matches[i] = -1; + } + } + + } + + // and now push them into list of detected games + for (i = 0; i < index; i++) + if (matches[i] != -1) + detectedGames.push_back(DetectedGame(gameDescriptions[matches[i]].toGameSettings(), + gameDescriptions[matches[i]].language, + gameDescriptions[matches[i]].platform)); + + if (retmatches) { + *retmatches = (int *)calloc(ARRAYSIZE(gameDescriptions), sizeof(int)); + for (i = 0; i < ARRAYSIZE(gameDescriptions); i++) + (*retmatches)[i] = matches[i]; + } + + return detectedGames; +} + +int detectGame(const FSList &fslist, bool mode, int start) { + int game_count = ARRAYSIZE(gameDescriptions); + int game_n = -1; + typedef Common::Map<Common::String, Common::String> StringMap; + StringMap filesMD5; + + typedef Common::Map<Common::String, bool> StringSet; + StringSet filesList; + + uint16 file_count; + uint16 file_n; + Common::File test_file; + bool file_missing; + + Common::String tstr, tstr1; + char md5str[32+1]; + uint8 md5sum[16]; + + // First we compose list of files which we need MD5s for + for (int i = 0; i < ARRAYSIZE(gameMD5); i++) { + tstr = Common::String(gameMD5[i].filename); + tstr.toLowercase(); + + if (gameMD5[i].caseSensitive && !mode) + filesList[Common::String(gameMD5[i].filename)] = true; + else + filesList[tstr] = true; + } + + if (mode) { + // Now count MD5s for required files + for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { + if (!file->isDirectory()) { + tstr = file->displayName(); + // FIXME: there is a bug in String class. tstr1 = tstr; tstr.toLowercase() + // makes tstr1 lowercase as well + tstr1 = Common::String(file->displayName().c_str()); + tstr.toLowercase(); + + if (filesList.contains(tstr) || filesList.contains(tstr1)) { + if (Common::md5_file(file->path().c_str(), md5sum, NULL, FILE_MD5_BYTES)) { + for (int j = 0; j < 16; j++) { + sprintf(md5str + j*2, "%02x", (int)md5sum[j]); + } + filesMD5[tstr] = Common::String(md5str); + filesMD5[tstr1] = Common::String(md5str); + } + } + } + } + } else { + Common::File testFile; + + for (StringSet::const_iterator file = filesList.begin(); file != filesList.end(); ++file) { + if (testFile.open(file->_key.c_str())) { + testFile.close(); + if (Common::md5_file(file->_key.c_str(), md5sum, NULL, FILE_MD5_BYTES)) { + for (int j = 0; j < 16; j++) { + sprintf(md5str + j*2, "%02x", (int)md5sum[j]); + } + filesMD5[file->_key] = Common::String(md5str); + } + } + } + } + + for (game_n = start + 1; game_n < game_count; game_n++) { + file_count = gameDescriptions[game_n].filesCount; + file_missing = false; + + // Try to open all files for this game + for (file_n = 0; file_n < file_count; file_n++) { + tstr = gameDescriptions[game_n].filesDescriptions[file_n].fileName; + + if (!filesMD5.contains(tstr)) { + file_missing = true; + break; + } + } + + // Try the next game, couldn't find all files for the current + // game + if (file_missing) { + continue; + } else { + bool match = true; + + debug(2, "Probing game: %s", gameDescriptions[game_n].title); + + for (int i = 0; i < ARRAYSIZE(gameMD5); i++) { + if (gameMD5[i].id == gameDescriptions[game_n].gameId) { + tstr = gameMD5[i].filename; + + if (strcmp(gameMD5[i].md5, filesMD5[tstr].c_str())) { + match = false; + break; + } + } + } + if (!match) + continue; + + debug(2, "Found game: %s", gameDescriptions[game_n].title); + + return game_n; + } + } + + if (!filesMD5.isEmpty() && start == -1) { + printf("MD5s of your game version are unknown. Please, report following data to\n"); + printf("ScummVM team along with your game name and version:\n"); + + for (StringMap::const_iterator file = filesMD5.begin(); file != filesMD5.end(); ++file) + printf("%s: %s\n", file->_key.c_str(), file->_value.c_str()); + } + + return -1; +} + +} // End of namespace Saga diff --git a/engines/saga/gfx.cpp b/engines/saga/gfx.cpp new file mode 100644 index 0000000000..4de8c52de2 --- /dev/null +++ b/engines/saga/gfx.cpp @@ -0,0 +1,485 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Misc. graphics routines + +#include "saga/saga.h" +#include "saga/gfx.h" +#include "saga/interface.h" +#include "saga/resnames.h" +#include "saga/rscfile.h" +#include "saga/scene.h" +#include "saga/stream.h" + +#include "common/system.h" + +namespace Saga { + +Gfx::Gfx(SagaEngine *vm, OSystem *system, int width, int height, GameDetector &detector) : _vm(vm), _system(system) { + _system->beginGFXTransaction(); + _vm->initCommonGFX(detector); + _system->initSize(width, height); + _system->endGFXTransaction(); + + debug(5, "Init screen %dx%d", width, height); + // Convert surface data to R surface data + _backBuffer.create(width, height, 1); + + // Set module data + _init = 1; + + // 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(); +} + +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++; + } + } +} + +// * 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->x, points->y, color); + } +} + +/** +* Dissolve one image with another. +* If flags if 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->getGameType() != GType_IHNM) + return; + + ResourceContext *resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE); + if (resourceContext == NULL) { + error("Resource::loadGlobalResources() resource context not found"); + } + + byte *resourcePointer; + size_t resourceLength; + + _vm->_resource->loadResource(resourceContext, RID_IHNM_DEFAULT_PALETTE, + resourcePointer, resourceLength); + + MemoryReadStream metaS(resourcePointer, resourceLength); + + for(int i = 0; i < 256; i++) { + _globalPalette[i].red = metaS.readByte(); + _globalPalette[i].green = metaS.readByte(); + _globalPalette[i].blue = metaS.readByte(); + } + + free(resourcePointer); + + setPalette(_globalPalette, true); +} + +void Gfx::setPalette(const PalEntry *pal, bool full) { + int i; + byte *ppal; + int from, numcolors; + + if (_vm->getGameType() != GType_IHNM || full) { + from = 0; + numcolors = PAL_ENTRIES; + } else { + from = 0; + numcolors = 248; + } + + for (i = 0, ppal = &_currentPal[from * 4]; i < numcolors; i++, ppal += 4) { + ppal[0] = _globalPalette[i].red = pal[i].red; + ppal[1] = _globalPalette[i].green = pal[i].green; + ppal[2] = _globalPalette[i].blue = pal[i].blue; + ppal[3] = 0; + } + + // Make 256th color black. See bug #1256368 + if ((_vm->getPlatform() == Common::kPlatformMacintosh) && !_vm->_scene->isInIntro()) + memset(&_currentPal[255 * 4], 0, 4); + + _system->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[4 * n + 0] != r) { + _currentPal[4 * n + 0] = _globalPalette[n].red = r; + update = true; + } + if (_currentPal[4 * n + 1] != g) { + _currentPal[4 * n + 1] = _globalPalette[n].green = g; + update = true; + } + if (_currentPal[4 * n + 2] != b) { + _currentPal[4 * n + 2] = _globalPalette[n].blue = b; + update = true; + } + if (_currentPal[4 * n + 3] != 0) { + _currentPal[4 * n + 3] = 0; + update = true; + } + + if (update) + _system->setPalette(_currentPal, n, 1); +} + +void Gfx::getCurrentPal(PalEntry *src_pal) { + int i; + byte *ppal; + + for (i = 0, ppal = _currentPal; i < PAL_ENTRIES; i++, ppal += 4) { + 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->getGameType() != GType_IHNM) { + from = 0; + numcolors = PAL_ENTRIES; + } 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 < PAL_ENTRIES; i++, ppal += 4) { + 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; + } + ppal[3] = 0; + } + + // Make 256th color black. See bug #1256368 + if ((_vm->getPlatform() == Common::kPlatformMacintosh) && !_vm->_scene->isInIntro()) + memset(&_currentPal[255 * 4], 0, 4); + + _system->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->getGameType() != GType_IHNM) { + from = 0; + numcolors = PAL_ENTRIES; + } 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 < PAL_ENTRIES; i++, ppal += 4) { + if (i < from || i >= from + numcolors) + palE = &_globalPalette[i]; + else + palE = &srcPal[i]; + + new_entry = (int)(palE->red - palE->red * fpercent); + + if (new_entry < 0) { + ppal[0] = 0; + } else { + ppal[0] = (byte)new_entry; + } + + new_entry = (int)(palE->green - palE->green * fpercent); + + if (new_entry < 0) { + ppal[1] = 0; + } else { + ppal[1] = (byte) new_entry; + } + + new_entry = (int)(palE->blue - palE->blue * fpercent); + + if (new_entry < 0) { + ppal[2] = 0; + } else { + ppal[2] = (byte) new_entry; + } + ppal[3] = 0; + } + + // Make 256th color black. See bug #1256368 + if ((_vm->getPlatform() == Common::kPlatformMacintosh) && !_vm->_scene->isInIntro()) + memset(&_currentPal[255 * 4], 0, 4); + + _system->setPalette(_currentPal, 0, PAL_ENTRIES); +} + +void Gfx::showCursor(bool state) { + g_system->showMouse(state); +} + +void Gfx::setCursor(CursorType cursorType) { + if (_vm->getGameType() == GType_ITE) { + // Set up the mouse cursor + const byte A = kITEColorLightGrey; + const byte B = kITEColorWhite; + + const byte cursor_img[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, + }; + + _system->setMouseCursor(cursor_img, CURSOR_W, CURSOR_H, 3, 3, 0); + } else { + uint32 resourceId; + + switch (cursorType) { + case kCursorBusy: + resourceId = RID_IHNM_HOURGLASS_CURSOR; + break; + default: + resourceId = (uint32)-1; + break; + } + + byte *resource; + size_t resourceLength; + byte *image; + size_t imageLength; + int width, height; + + if (resourceId != (uint32)-1) { + ResourceContext *context = _vm->_resource->getContext(GAME_RESOURCEFILE); + + _vm->_resource->loadResource(context, resourceId, resource, resourceLength); + + _vm->decodeBGImage(resource, resourceLength, &image, &imageLength, &width, &height); + } else { + resource = NULL; + width = height = 31; + image = (byte *)calloc(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 + _system->setMouseCursor(image, width, height, 15, 15, 0); + + free(image); + free(resource); + } +} + +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; +} + +} // End of namespace Saga diff --git a/engines/saga/gfx.h b/engines/saga/gfx.h new file mode 100644 index 0000000000..ff96cbf081 --- /dev/null +++ b/engines/saga/gfx.h @@ -0,0 +1,164 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Graphics maniuplation routines - private header file + +#ifndef SAGA_GFX_H_ +#define SAGA_GFX_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; + } +}; + +#pragma START_PACK_STRUCTS +struct PalEntry { + byte red; + byte green; + byte blue; +} GCC_PACK; + +#pragma END_PACK_STRUCTS + +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 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); + frameRect(rect, color); + } + void drawRect(const Common::Rect &destRect, int color) { + Common::Rect rect(w , h); + rect.clip(destRect); + + if (rect.isValidRect()) { + fillRect(rect, color); + } + } +}; + +#define PAL_ENTRIES 256 + +#define CURSOR_W 7 +#define CURSOR_H 7 + +#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, GameDetector &detector); + ~Gfx(); + Surface *getBackBuffer() { + return &_backBuffer; + } + + void initPalette(); + void setPalette(const PalEntry *pal, bool full = false); + void setPaletteColor(int n, int r, int g, int b); + void getCurrentPal(PalEntry *src_pal); + void palToBlack(PalEntry *src_pal, double percent); + void blackToPal(PalEntry *src_pal, double percent); + void showCursor(bool state); + +private: + void setCursor(CursorType cursorType = kCursorNormal); + int _init; + Surface _backBuffer; + byte _currentPal[PAL_ENTRIES * 4]; + OSystem *_system; + SagaEngine *_vm; + + PalEntry _globalPalette[PAL_ENTRIES]; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/ihnm_introproc.cpp b/engines/saga/ihnm_introproc.cpp new file mode 100644 index 0000000000..881625c170 --- /dev/null +++ b/engines/saga/ihnm_introproc.cpp @@ -0,0 +1,331 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// "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/sndres.h" +#include "saga/music.h" + +#include "saga/scene.h" + +namespace Saga { + +SceneResourceData IHNM_IntroMovie1RL[] = { + {30, 2, 0, 0, false} , + {31, 14, 0, 0, false} +}; + +SceneDescription IHNM_IntroMovie1Desc = { + 0, 0, 0, 0, 0, 0, 0, 0, + IHNM_IntroMovie1RL, + ARRAYSIZE(IHNM_IntroMovie1RL) +}; + +SceneResourceData IHNM_IntroMovie2RL[] = { + {32, 2, 0, 0, false} , + {33, 14, 0, 0, false} +}; + +SceneDescription IHNM_IntroMovie2Desc = { + 0, 0, 0, 0, 0, 0, 0, 0, + IHNM_IntroMovie2RL, + ARRAYSIZE(IHNM_IntroMovie2RL) +}; + +SceneResourceData IHNM_IntroMovie3RL[] = { + {34, 2, 0, 0, false}, + {35, 14, 0, 0, false} +}; + +SceneDescription IHNM_IntroMovie3Desc = { + 0, 0, 0, 0, 0, 0, 0, 0, + IHNM_IntroMovie3RL, + ARRAYSIZE(IHNM_IntroMovie3RL) +}; + +SceneResourceData IHNM_IntroMovie4RL[] = { + {1227, 2, 0, 0, false}, + {1226, 14, 0, 0, false} +}; + +SceneDescription IHNM_IntroMovie4Desc = { + 0, 0, 0, 0, 0, 0, 0, 0, + IHNM_IntroMovie4RL, + ARRAYSIZE(IHNM_IntroMovie4RL) +}; + +LoadSceneParams IHNM_IntroList[] = { + {0, kLoadByDescription, &IHNM_IntroMovie1Desc, Scene::SC_IHNMIntroMovieProc1, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, + {0, kLoadByDescription, &IHNM_IntroMovie2Desc, Scene::SC_IHNMIntroMovieProc2, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, + {0, kLoadByDescription, &IHNM_IntroMovie3Desc, Scene::SC_IHNMIntroMovieProc3, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, +}; + +int Scene::IHNMStartProc() { + size_t n_introscenes; + size_t i; + + LoadSceneParams firstScene; + + // The original used the "play video" mechanism for the first part of + // the intro. We just use that panel mode. + + _vm->_interface->setMode(kPanelVideo); + + n_introscenes = ARRAYSIZE(IHNM_IntroList); + + for (i = 0; i < n_introscenes; i++) { + _vm->_scene->queueScene(&IHNM_IntroList[i]); + } + + firstScene.loadFlag = kLoadBySceneNumber; + firstScene.sceneDescriptor = -1; + firstScene.sceneDescription = NULL; + firstScene.sceneSkipTarget = true; + firstScene.sceneProc = NULL; + firstScene.transitionType = kTransitionFade; + firstScene.actorsEntrance = 0; + firstScene.chapter = -1; + + _vm->_scene->queueScene(&firstScene); + + return SUCCESS; +} + +int Scene::SC_IHNMIntroMovieProc1(int param, void *refCon) { + return ((Scene *)refCon)->IHNMIntroMovieProc1(param); +} + +int Scene::IHNMIntroMovieProc1(int param) { + Event event; + Event *q_event; + + 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; + + q_event = _vm->_events->queue(&event); + + _vm->_anim->setFrameTime(0, IHNM_INTRO_FRAMETIME); + _vm->_anim->setFlag(0, ANIM_FLAG_ENDSCENE); + + event.type = kEvTOneshot; + event.code = kAnimEvent; + event.op = kEventPlay; + event.param = 0; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + break; + default: + break; + } + + return 0; +} + +int Scene::SC_IHNMIntroMovieProc2(int param, void *refCon) { + return ((Scene *)refCon)->IHNMIntroMovieProc2(param); +} + +int Scene::IHNMIntroMovieProc2(int param) { + Event event; + Event *q_event; + PalEntry *pal; + + static PalEntry current_pal[PAL_ENTRIES]; + + switch (param) { + case SCENE_BEGIN: + // Fade to black out of the intro CyberDreams logo anim + _vm->_gfx->getCurrentPal(current_pal); + + event.type = kEvTContinuous; + event.code = kPalEvent; + event.op = kEventPalToBlack; + event.time = 0; + event.duration = IHNM_PALFADE_TIME; + event.data = current_pal; + + q_event = _vm->_events->queue(&event); + + // Background for intro scene is the first frame of the + // intro animation; display it but don't set palette + event.type = kEvTOneshot; + event.code = kBgEvent; + event.op = kEventDisplay; + event.param = kEvPNoSetPalette; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + _vm->_anim->setCycles(0, -1); + + // Unlike the original, we keep the logo spinning during the + // palette fades. We don't have to, but I think it looks better + // that way. + + event.type = kEvTOneshot; + event.code = kAnimEvent; + event.op = kEventPlay; + event.param = 0; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + // Fade in from black to the scene background palette + _vm->_scene->getBGPal(pal); + + event.type = kEvTContinuous; + event.code = kPalEvent; + event.op = kEventBlackToPal; + event.time = 0; + event.duration = IHNM_PALFADE_TIME; + event.data = pal; + + q_event = _vm->_events->chain(q_event, &event); + + // Fade to black after looping animation for a while + event.type = kEvTContinuous; + event.code = kPalEvent; + event.op = kEventPalToBlack; + event.time = IHNM_DGLOGO_TIME; + event.duration = IHNM_PALFADE_TIME; + event.data = pal; + + q_event = _vm->_events->chain(q_event, &event); + + // Queue end of scene + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + break; + default: + break; + } + + return 0; +} + +int Scene::SC_IHNMIntroMovieProc3(int param, void *refCon) { + return ((Scene *)refCon)->IHNMIntroMovieProc3(param); +} + +int Scene::IHNMIntroMovieProc3(int param) { + Event event; + Event *q_event; + PalEntry *pal; + static PalEntry current_pal[PAL_ENTRIES]; + + switch (param) { + case SCENE_BEGIN: + // Fade to black out of the intro DG logo anim + _vm->_gfx->getCurrentPal(current_pal); + + event.type = kEvTContinuous; + event.code = kPalEvent; + event.op = kEventPalToBlack; + event.time = 0; + event.duration = IHNM_PALFADE_TIME; + event.data = current_pal; + + q_event = _vm->_events->queue(&event); + + // Music, maestro + + // In the GM file, this music also appears as tracks 7, 13, 19, + // 25 and 31, but only track 1 sounds right with the FM music. + + event.type = kEvTOneshot; + event.code = kMusicEvent; + event.param = 1; + event.param2 = MUSIC_NORMAL; + event.op = kEventPlay; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + // Background for intro scene is the first frame of the intro + // animation; display it but don't set palette + event.type = kEvTOneshot; + event.code = kBgEvent; + event.op = kEventDisplay; + event.param = kEvPNoSetPalette; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + // Fade in from black to the scene background palette + _vm->_scene->getBGPal(pal); + + event.type = kEvTContinuous; + event.code = kPalEvent; + event.op = kEventBlackToPal; + event.time = 0; + event.duration = IHNM_PALFADE_TIME; + event.data = pal; + + q_event = _vm->_events->chain(q_event, &event); + + event.type = kEvTOneshot; + event.code = kAnimEvent; + event.op = kEventPlay; + event.param = 0; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + // Queue end of scene after a while + // TODO: I've increased the delay so the speech won't start + // until the music has ended. Could someone verify if that's + // the correct behaviour? + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = _vm->_music->hasAdlib() ? IHNM_TITLE_TIME_FM : IHNM_TITLE_TIME_GM; + + q_event = _vm->_events->chain(q_event, &event); + break; + default: + break; + } + + return 0; +} + +} // End of namespace Saga diff --git a/engines/saga/image.cpp b/engines/saga/image.cpp new file mode 100644 index 0000000000..b7ac53f179 --- /dev/null +++ b/engines/saga/image.cpp @@ -0,0 +1,439 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// SAGA Image resource management routines +#include "saga/saga.h" + +#include "saga/stream.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); + } +} + +int SagaEngine::decodeBGImage(const byte *image_data, size_t image_size, + byte **output_buf, size_t *output_buf_len, int *w, int *h, bool flip) { + ImageHeader hdr; + int modex_height; + const byte *RLE_data_ptr; + size_t RLE_data_len; + byte *decode_buf; + size_t decode_buf_len; + byte *out_buf; + size_t out_buf_len; + + if (image_size <= SAGA_IMAGE_DATA_OFFSET) { + error("decodeBGImage() Image size is way too small (%d)", image_size); + } + + MemoryReadStreamEndian readS(image_data, image_size, isBigEndian()); + + hdr.width = readS.readUint16(); + hdr.height = readS.readUint16(); + // The next four bytes of the image header aren't used. + readS.readUint16(); + readS.readUint16(); + + RLE_data_ptr = image_data + SAGA_IMAGE_DATA_OFFSET; + RLE_data_len = image_size - SAGA_IMAGE_DATA_OFFSET; + + modex_height = granulate(hdr.height, 4); + + decode_buf_len = hdr.width * modex_height; + decode_buf = (byte *)malloc(decode_buf_len); + + out_buf_len = hdr.width * hdr.height; + out_buf = (byte *)malloc(out_buf_len); + + if (decodeBGImageRLE(RLE_data_ptr, + RLE_data_len, decode_buf, decode_buf_len) != SUCCESS) { + free(decode_buf); + free(out_buf); + return FAILURE; + } + + unbankBGImage(out_buf, decode_buf, hdr.width, hdr.height); + + // For some reason bg images in IHNM are upside down + if (getGameType() == GType_IHNM && !flip) { + flipImage(out_buf, hdr.width, hdr.height); + } + + free(decode_buf); + + *output_buf_len = out_buf_len; + *output_buf = out_buf; + + *w = hdr.width; + *h = hdr.height; + + return SUCCESS; +} + +int SagaEngine::decodeBGImageRLE(const byte *inbuf, size_t inbuf_len, byte *outbuf, size_t outbuf_len) { + const byte *inbuf_ptr; + byte *outbuf_ptr; + 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_ptr = outbuf; + outbuf_remain = outbuf_len; + + inbuf_end = (inbuf + inbuf_len) - 1; + outbuf_end = (outbuf + outbuf_len) - 1; + + memset(outbuf, 0, outbuf_len); + + while ((inbuf_remain > 1) && (outbuf_remain > 0) && !decode_err) { + + if ((inbuf_ptr > inbuf_end) || (outbuf_ptr > outbuf_end)) { + return FAILURE; + } + + 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 FAILURE; + } + + 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 FAILURE; + } + + 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)) || (runcount > outbuf_remain)) { + return FAILURE; + } + + 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 FAILURE; + } + + 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 FAILURE; + } + + 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 FAILURE; + } + + inbuf_ptr++; + runcount = *inbuf_ptr++; + + if ((backtrack_amount > (outbuf_ptr - outbuf)) || (outbuf_remain < runcount)) { + return FAILURE; + } + + 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 FAILURE; + break; + } + } + + return SUCCESS; +} + +int SagaEngine::flipImage(byte *img_buf, int columns, int scanlines) { + int line; + byte *tmp_scan; + + byte *flip_p1; + byte *flip_p2; + + int flipcount = scanlines / 2; + + tmp_scan = (byte *)malloc(columns); + if (tmp_scan == NULL) { + return FAILURE; + } + + flip_p1 = img_buf; + flip_p2 = img_buf + (columns * (scanlines - 1)); + + for (line = 0; line < flipcount; line++) { + memcpy(tmp_scan, flip_p1, columns); + memcpy(flip_p1, flip_p2, columns); + memcpy(flip_p2, tmp_scan, columns); + flip_p1 += columns; + flip_p2 -= columns; + } + + free(tmp_scan); + + return SUCCESS; +} + +int 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; + byte *dst_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; + dst_p = dst_buf + columns; + + 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; + } + return SUCCESS; +} + +const byte *SagaEngine::getImagePal(const byte *image_data, size_t image_size) { + if (image_size <= SAGA_IMAGE_HEADER_LEN) { + return NULL; + } + + return image_data + SAGA_IMAGE_HEADER_LEN; +} + +} // End of namespace Saga diff --git a/engines/saga/input.cpp b/engines/saga/input.cpp new file mode 100644 index 0000000000..0abce03a31 --- /dev/null +++ b/engines/saga/input.cpp @@ -0,0 +1,163 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ +#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/system.h" + +namespace Saga { + +int SagaEngine::processInput() { + OSystem::Event event; + +// Point imousePt; + + while (g_system->pollEvent(event)) { + switch (event.type) { + case OSystem::EVENT_KEYDOWN: + if (event.kbd.flags == OSystem::KBD_CTRL) { + if (event.kbd.keycode == 'd') + _console->attach(); + } + if (_interface->_textInput || _interface->_statusTextInput) { + _interface->processAscii(event.kbd.ascii); + return SUCCESS; + } + + switch (event.kbd.keycode) { + case '#': + case '`': + case '~': + _console->attach(); + break; + case 'r': + _interface->draw(); + break; + +#if 0 + case 269: + case 270: + case 273: + case 274: + case 275: + case 276: + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _vm->_isoMap->_viewDiff += (event.kbd.keycode == 270) - (event.kbd.keycode == 269); + _vm->_isoMap->_viewScroll.y += (_vm->_isoMap->_viewDiff * (event.kbd.keycode == 274) - _vm->_isoMap->_viewDiff * (event.kbd.keycode == 273)); + _vm->_isoMap->_viewScroll.x += (_vm->_isoMap->_viewDiff * (event.kbd.keycode == 275) - _vm->_isoMap->_viewDiff * (event.kbd.keycode == 276)); + } + break; +#endif + case 282: // F1 + _render->toggleFlag(RF_SHOW_FPS); + _actor->_handleActionDiv = (_actor->_handleActionDiv == 15) ? 50 : 15; + break; + case 283: // F2 + _render->toggleFlag(RF_PALETTE_TEST); + break; + case 284: // F3 + _render->toggleFlag(RF_TEXT_TEST); + break; + case 285: // F4 + _render->toggleFlag(RF_OBJECTMAP_TEST); + break; + case 286: // F5 + if (_interface->getSaveReminderState() > 0) + _interface->setMode(kPanelOption); + break; + case 287: // F6 + _render->toggleFlag(RF_ACTOR_PATH_TEST); + break; + case 288: // F7 + //_actor->frameTest(); + break; + case 289: // F8 + break; + case 290: // F9 + _interface->keyBoss(); + break; + + // Actual game keys + case 32: // space + _actor->abortSpeech(); + break; + case 19: // pause + case 'z': + _render->toggleFlag(RF_RENDERPAUSE); + break; + default: + _interface->processAscii(event.kbd.ascii); + break; + } + break; + case OSystem::EVENT_KEYUP: + _interface->processKeyUp(event.kbd.ascii); + break; + case OSystem::EVENT_LBUTTONUP: + _leftMouseButtonPressed = false; + break; + case OSystem::EVENT_RBUTTONUP: + _rightMouseButtonPressed = false; + break; + case OSystem::EVENT_LBUTTONDOWN: + _leftMouseButtonPressed = true; + _mousePos = event.mouse; + _interface->update(_mousePos, UPDATE_LEFTBUTTONCLICK); + break; + case OSystem::EVENT_RBUTTONDOWN: + _rightMouseButtonPressed = true; + _mousePos = event.mouse; + _interface->update(_mousePos, UPDATE_RIGHTBUTTONCLICK); + break; + case OSystem::EVENT_WHEELUP: + _interface->update(_mousePos, UPDATE_WHEELUP); + break; + case OSystem::EVENT_WHEELDOWN: + _interface->update(_mousePos, UPDATE_WHEELDOWN); + break; + case OSystem::EVENT_MOUSEMOVE: + _mousePos = event.mouse; + break; + case OSystem::EVENT_QUIT: + shutDown(); + break; + default: + break; + } + } + + return SUCCESS; +} + + +} // End of namespace Saga + diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp new file mode 100644 index 0000000000..1d3b110068 --- /dev/null +++ b/engines/saga/interface.cpp @@ -0,0 +1,2453 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Game interface module +#include "saga/saga.h" + +#include "saga/gfx.h" +#include "saga/actor.h" +#include "saga/console.h" +#include "saga/events.h" +#include "saga/font.h" +#include "saga/objectmap.h" +#include "saga/isomap.h" +#include "saga/itedata.h" +#include "saga/music.h" +#include "saga/puzzle.h" +#include "saga/render.h" +#include "saga/scene.h" +#include "saga/script.h" +#include "saga/sound.h" +#include "saga/sprite.h" +#include "saga/rscfile.h" +#include "saga/resnames.h" + +#include "saga/interface.h" + +#include "common/config-manager.h" +#include "common/system.h" +#include "common/timer.h" + +namespace Saga { + +static int verbTypeToTextStringsIdLUT[2][kVerbTypeIdsMax] = { + {-1, + kTextPickUp, + kTextLookAt, + kTextWalkTo, + kTextTalkTo, + kTextOpen, + kTextClose, + kTextGive, + kTextUse, + -1, + -1, + -1, + -1, + -1, + -1}, + {-1, + 3, //TODO:check + 2, + 1, + 5, + 6, //TODO:check + 8, //TODO:check + 7, + 4} +}; + +Interface::Interface(SagaEngine *vm) : _vm(vm) { + byte *resource; + size_t resourceLength; + int i; + + // Load interface module resource file context + _interfaceContext = _vm->_resource->getContext(GAME_RESOURCEFILE); + if (_interfaceContext == NULL) { + error("Interface::Interface() resource context not found"); + } + + _mainPanel.buttons = _vm->getDisplayInfo().mainPanelButtons; + _mainPanel.buttonsCount = _vm->getDisplayInfo().mainPanelButtonsCount; + + for (i = 0; i < kVerbTypeIdsMax; i++) { + _verbTypeToPanelButton[i] = NULL; + } + + for (i = 0; i < _mainPanel.buttonsCount; i++) { + if (_mainPanel.buttons[i].type == kPanelButtonVerb) { + _verbTypeToPanelButton[_mainPanel.buttons[i].id] = &_mainPanel.buttons[i]; + } + } + + _vm->_resource->loadResource(_interfaceContext, _vm->getResourceDescription()->mainPanelResourceId, resource, resourceLength); + _vm->decodeBGImage(resource, resourceLength, &_mainPanel.image, + &_mainPanel.imageLength, &_mainPanel.imageWidth, &_mainPanel.imageHeight); + + free(resource); + + _conversePanel.buttons = _vm->getDisplayInfo().conversePanelButtons; + _conversePanel.buttonsCount = _vm->getDisplayInfo().conversePanelButtonsCount; + + _vm->_resource->loadResource(_interfaceContext, _vm->getResourceDescription()->conversePanelResourceId, resource, resourceLength); + _vm->decodeBGImage(resource, resourceLength, &_conversePanel.image, + &_conversePanel.imageLength, &_conversePanel.imageWidth, &_conversePanel.imageHeight); + free(resource); + + _optionPanel.buttons = _vm->getDisplayInfo().optionPanelButtons; + _optionPanel.buttonsCount = _vm->getDisplayInfo().optionPanelButtonsCount; + + _vm->_resource->loadResource(_interfaceContext, _vm->getResourceDescription()->optionPanelResourceId, resource, resourceLength); + _vm->decodeBGImage(resource, resourceLength, &_optionPanel.image, + &_optionPanel.imageLength, &_optionPanel.imageWidth, &_optionPanel.imageHeight); + free(resource); + + _vm->_sprite->loadList(_vm->getResourceDescription()->mainPanelSpritesResourceId, _mainPanel.sprites); + + if (_vm->getGameType() == GType_ITE) { + _vm->_sprite->loadList(_vm->getResourceDescription()->defaultPortraitsResourceId, _defPortraits); + } + + setPortraitBgColor(0, 0, 0); + + _mainPanel.x = _vm->getDisplayInfo().mainPanelXOffset; + _mainPanel.y = _vm->getDisplayInfo().mainPanelYOffset; + _mainPanel.currentButton = NULL; + _inventoryUpButton = _mainPanel.getButton(_vm->getDisplayInfo().inventoryUpButtonIndex); + _inventoryDownButton = _mainPanel.getButton(_vm->getDisplayInfo().inventoryDownButtonIndex); + + _conversePanel.x = _vm->getDisplayInfo().conversePanelXOffset; + _conversePanel.y = _vm->getDisplayInfo().conversePanelYOffset; + _conversePanel.currentButton = NULL; + _converseUpButton = _conversePanel.getButton(_vm->getDisplayInfo().converseUpButtonIndex); + _converseDownButton = _conversePanel.getButton(_vm->getDisplayInfo().converseDownButtonIndex); + + _leftPortrait = 0; + _rightPortrait = 0; + + _optionPanel.x = _vm->getDisplayInfo().optionPanelXOffset; + _optionPanel.y = _vm->getDisplayInfo().optionPanelYOffset; + _optionPanel.currentButton = NULL; + _optionSaveFileSlider = _optionPanel.getButton(_vm->getDisplayInfo().optionSaveFileSliderIndex); + _optionSaveFilePanel = _optionPanel.getButton(_vm->getDisplayInfo().optionSaveFilePanelIndex); + + _quitPanel.x = _vm->getDisplayInfo().quitPanelXOffset; + _quitPanel.y = _vm->getDisplayInfo().quitPanelYOffset; + _quitPanel.imageWidth = _vm->getDisplayInfo().quitPanelWidth; + _quitPanel.imageHeight = _vm->getDisplayInfo().quitPanelHeight; + _quitPanel.buttons = _vm->getDisplayInfo().quitPanelButtons; + _quitPanel.buttonsCount = _vm->getDisplayInfo().quitPanelButtonsCount; + _quitPanel.currentButton = NULL; + + _loadPanel.x = _vm->getDisplayInfo().loadPanelXOffset; + _loadPanel.y = _vm->getDisplayInfo().loadPanelYOffset; + _loadPanel.imageWidth = _vm->getDisplayInfo().loadPanelWidth; + _loadPanel.imageHeight = _vm->getDisplayInfo().loadPanelHeight; + _loadPanel.buttons = _vm->getDisplayInfo().loadPanelButtons; + _loadPanel.buttonsCount = _vm->getDisplayInfo().loadPanelButtonsCount; + _loadPanel.currentButton = NULL; + + _savePanel.x = _vm->getDisplayInfo().savePanelXOffset; + _savePanel.y = _vm->getDisplayInfo().savePanelYOffset; + _savePanel.imageWidth = _vm->getDisplayInfo().savePanelWidth; + _savePanel.imageHeight = _vm->getDisplayInfo().savePanelHeight; + _savePanel.buttons = _vm->getDisplayInfo().savePanelButtons; + _savePanel.buttonsCount = _vm->getDisplayInfo().savePanelButtonsCount; + _saveEdit = _savePanel.getButton(_vm->getDisplayInfo().saveEditIndex); + _savePanel.currentButton = NULL; + + _protectPanel.x = _vm->getDisplayInfo().protectPanelXOffset; + _protectPanel.y = _vm->getDisplayInfo().protectPanelYOffset; + _protectPanel.imageWidth = _vm->getDisplayInfo().protectPanelWidth; + _protectPanel.imageHeight = _vm->getDisplayInfo().protectPanelHeight; + _protectPanel.buttons = _vm->getDisplayInfo().protectPanelButtons; + _protectPanel.buttonsCount = _vm->getDisplayInfo().protectPanelButtonsCount; + _protectEdit = _protectPanel.getButton(_vm->getDisplayInfo().protectEditIndex); + _protectPanel.currentButton = NULL; + + _active = true; + _panelMode = _lockedMode = kPanelNull; + _savedMode = -1; + _bossMode = -1; + _fadeMode = kNoFade; + _inMainMode = false; + *_statusText = 0; + _statusOnceColor = -1; + + _inventoryCount = 0; + _inventoryPos = 0; + _inventoryStart = 0; + _inventoryEnd = 0; + _inventoryBox = 0; + _inventorySize = ITE_INVENTORY_SIZE; + _saveReminderState = 0; + + _optionSaveFileTop = 0; + _optionSaveFileTitleNumber = 0; + + _inventory = (uint16 *)calloc(_inventorySize, sizeof(uint16)); + if (_inventory == NULL) { + error("Interface::Interface(): not enough memory"); + } + + _textInputRepeatPhase = 0; + _textInput = false; + _statusTextInput = false; + _statusTextInputState = kStatusTextInputFirstRun; + + _disableAbortSpeeches = false; +} + +Interface::~Interface(void) { + free(_inventory); + + _mainPanel.sprites.freeMem(); + _defPortraits.freeMem(); + _scenePortraits.freeMem(); +} + +int Interface::activate() { + if (!_active) { + _active = true; + _vm->_script->_skipSpeeches = false; + _vm->_actor->_protagonist->_targetObject = ID_NOTHING; + unlockMode(); + if (_panelMode == kPanelMain){ + _saveReminderState = 1; + } + draw(); + } + _vm->_gfx->showCursor(true); + + return SUCCESS; +} + +int Interface::deactivate() { + if (_active) { + _active = false; + lockMode(); + setMode(kPanelNull); + } + _vm->_gfx->showCursor(false); + + return SUCCESS; +} + +void Interface::rememberMode() { + assert (_savedMode == -1); + + _savedMode = _panelMode; +} + +void Interface::restoreMode() { + assert (_savedMode != -1); + + _panelMode = _savedMode; + _savedMode = -1; + + draw(); +} + +void Interface::setMode(int mode) { + debug(1, "Interface::setMode %i", mode); + + if (mode == kPanelMain) { + _inMainMode = true; + _saveReminderState = 1; //TODO: blinking timeout + } else { + if (mode == kPanelConverse) { + _inMainMode = false; + } + _saveReminderState = 0; + } + + _panelMode = mode; + + switch (_panelMode) { + case kPanelMain: + if (_vm->getGameType() == GType_IHNM) + warning("FIXME: Implement IHNM differences from ExecuteInventoryPanel"); + + _mainPanel.currentButton = NULL; + break; + case kPanelConverse: + _conversePanel.currentButton = NULL; + converseDisplayText(); + break; + case kPanelOption: + _optionPanel.currentButton = NULL; + _vm->fillSaveList(); + calcOptionSaveSlider(); + if (_optionSaveFileTitleNumber >= _vm->getDisplayInfo().optionSaveFileVisible) { + _optionSaveFileTitleNumber = _vm->getDisplayInfo().optionSaveFileVisible - 1; + } + break; + case kPanelLoad: + _loadPanel.currentButton = NULL; + break; + case kPanelQuit: + _quitPanel.currentButton = NULL; + break; + case kPanelSave: + _savePanel.currentButton = NULL; + _textInputMaxWidth = _saveEdit->width - 10; + _textInput = true; + _textInputStringLength = strlen(_textInputString); + _textInputPos = _textInputStringLength + 1; + _textInputRepeatPhase = 0; + break; + case kPanelMap: + mapPanelShow(); + break; + case kPanelSceneSubstitute: + _vm->_render->setFlag(RF_DEMO_SUBST); + _vm->_gfx->getCurrentPal(_mapSavedPal); + break; + case kPanelChapterSelection: + break; + case kPanelBoss: + _vm->_render->setFlag(RF_DEMO_SUBST); + break; + case kPanelProtect: + _protectPanel.currentButton = NULL; + _textInputMaxWidth = _protectEdit->width - 10; + _textInput = true; + _textInputString[0] = 0; + _textInputStringLength = 0; + _textInputPos = _textInputStringLength + 1; + _textInputRepeatPhase = 0; + break; + } + + draw(); +} + +bool Interface::processAscii(uint16 ascii, bool synthetic) { + // TODO: Checking for Esc and Enter below is a bit hackish, and + // and probably only works with the English version. Maybe we should + // add a flag to the button so it can indicate if it's the default or + // cancel button? + + int i; + PanelButton *panelButton; + if (!synthetic) + _textInputRepeatPhase = 0; + if (_statusTextInput) { + processStatusTextInput(ascii); + return true; + } + + switch (_panelMode) { + case kPanelNull: + if (ascii == 27) { // Esc + if (_vm->_scene->isInIntro()) { + _vm->_scene->skipScene(); + } else { + if (!_disableAbortSpeeches) + _vm->_actor->abortAllSpeeches(); + } + return true; + } + break; + case kPanelCutaway: + if (ascii == 27) { // Esc + if (!_disableAbortSpeeches) + _vm->_actor->abortAllSpeeches(); + _vm->_scene->cutawaySkip(); + return true; + } + break; + case kPanelVideo: + if (ascii == 27) { // Esc + if (_vm->_scene->isInIntro()) { + _vm->_scene->skipScene(); + } else { + if (!_disableAbortSpeeches) + _vm->_actor->abortAllSpeeches(); + } + _vm->_scene->cutawaySkip(); + } + break; + case kPanelOption: + // TODO: check input dialog keys + if (ascii == 27 || ascii == 13) { // Esc or Enter + ascii = 'c'; //continue + } + + for (i = 0; i < _optionPanel.buttonsCount; i++) { + panelButton = &_optionPanel.buttons[i]; + if (panelButton->type == kPanelButtonOption) { + if (panelButton->ascii == ascii) { + setOption(panelButton); + return true; + } + } + } + break; + case kPanelSave: + if (_textInput && processTextInput(ascii)) { + return true; + } + + if (ascii == 27) { // Esc + ascii = 'c'; // cancel + } else if (ascii == 13) { // Enter + ascii = 's'; // save + } + + for (i = 0; i < _savePanel.buttonsCount; i++) { + panelButton = &_savePanel.buttons[i]; + if (panelButton->type == kPanelButtonSave) { + if (panelButton->ascii == ascii) { + setSave(panelButton); + return true; + } + } + } + break; + case kPanelQuit: + if (ascii == 27) { // Esc + ascii = 'c'; // cancel + } else if (ascii == 13) { // Enter + ascii = 'q'; // quit + } + + for (i = 0; i < _quitPanel.buttonsCount; i++) { + panelButton = &_quitPanel.buttons[i]; + if (panelButton->type == kPanelButtonQuit) { + if (panelButton->ascii == ascii) { + setQuit(panelButton); + return true; + } + } + } + break; + case kPanelLoad: + for (i = 0; i < _loadPanel.buttonsCount; i++) { + panelButton = &_loadPanel.buttons[i]; + if (panelButton->type == kPanelButtonLoad) { + if (panelButton->ascii == ascii) { + setLoad(panelButton); + return true; + } + } + } + break; + case kPanelMain: + for (i = 0; i < _mainPanel.buttonsCount; i++) { + panelButton = &_mainPanel.buttons[i]; + if (panelButton->ascii == ascii) { + if (panelButton->type == kPanelButtonVerb) { + _vm->_script->setVerb(panelButton->id); + } + if (panelButton->type == kPanelButtonArrow) { + inventoryChangePos(panelButton->id); + } + return true; + } + } + if (ascii == 15) // ctrl-o + { + if (_saveReminderState > 0) { + setMode(kPanelOption); + return true; + } + } + break; + case kPanelConverse: + switch (ascii) { + case 'x': + setMode(kPanelMain); + if (_vm->_puzzle->isActive()) + _vm->_puzzle->exitPuzzle(); + break; + + case 'u': + converseChangePos(-1); + break; + + case 'd': + converseChangePos(1); + break; + + case '1': + case '2': + case '3': + case '4': + converseSetPos(ascii); + break; + } + break; + case kPanelMap: + mapPanelClean(); + break; + case kPanelSceneSubstitute: + if (ascii == 13) { + _vm->_render->clearFlag(RF_DEMO_SUBST); + _vm->_gfx->setPalette(_mapSavedPal); + setMode(kPanelMain); + _vm->_script->setNoPendingVerb(); + } else if (ascii == 'q' || ascii == 'Q') { + _vm->shutDown(); + } + break; + case kPanelBoss: + _vm->_render->clearFlag(RF_DEMO_SUBST); + keyBossExit(); + break; + case kPanelProtect: + if (_textInput && processTextInput(ascii)) { + return true; + } + + if (ascii == 27 || ascii == 13) { // Esc or Enter + _vm->_script->wakeUpThreads(kWaitTypeRequest); + _vm->_interface->setMode(kPanelMain); + + _protectHash = 0; + + for (char *p = _textInputString; *p; p++) + _protectHash = (_protectHash << 1) + toupper(*p); + } + break; + } + return false; +} + +#define KEYBOARD_REPEAT_DELAY1 300000L +#define KEYBOARD_REPEAT_DELAY2 50000L + +void Interface::textInputRepeatCallback(void *refCon) { + ((Interface *)refCon)->textInputRepeat(); +} + +void Interface::textInputStartRepeat(uint16 ascii) { + if (!_textInputRepeatPhase) { + _textInputRepeatPhase = 1; + Common::g_timer->removeTimerProc(&textInputRepeatCallback); + Common::g_timer->installTimerProc(&textInputRepeatCallback, KEYBOARD_REPEAT_DELAY1, this); + } + + _textInputRepeatChar = ascii; +} + +void Interface::textInputRepeat() { + if (_textInputRepeatPhase == 1) { + _textInputRepeatPhase = 2; + Common::g_timer->removeTimerProc(&textInputRepeatCallback); + Common::g_timer->installTimerProc(&textInputRepeatCallback, KEYBOARD_REPEAT_DELAY2, this); + } else if (_textInputRepeatPhase == 2) { + processAscii(_textInputRepeatChar, true); + } +} + +void Interface::processKeyUp(uint16 ascii) { + if (_textInputRepeatPhase) { + Common::g_timer->removeTimerProc(&textInputRepeatCallback); + _textInputRepeatPhase = 0; + } +} + +void Interface::setStatusText(const char *text, int statusColor) { + assert(text != NULL); + assert(strlen(text) < STATUS_TEXT_LEN); + + if (_vm->_render->getFlags() & (RF_PLACARD | RF_MAP)) + return; + + strncpy(_statusText, text, STATUS_TEXT_LEN); + _statusOnceColor = statusColor; + drawStatusBar(); +} + +void Interface::loadScenePortraits(int resourceId) { + _scenePortraits.freeMem(); + + _vm->_sprite->loadList(resourceId, _scenePortraits); +} + +void Interface::drawVerbPanel(Surface *backBuffer, PanelButton* panelButton) { + PanelButton * rightButtonVerbPanelButton; + PanelButton * currentVerbPanelButton; + KnownColor textColor; + int spriteNumber; + Point point; + + rightButtonVerbPanelButton = getPanelButtonByVerbType(_vm->_script->getRightButtonVerb()); + currentVerbPanelButton = getPanelButtonByVerbType(_vm->_script->getCurrentVerb()); + + if (panelButton->state) { + textColor = kKnownColorVerbTextActive; + } else if (panelButton == rightButtonVerbPanelButton) { + textColor = kKnownColorVerbTextActive; + } else { + textColor = kKnownColorVerbText; + } + + if (panelButton == currentVerbPanelButton) { + spriteNumber = panelButton->downSpriteNumber; + } else { + spriteNumber = panelButton->upSpriteNumber; + } + point.x = _mainPanel.x + panelButton->xOffset; + point.y = _mainPanel.y + panelButton->yOffset; + + _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _mainPanel.sprites, spriteNumber, point, 256); + + drawVerbPanelText(backBuffer, panelButton, textColor, kKnownColorVerbTextShadow); +} + +void Interface::draw() { + Surface *backBuffer; + int i; + + Point leftPortraitPoint; + Point rightPortraitPoint; + Rect rect; + + backBuffer = _vm->_gfx->getBackBuffer(); + + if (_vm->_scene->isInIntro() || _fadeMode == kFadeOut) + return; + + drawStatusBar(); + + if (_panelMode == kPanelMain || _panelMode == kPanelMap) { + _mainPanel.getRect(rect); + backBuffer->blit(rect, _mainPanel.image); + + for (i = 0; i < kVerbTypeIdsMax; i++) { + if (_verbTypeToPanelButton[i] != NULL) { + drawVerbPanel(backBuffer, _verbTypeToPanelButton[i]); + } + } + } else if (_panelMode == kPanelConverse) { + _conversePanel.getRect(rect); + backBuffer->blit(rect, _conversePanel.image); + converseDisplayTextLines(backBuffer); + } + + if (_vm->getGameType() == GType_IHNM) { + if (_vm->_spiritualBarometer > 255) + _vm->_gfx->setPaletteColor(kIHNMColorPortrait, 0xff, 0xff, 0xff); + else + _vm->_gfx->setPaletteColor(kIHNMColorPortrait, + _vm->_spiritualBarometer * _portraitBgColor.red / 256, + _vm->_spiritualBarometer * _portraitBgColor.green / 256, + _vm->_spiritualBarometer * _portraitBgColor.blue / 256); + } + + if (_panelMode == kPanelMain || _panelMode == kPanelConverse || + _lockedMode == kPanelMain || _lockedMode == kPanelConverse) { + leftPortraitPoint.x = _mainPanel.x + _vm->getDisplayInfo().leftPortraitXOffset; + leftPortraitPoint.y = _mainPanel.y + _vm->getDisplayInfo().leftPortraitYOffset; + _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _defPortraits, _leftPortrait, leftPortraitPoint, 256); + } + + if (!_inMainMode && _vm->getDisplayInfo().rightPortraitXOffset >= 0) { //FIXME: should we change !_inMainMode to _panelMode == kPanelConverse ? + rightPortraitPoint.x = _mainPanel.x + _vm->getDisplayInfo().rightPortraitXOffset; + rightPortraitPoint.y = _mainPanel.y + _vm->getDisplayInfo().rightPortraitYOffset; + + // This looks like hack - particularly since it's only done for + // the right-side portrait - and perhaps it is! But as far as I + // can tell this is what the original engine does. And it keeps + // ITE from crashing when entering the Elk King's court. + + if (_rightPortrait >= _scenePortraits.spriteCount) + _rightPortrait = 0; + + _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _scenePortraits, _rightPortrait, rightPortraitPoint, 256); + } + + drawInventory(backBuffer); +} + +void Interface::calcOptionSaveSlider() { + int totalFiles = _vm->getSaveFilesCount(); + int visibleFiles = _vm->getDisplayInfo().optionSaveFileVisible; + if (_optionSaveFileSlider == NULL) return; //TODO:REMOVE + int height = _optionSaveFileSlider->height; + int sliderHeight; + int pos; + + if (totalFiles < visibleFiles) { + totalFiles = visibleFiles; + } + + sliderHeight = visibleFiles * height / totalFiles; + if (sliderHeight < 7) { + sliderHeight = 7; + } + + if (totalFiles - visibleFiles <= 0) { + pos = 0; + } else { + pos = _optionSaveFileTop * (height - sliderHeight) / (totalFiles - visibleFiles); + } + _optionPanel.calcPanelButtonRect(_optionSaveFileSlider, _optionSaveRectTop); + _optionSaveRectBottom = _optionSaveRectSlider = _optionSaveRectTop; + + _optionSaveRectTop.bottom = _optionSaveRectTop.top + pos; + _optionSaveRectTop.top++; + _optionSaveRectTop.right--; + + _optionSaveRectSlider.top = _optionSaveRectTop.bottom; + _optionSaveRectSlider.bottom = _optionSaveRectSlider.top + sliderHeight; + + _optionSaveRectBottom.top = _optionSaveRectSlider.bottom; + _optionSaveRectBottom.right--; +} + +void Interface::drawPanelText(Surface *ds, InterfacePanel *panel, PanelButton *panelButton) { + const char *text; + int textWidth; + Rect rect; + Point textPoint; + + // Button differs for CD version + if (panelButton->id == kTextReadingSpeed && _vm->getFeatures() & GF_CD_FX) + return; + if (panelButton->id == kTextShowDialog && !(_vm->getFeatures() & GF_CD_FX)) + return; + + text = _vm->getTextString(panelButton->id); + panel->calcPanelButtonRect(panelButton, rect); + if (panelButton->xOffset < 0) { + textWidth = _vm->_font->getStringWidth(kKnownFontMedium, text, 0, kFontNormal); + rect.left += 2 + (panel->imageWidth - 1 - textWidth) / 2; + } + + textPoint.x = rect.left; + textPoint.y = rect.top + 1; + + _vm->_font->textDraw(kKnownFontMedium, ds, text, textPoint, _vm->KnownColor2ColorId(kKnownColorVerbText), _vm->KnownColor2ColorId(kKnownColorVerbTextShadow), kFontShadow); +} + +void Interface::drawOption() { + const char *text; + Surface *backBuffer; + int i; + int fontHeight; + uint j, idx; + int fgColor; + int bgColor; + Rect rect; + Rect rect2; + PanelButton *panelButton; + Point textPoint; + if (_optionSaveFileSlider == NULL) return;//TODO:REMOVE + + backBuffer = _vm->_gfx->getBackBuffer(); + + _optionPanel.getRect(rect); + backBuffer->blit(rect, _optionPanel.image); + + for (i = 0; i < _optionPanel.buttonsCount; i++) { + panelButton = &_optionPanel.buttons[i]; + if (panelButton->type == kPanelButtonOption) { + drawPanelButtonText(backBuffer, &_optionPanel, panelButton); + } + if (panelButton->type == kPanelButtonOptionText) { + drawPanelText(backBuffer, &_optionPanel, panelButton); + } + } + + if (_optionSaveRectTop.height() > 0) { + backBuffer->drawRect(_optionSaveRectTop, kITEColorDarkGrey); + } + + drawButtonBox(backBuffer, _optionSaveRectSlider, kSlider, _optionSaveFileSlider->state > 0); + + if (_optionSaveRectBottom.height() > 0) { + backBuffer->drawRect(_optionSaveRectBottom, kITEColorDarkGrey); + } + + _optionPanel.calcPanelButtonRect(_optionSaveFilePanel, rect); + rect.top++; + rect2 = rect; + fontHeight = _vm->_font->getHeight(kKnownFontSmall); + for (j = 0; j < _vm->getDisplayInfo().optionSaveFileVisible; j++) { + bgColor = kITEColorDarkGrey0C; + fgColor = kITEColorBrightWhite; + + idx = j + _optionSaveFileTop; + if (idx == _optionSaveFileTitleNumber) { + SWAP(bgColor, fgColor); + } + if (idx < _vm->getSaveFilesCount()) { + rect2.top = rect.top + j * (fontHeight + 1); + rect2.bottom = rect2.top + fontHeight; + backBuffer->fillRect(rect2, bgColor); + text = _vm->getSaveFile(idx)->name; + textPoint.x = rect.left + 1; + textPoint.y = rect2.top; + _vm->_font->textDraw(kKnownFontSmall, backBuffer, text, textPoint, fgColor, 0, kFontNormal); + } + } + +} + +void Interface::drawQuit() { + Surface *backBuffer; + Rect rect; + int i; + PanelButton *panelButton; + + backBuffer = _vm->_gfx->getBackBuffer(); + + _quitPanel.getRect(rect); + drawButtonBox(backBuffer, rect, kButton, false); + for (i = 0; i < _quitPanel.buttonsCount; i++) { + panelButton = &_quitPanel.buttons[i]; + if (panelButton->type == kPanelButtonQuit) { + drawPanelButtonText(backBuffer, &_quitPanel, panelButton); + } + if (panelButton->type == kPanelButtonQuitText) { + drawPanelText(backBuffer, &_quitPanel, panelButton); + } + } +} + +void Interface::handleQuitUpdate(const Point& mousePoint) { + bool releasedButton; + + _quitPanel.currentButton = quitHitTest(mousePoint); + releasedButton = (_quitPanel.currentButton != NULL) && (_quitPanel.currentButton->state > 0) && (!_vm->mouseButtonPressed()); + + if (!_vm->mouseButtonPressed()) { + _quitPanel.zeroAllButtonState(); + } + + if (releasedButton) { + setQuit(_quitPanel.currentButton); + } +} + +void Interface::handleQuitClick(const Point& mousePoint) { + _quitPanel.currentButton = quitHitTest(mousePoint); + + _quitPanel.zeroAllButtonState(); + + if (_quitPanel.currentButton == NULL) { + return; + } + + _quitPanel.currentButton->state = 1; +} + +void Interface::setQuit(PanelButton *panelButton) { + _quitPanel.currentButton = NULL; + switch (panelButton->id) { + case kTextCancel: + setMode(kPanelOption); + break; + case kTextQuit: + _vm->shutDown(); + break; + } +} + +void Interface::drawLoad() { + Surface *backBuffer; + Rect rect; + int i; + PanelButton *panelButton; + + backBuffer = _vm->_gfx->getBackBuffer(); + + _loadPanel.getRect(rect); + drawButtonBox(backBuffer, rect, kButton, false); + for (i = 0; i < _loadPanel.buttonsCount; i++) { + panelButton = &_loadPanel.buttons[i]; + if (panelButton->type == kPanelButtonLoad) { + drawPanelButtonText(backBuffer, &_loadPanel, panelButton); + } + if (panelButton->type == kPanelButtonLoadText) { + drawPanelText(backBuffer, &_loadPanel, panelButton); + } + } +} + +void Interface::handleLoadUpdate(const Point& mousePoint) { + bool releasedButton; + + _loadPanel.currentButton = loadHitTest(mousePoint); + releasedButton = (_loadPanel.currentButton != NULL) && (_loadPanel.currentButton->state > 0) && (!_vm->mouseButtonPressed()); + + if (!_vm->mouseButtonPressed()) { + _loadPanel.zeroAllButtonState(); + } + + if (releasedButton) { + setLoad(_loadPanel.currentButton); + } +} + +void Interface::handleLoadClick(const Point& mousePoint) { + _loadPanel.currentButton = loadHitTest(mousePoint); + + _loadPanel.zeroAllButtonState(); + + if (_loadPanel.currentButton == NULL) { + return; + } + + _loadPanel.currentButton->state = 1; +} + +void Interface::setLoad(PanelButton *panelButton) { + _loadPanel.currentButton = NULL; + switch (panelButton->id) { + case kTextOK: + setMode(kPanelMain); + break; + } +} + +void Interface::processStatusTextInput(uint16 ascii) { + + textInputStartRepeat(ascii); + switch (ascii) { + case 27: // esc + _statusTextInputState = kStatusTextInputAborted; + _statusTextInput = false; + _vm->_script->wakeUpThreads(kWaitTypeStatusTextInput); + break; + case 13: // return + _statusTextInputState = kStatusTextInputEntered; + _statusTextInput = false; + _vm->_script->wakeUpThreads(kWaitTypeStatusTextInput); + break; + case 8: // backspace + if (_statusTextInputPos == 0) { + break; + } + _statusTextInputPos--; + _statusTextInputString[_statusTextInputPos] = 0; + default: + if (_statusTextInputPos >= STATUS_TEXT_INPUT_MAX) { + break; + } + if (((ascii >= 'a') && (ascii <='z')) || + ((ascii >= '0') && (ascii <='9')) || + ((ascii >= 'A') && (ascii <='Z')) || + (ascii == ' ')) { + _statusTextInputString[_statusTextInputPos++] = ascii; + _statusTextInputString[_statusTextInputPos] = 0; + } + } + setStatusText(_statusTextInputString); +} + +bool Interface::processTextInput(uint16 ascii) { + char ch[2]; + char tempString[SAVE_TITLE_SIZE]; + uint tempWidth; + memset(tempString, 0, SAVE_TITLE_SIZE); + ch[1] = 0; + + textInputStartRepeat(ascii); + + switch (ascii) { + case 13: + return false; + case 27: // esc + _textInput = false; + break; + case 8: // backspace + if (_textInputPos <= 1) { + break; + } + _textInputPos--; + case 127: // del + if (_textInputPos <= _textInputStringLength) { + if (_textInputPos != 1) { + strncpy(tempString, _textInputString, _textInputPos - 1); + } + if (_textInputPos != _textInputStringLength) { + strncat(tempString, &_textInputString[_textInputPos], _textInputStringLength - _textInputPos); + } + strcpy(_textInputString, tempString); + _textInputStringLength = strlen(_textInputString); + } + break; + case 276: // left + if (_textInputPos > 1) { + _textInputPos--; + } + break; + case 275: // right + if (_textInputPos <= _textInputStringLength) { + _textInputPos++; + } + break; + default: + if (((ascii >= 'a') && (ascii <='z')) || + ((ascii >= '0') && (ascii <='9')) || + ((ascii >= 'A') && (ascii <='Z')) || + (ascii == ' ')) { + if (_textInputStringLength < SAVE_TITLE_SIZE - 1) { + ch[0] = ascii; + tempWidth = _vm->_font->getStringWidth(kKnownFontSmall, ch, 0, kFontNormal); + tempWidth += _vm->_font->getStringWidth(kKnownFontSmall, _textInputString, 0, kFontNormal); + if (tempWidth > _textInputMaxWidth) { + break; + } + if (_textInputPos != 1) { + strncpy(tempString, _textInputString, _textInputPos - 1); + strcat(tempString, ch); + } + if ((_textInputStringLength == 0) || (_textInputPos == 1)) { + strcpy(tempString, ch); + } + if ((_textInputStringLength != 0) && (_textInputPos != _textInputStringLength)) { + strncat(tempString, &_textInputString[_textInputPos - 1], _textInputStringLength - _textInputPos + 1); + } + + strcpy(_textInputString, tempString); + _textInputStringLength = strlen(_textInputString); + _textInputPos++; + } + } + break; + } + return true; +} + +void Interface::drawTextInput(Surface *ds, InterfacePanel *panel, PanelButton *panelButton) { + Point textPoint; + Rect rect; + char ch[2]; + int fgColor; + uint i; + + ch[1] = 0; + panel->calcPanelButtonRect(panelButton, rect); + drawButtonBox(ds, rect, kEdit, _textInput); + rect.left += 4; + rect.top += 4; + rect.setHeight(_vm->_font->getHeight(kKnownFontSmall)); + + i = 0; + while ((ch[0] = _textInputString[i++]) != 0) { + rect.setWidth(_vm->_font->getStringWidth(kKnownFontSmall, ch, 0, kFontNormal)); + if ((i == _textInputPos) && _textInput) { + fgColor = kITEColorBlack; + ds->fillRect(rect, kITEColorWhite); + } else { + fgColor = kITEColorWhite; + } + textPoint.x = rect.left; + textPoint.y = rect.top + 1; + + _vm->_font->textDraw(kKnownFontSmall, ds, ch, textPoint, fgColor, 0, kFontNormal); + rect.left += rect.width(); + } + if (_textInput && (_textInputPos >= i)) { + ch[0] = ' '; + rect.setWidth(_vm->_font->getStringWidth(kKnownFontSmall, ch, 0, kFontNormal)); + ds->fillRect(rect, kITEColorWhite); + } +} + +void Interface::drawSave() { + Surface *backBuffer; + Rect rect; + int i; + PanelButton *panelButton; + + backBuffer = _vm->_gfx->getBackBuffer(); + + _savePanel.getRect(rect); + drawButtonBox(backBuffer, rect, kButton, false); + for (i = 0; i < _savePanel.buttonsCount; i++) { + panelButton = &_savePanel.buttons[i]; + if (panelButton->type == kPanelButtonSave) { + drawPanelButtonText(backBuffer, &_savePanel, panelButton); + } + if (panelButton->type == kPanelButtonSaveText) { + drawPanelText(backBuffer, &_savePanel, panelButton); + } + } + + drawTextInput(backBuffer, &_savePanel, _saveEdit); +} + +void Interface::drawProtect() { + Surface *backBuffer; + Rect rect; + int i; + PanelButton *panelButton; + + backBuffer = _vm->_gfx->getBackBuffer(); + + _protectPanel.getRect(rect); + drawButtonBox(backBuffer, rect, kButton, false); + + for (i = 0; i < _protectPanel.buttonsCount; i++) { + panelButton = &_protectPanel.buttons[i]; + if (panelButton->type == kPanelButtonProtectText) { + drawPanelText(backBuffer, &_protectPanel, panelButton); + } + } + drawTextInput(backBuffer, &_protectPanel, _protectEdit); +} + +void Interface::handleSaveUpdate(const Point& mousePoint) { + bool releasedButton; + + _savePanel.currentButton = saveHitTest(mousePoint); + + validateSaveButtons(); + + releasedButton = (_savePanel.currentButton != NULL) && + (_savePanel.currentButton->state > 0) && (!_vm->mouseButtonPressed()); + + if (!_vm->mouseButtonPressed()) { + _savePanel.zeroAllButtonState(); + } + + if (releasedButton) { + setSave(_savePanel.currentButton); + } +} + +void Interface::handleSaveClick(const Point& mousePoint) { + _savePanel.currentButton = saveHitTest(mousePoint); + + validateSaveButtons(); + + _savePanel.zeroAllButtonState(); + + if (_savePanel.currentButton == NULL) { + _textInput = false; + return; + } + + _savePanel.currentButton->state = 1; + if (_savePanel.currentButton == _saveEdit) { + _textInput = true; + } +} + +void Interface::setSave(PanelButton *panelButton) { + _savePanel.currentButton = NULL; + uint titleNumber; + char *fileName; + switch (panelButton->id) { + case kTextSave: + if (_textInputStringLength == 0 ) { + break; + } + if (!_vm->isSaveListFull() && (_optionSaveFileTitleNumber == 0)) { + if (_vm->locateSaveFile(_textInputString, titleNumber)) { + fileName = _vm->calcSaveFileName(_vm->getSaveFile(titleNumber)->slotNumber); + _vm->save(fileName, _textInputString); + _optionSaveFileTitleNumber = titleNumber; + } else { + fileName = _vm->calcSaveFileName(_vm->getNewSaveSlotNumber()); + _vm->save(fileName, _textInputString); + _vm->fillSaveList(); + calcOptionSaveSlider(); + } + } else { + fileName = _vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber); + _vm->save(fileName, _textInputString); + } + _textInput = false; + setMode(kPanelOption); + break; + case kTextCancel: + _textInput = false; + setMode(kPanelOption); + break; + } +} + +void Interface::handleOptionUpdate(const Point& mousePoint) { + int16 mouseY; + Rect rect; + int totalFiles = _vm->getSaveFilesCount(); + int visibleFiles = _vm->getDisplayInfo().optionSaveFileVisible; + bool releasedButton; + + if (_vm->mouseButtonPressed()) { + if (_optionSaveFileSlider != NULL) //TODO:REMOVE + if (_optionSaveFileSlider->state > 0) { + _optionPanel.calcPanelButtonRect(_optionSaveFileSlider, rect); + + mouseY = mousePoint.y - rect.top -_optionSaveFileMouseOff; + + if (totalFiles - visibleFiles <= 0) { + _optionSaveFileTop = 0; + } else { + _optionSaveFileTop = mouseY * (totalFiles - visibleFiles) / + (_optionSaveFileSlider->height - _optionSaveRectSlider.height()); + } + + _optionSaveFileTop = clamp(0, _optionSaveFileTop, _vm->getSaveFilesCount() - _vm->getDisplayInfo().optionSaveFileVisible); + calcOptionSaveSlider(); + } + } + + _optionPanel.currentButton = optionHitTest(mousePoint); + + validateOptionButtons(); + + releasedButton = (_optionPanel.currentButton != NULL) && (_optionPanel.currentButton->state > 0) && (!_vm->mouseButtonPressed()); + + if (!_vm->mouseButtonPressed()) { + _optionPanel.zeroAllButtonState(); + } + + if (releasedButton) { + setOption(_optionPanel.currentButton); + } +} + + +void Interface::handleOptionClick(const Point& mousePoint) { + Rect rect; + _optionPanel.currentButton = optionHitTest(mousePoint); + + validateOptionButtons(); + + _optionPanel.zeroAllButtonState(); + + if (_optionPanel.currentButton == NULL) { + return; + } + + if (_optionPanel.currentButton == _optionSaveFileSlider) { + if ((_optionSaveRectTop.height() > 0) && (mousePoint.y < _optionSaveRectTop.bottom)) { + _optionSaveFileTop -= _vm->getDisplayInfo().optionSaveFileVisible; + } else { + if ((_optionSaveRectBottom.height() > 0) && (mousePoint.y >= _optionSaveRectBottom.top)) { + _optionSaveFileTop += _vm->getDisplayInfo().optionSaveFileVisible; + } else { + if (_vm->getDisplayInfo().optionSaveFileVisible < _vm->getSaveFilesCount()) { + _optionSaveFileMouseOff = mousePoint.y - _optionSaveRectSlider.top; + _optionPanel.currentButton->state = 1; + } + } + } + + _optionSaveFileTop = clamp(0, _optionSaveFileTop, _vm->getSaveFilesCount() - _vm->getDisplayInfo().optionSaveFileVisible); + calcOptionSaveSlider(); + } else { + if (_optionPanel.currentButton == _optionSaveFilePanel) { + _optionPanel.calcPanelButtonRect(_optionSaveFilePanel, rect); + _optionSaveFileTitleNumber = (mousePoint.y - rect.top) / (_vm->_font->getHeight(kKnownFontSmall) + 1); + + if (_optionSaveFileTitleNumber >= _vm->getDisplayInfo().optionSaveFileVisible) { + _optionSaveFileTitleNumber = _vm->getDisplayInfo().optionSaveFileVisible - 1; + } + _optionSaveFileTitleNumber += _optionSaveFileTop; + if (_optionSaveFileTitleNumber >= _vm->getSaveFilesCount()) { + _optionSaveFileTitleNumber = _vm->getSaveFilesCount() - 1; + } + } else { + _optionPanel.currentButton->state = 1; + } + } +} + +void Interface::handleChapterSelectionUpdate(const Point& mousePoint) { + uint16 objectId; + + // FIXME: Original handled more object types here. + + objectId = _vm->_actor->hitTest(mousePoint, true); + + if (objectId != _vm->_script->_pointerObject) { + _vm->_script->_pointerObject = objectId; + } +} + +void Interface::handleChapterSelectionClick(const Point& mousePoint) { + int obj = _vm->_script->_pointerObject; + + _vm->_actor->abortSpeech(); + + if (obj) { + int script = 0; + HitZone *hitZone; + ActorData *a; + ObjectData *o; + Event event; + + switch (objectTypeId(obj)) { + case kGameObjectHitZone: + hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(obj)); + if (hitZone->getFlags() & kHitZoneExit) + script = hitZone->getScriptNumber(); + break; + + case kGameObjectActor: + a = _vm->_actor->getActor(obj); + script = a->_scriptEntrypointNumber; + break; + + case kGameObjectObject: + o = _vm->_actor->getObj(obj); + script = o->_scriptEntrypointNumber; + break; + } + + if (script > 0) { + event.type = kEvTOneshot; + event.code = kScriptEvent; + event.op = kEventExecNonBlocking; + event.time = 0; + event.param = _vm->_scene->getScriptModuleNumber(); + event.param2 = script; + event.param3 = _vm->_script->getVerbType(kVerbUse); // Action + event.param4 = obj; // Object + event.param5 = 0; // With Object + event.param6 = obj; // Actor + + _vm->_events->queue(&event); + } + } +} + +void Interface::setOption(PanelButton *panelButton) { + char * fileName; + _optionPanel.currentButton = NULL; + switch (panelButton->id) { + case kTextContinuePlaying: + ConfMan.flushToDisk(); + setMode(kPanelMain); + break; + case kTextQuitGame: + setMode(kPanelQuit); + break; + case kTextLoad: + if (_vm->getSaveFilesCount() > 0) { + if (_vm->isSaveListFull() || (_optionSaveFileTitleNumber > 0)) { + fileName = _vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber); + setMode(kPanelMain); + _vm->load(fileName); + } + } + break; + case kTextSave: + if (!_vm->isSaveListFull() && (_optionSaveFileTitleNumber == 0)) { + _textInputString[0] = 0; + } else { + strcpy(_textInputString, _vm->getSaveFile(_optionSaveFileTitleNumber)->name); + } + setMode(kPanelSave); + break; + case kTextReadingSpeed: + if (_vm->getFeatures() & GF_CD_FX) { + _vm->_subtitlesEnabled = !_vm->_subtitlesEnabled; + ConfMan.set("subtitles", _vm->_subtitlesEnabled); + } else { + _vm->_readingSpeed = (_vm->_readingSpeed + 1) % 4; + ConfMan.set("talkspeed", _vm->_readingSpeed); + } + break; + case kTextMusic: + _vm->_musicVolume = (_vm->_musicVolume + 1) % 11; + _vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1); + ConfMan.set("music_volume", _vm->_musicVolume * 25); + break; + case kTextSound: + _vm->_soundVolume = (_vm->_soundVolume + 1) % 11; + _vm->_sound->setVolume(_vm->_soundVolume == 10 ? 255 : _vm->_soundVolume * 25); + ConfMan.set("sfx_volume", _vm->_soundVolume * 25); + break; + } +} + +void Interface::update(const Point& mousePoint, int updateFlag) { + + if (!_active && _panelMode == kPanelNull && (updateFlag & UPDATE_MOUSECLICK)) + _vm->_actor->abortSpeech(); + + if (_vm->_scene->isInIntro() || _fadeMode == kFadeOut || !_active) { + return; + } + + if (_statusTextInput) { + return; + } + + switch (_panelMode) { + case kPanelMain: + if (updateFlag & UPDATE_MOUSEMOVE) { + bool lastWasPlayfield = _lastMousePoint.y < _vm->_scene->getHeight(); + if (mousePoint.y < _vm->_scene->getHeight()) { + if (!lastWasPlayfield) { + handleMainUpdate(mousePoint); + } + _vm->_script->whichObject(mousePoint); + } else { + if (lastWasPlayfield) { + _vm->_script->setNonPlayfieldVerb(); + } + handleMainUpdate(mousePoint); + } + + } else { + + if (updateFlag & UPDATE_MOUSECLICK) { + if (mousePoint.y < _vm->_scene->getHeight()) { + _vm->_script->playfieldClick(mousePoint, (updateFlag & UPDATE_LEFTBUTTONCLICK) != 0); + } else { + handleMainClick(mousePoint); + } + } + } + break; + + case kPanelConverse: + if (updateFlag & UPDATE_MOUSEMOVE) { + handleConverseUpdate(mousePoint); + } else { + if (updateFlag & UPDATE_MOUSECLICK) { + handleConverseClick(mousePoint); + } + if (updateFlag & UPDATE_WHEELUP) { + converseChangePos(-1); + } + if (updateFlag & UPDATE_WHEELDOWN) { + converseChangePos(1); + } + + if (_vm->_puzzle->isActive()) { + _vm->_puzzle->handleClick(mousePoint); + } + } + break; + + case kPanelOption: + if (updateFlag & UPDATE_MOUSEMOVE) { + handleOptionUpdate(mousePoint); + } else { + if (updateFlag & UPDATE_MOUSECLICK) { + handleOptionClick(mousePoint); + } + if (updateFlag & UPDATE_WHEELUP) { + if (_optionSaveFileTop) + _optionSaveFileTop--; + calcOptionSaveSlider(); + } + if (updateFlag & UPDATE_WHEELDOWN) { + if (_optionSaveFileTop < _vm->getSaveFilesCount() - _vm->getDisplayInfo().optionSaveFileVisible) + _optionSaveFileTop++; + calcOptionSaveSlider(); + } + } + break; + + case kPanelQuit: + if (updateFlag & UPDATE_MOUSEMOVE) { + handleQuitUpdate(mousePoint); + } else { + if (updateFlag & UPDATE_MOUSECLICK) { + handleQuitClick(mousePoint); + } + } + break; + + case kPanelLoad: + if (updateFlag & UPDATE_MOUSEMOVE) { + + handleLoadUpdate(mousePoint); + + } else { + if (updateFlag & UPDATE_MOUSECLICK) { + handleLoadClick(mousePoint); + } + } + break; + + case kPanelSave: + if (updateFlag & UPDATE_MOUSEMOVE) { + + handleSaveUpdate(mousePoint); + + } else { + if (updateFlag & UPDATE_MOUSECLICK) { + handleSaveClick(mousePoint); + } + } + break; + + case kPanelMap: + if (updateFlag & UPDATE_MOUSECLICK) + mapPanelClean(); + break; + + case kPanelSceneSubstitute: + if (updateFlag & UPDATE_MOUSECLICK) { + _vm->_render->clearFlag(RF_DEMO_SUBST); + _vm->_gfx->setPalette(_mapSavedPal); + setMode(kPanelMain); + _vm->_script->setNoPendingVerb(); + } + break; + + case kPanelChapterSelection: + // TODO: panel has silent button + if (updateFlag & UPDATE_MOUSEMOVE) { + handleChapterSelectionUpdate(mousePoint); + } else { + if (updateFlag & UPDATE_MOUSECLICK) + handleChapterSelectionClick(mousePoint); + } + break; + + case kPanelProtect: + // No mouse interaction + break; + + } + + _lastMousePoint = mousePoint; +} + +void Interface::drawStatusBar() { + Surface *backBuffer; + Rect rect; + Point textPoint; + int stringWidth; + int color; + + if (_panelMode == kPanelChapterSelection) + return; + + backBuffer = _vm->_gfx->getBackBuffer(); + + // Disable this for IHNM for now, since that game uses the full screen + // in some cases. + + // Erase background of status bar + rect.left = _vm->getDisplayInfo().statusXOffset; + rect.top = _vm->getDisplayInfo().statusYOffset; + rect.right = rect.left + _vm->getDisplayWidth(); + rect.bottom = rect.top + _vm->getDisplayInfo().statusHeight; + + backBuffer->drawRect(rect, _vm->getDisplayInfo().statusBGColor); + + stringWidth = _vm->_font->getStringWidth(kKnownFontSmall, _statusText, 0, kFontNormal); + + if (_statusOnceColor == -1) + color = _vm->getDisplayInfo().statusTextColor; + else + color = _statusOnceColor; + + textPoint.x = _vm->getDisplayInfo().statusXOffset + (_vm->getDisplayInfo().statusWidth - stringWidth) / 2; + textPoint.y = _vm->getDisplayInfo().statusYOffset + _vm->getDisplayInfo().statusTextY; + _vm->_font->textDraw(kKnownFontSmall, backBuffer, _statusText, textPoint, color, 0, kFontNormal); + + if (_saveReminderState > 0) { + rect.left = _vm->getDisplayInfo().saveReminderXOffset; + rect.top = _vm->getDisplayInfo().saveReminderYOffset; + + rect.right = rect.left + _vm->getDisplayInfo().saveReminderWidth; + rect.bottom = rect.top + _vm->getDisplayInfo().saveReminderHeight; + _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _vm->_sprite->_saveReminderSprites, + _saveReminderState == 1 ? _vm->getDisplayInfo().saveReminderFirstSpriteNumber : _vm->getDisplayInfo().saveReminderSecondSpriteNumber, + rect, 256); + + } +} + +void Interface::handleMainClick(const Point& mousePoint) { + + PanelButton *panelButton; + + panelButton = verbHitTest(mousePoint); + if (panelButton) { + _vm->_script->setVerb(panelButton->id); + return; + } + + panelButton = _mainPanel.hitTest(mousePoint, kPanelAllButtons); + + if (panelButton != NULL) { + if (panelButton->type == kPanelButtonArrow) { + panelButton->state = 1; + converseChangePos(panelButton->id); + } + + if (panelButton->type == kPanelButtonInventory) { + if (_vm->_script->_pointerObject != ID_NOTHING) { + _vm->_script->hitObject(_vm->leftMouseButtonPressed()); + } + if (_vm->_script->_pendingVerb) { + _vm->_actor->_protagonist->_currentAction = kActionWait; + _vm->_script->doVerb(); + } + } + } else { + if (_saveReminderState > 0) { + Rect rect; + rect.left = _vm->getDisplayInfo().saveReminderXOffset; + rect.top = _vm->getDisplayInfo().saveReminderYOffset; + + rect.right = rect.left + _vm->getDisplayInfo().saveReminderWidth; + rect.bottom = rect.top + _vm->getDisplayInfo().saveReminderHeight; + if (rect.contains(mousePoint)) { + setMode(kPanelOption); + } + } + } +} + +void Interface::handleMainUpdate(const Point& mousePoint) { + PanelButton *panelButton; + + panelButton = verbHitTest(mousePoint); + if (_mainPanel.currentButton != panelButton) { + if (_mainPanel.currentButton) { + if (_mainPanel.currentButton->type == kPanelButtonVerb) { + setVerbState(_mainPanel.currentButton->id, 0); + } + } + if (panelButton) { + setVerbState(panelButton->id, 1); + } + } + + if (panelButton) { + _mainPanel.currentButton = panelButton; + return; + } + + + if (!_vm->mouseButtonPressed()) { // remove pressed flag + if (_inventoryUpButton) { + _inventoryUpButton->state = 0; + _inventoryDownButton->state = 0; + } + } + + panelButton = _mainPanel.hitTest(mousePoint, kPanelAllButtons); + + bool changed = false; + + if ((panelButton != NULL) && (panelButton->type == kPanelButtonArrow)) { + if (panelButton->state == 1) { + //TODO: insert timeout catchup + inventoryChangePos(panelButton->id); + } + changed = true; + } else { + _vm->_script->whichObject(mousePoint); + } + + changed = changed || (panelButton != _mainPanel.currentButton); + _mainPanel.currentButton = panelButton; + if (changed) { + draw(); + } +} + +//inventory stuff +void Interface::inventoryChangePos(int chg) { + if ((chg < 0 && _inventoryStart + chg >= 0) || + (chg > 0 && _inventoryStart < _inventoryEnd)) { + _inventoryStart += chg; + draw(); + } +} + +void Interface::inventorySetPos(int key) { + _inventoryBox = key - '1'; + _inventoryPos = _inventoryStart + _inventoryBox; + if (_inventoryPos >= _inventoryCount) + _inventoryPos = -1; +} + +void Interface::updateInventory(int pos) { + int cols = _vm->getDisplayInfo().inventoryColumns; + if (pos >= _inventoryCount) { + pos = _inventoryCount - 1; + } + if (pos < 0) { + pos = 0; + } + _inventoryStart = (pos - cols) / cols * cols; + if (_inventoryStart < 0) { + _inventoryStart = 0; + } + + _inventoryEnd = (_inventoryCount - 1 - cols) / cols * cols; + if (_inventoryEnd < 0) { + _inventoryEnd = 0; + } +} + +void Interface::addToInventory(int objectId) { + if (_inventoryCount >= _inventorySize) { + return; + } + + for (int i = _inventoryCount; i > 0; i--) { + _inventory[i] = _inventory[i - 1]; + } + + _inventory[0] = objectId; + _inventoryCount++; + + _inventoryPos = 0; + updateInventory(0); + draw(); +} + +void Interface::removeFromInventory(int objectId) { + int j = inventoryItemPosition(objectId); + if (j == -1) { + return; + } + + int i; + + for (i = j; i < _inventoryCount - 1; i++) { + _inventory[i] = _inventory[i + 1]; + } + + --_inventoryCount; + _inventory[_inventoryCount] = 0; + updateInventory(j); + draw(); +} + +void Interface::clearInventory() { + for (int i = 0; i < _inventoryCount; i++) + _inventory[i] = 0; + + _inventoryCount = 0; + updateInventory(0); +} + +int Interface::inventoryItemPosition(int objectId) { + for (int i = 0; i < _inventoryCount; i++) + if (_inventory[i] == objectId) + return i; + + return -1; +} + +void Interface::drawInventory(Surface *backBuffer) { + if (!isInMainMode()) + return; + + int i; + Rect rect; + int ci; + ObjectData *obj; + ci = _inventoryStart; + if (_inventoryStart != 0) { + drawPanelButtonArrow(backBuffer, &_mainPanel, _inventoryUpButton); + } + if (_inventoryStart != _inventoryEnd) { + drawPanelButtonArrow(backBuffer, &_mainPanel, _inventoryDownButton); + } + + for (i = 0; i < _mainPanel.buttonsCount; i++) { + if (_mainPanel.buttons[i].type != kPanelButtonInventory) { + continue; + } + _mainPanel.calcPanelButtonRect(&_mainPanel.buttons[i], rect); + + // TODO: Different colour for IHNM, probably. + backBuffer->drawRect(rect, kITEColorDarkGrey); + + if (ci < _inventoryCount) { + obj = _vm->_actor->getObj(_inventory[ci]); + _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _vm->_sprite->_inventorySprites, obj->_spriteListResourceId, rect, 256); + } + + ci++; + } +} + +void Interface::setVerbState(int verb, int state) { + PanelButton * panelButton = getPanelButtonByVerbType(verb); + if (panelButton == NULL) return; + if (state == 2) { + state = (_mainPanel.currentButton == panelButton) ? 1 : 0; + } + panelButton->state = state; + draw(); +} + +void Interface::drawButtonBox(Surface *ds, const Rect& rect, ButtonKind kind, bool down) { + byte cornerColor; + byte frameColor; + byte fillColor; + byte solidColor; + byte odl, our, idl, iur; + + switch (kind ) { + case kSlider: + cornerColor = 0x8b; + frameColor = kITEColorBlack; + fillColor = kITEColorLightBlue96; + odl = kITEColorDarkBlue8a; + our = kITEColorLightBlue92; + idl = 0x89; + iur = 0x94; + solidColor = down ? kITEColorLightBlue94 : kITEColorLightBlue96; + break; + case kEdit: + cornerColor = kITEColorLightBlue96; + frameColor = kITEColorLightBlue96; + fillColor = kITEColorLightBlue96; + our = kITEColorDarkBlue8a; + odl = kITEColorLightBlue94; + iur = 0x97; + idl = 0x95; + if (down) { + solidColor = kITEColorBlue; + } else { + solidColor = kITEColorDarkGrey0C; + } + break; + default: + cornerColor = 0x8b; + frameColor = kITEColorBlack; + solidColor = fillColor = kITEColorLightBlue96; + odl = kITEColorDarkBlue8a; + our = kITEColorLightBlue94; + idl = 0x97; + iur = 0x95; + if (down) { + SWAP(odl, our); + SWAP(idl, iur); + } + break; + } + + int x = rect.left; + int y = rect.top; + int w = rect.width(); + int h = rect.height(); + int xe = rect.right - 1; + int ye = rect.bottom - 1; + + ((byte *)ds->getBasePtr(x, y))[0] = cornerColor; + ((byte *)ds->getBasePtr(x, ye))[0] = cornerColor; + ((byte *)ds->getBasePtr(xe, y))[0] = cornerColor; + ((byte *)ds->getBasePtr(xe, ye))[0] = cornerColor; + ds->hLine(x + 1, y, x + 1 + w - 2, frameColor); + ds->hLine(x + 1, ye, x + 1 + w - 2, frameColor); + ds->vLine(x, y + 1, y + 1 + h - 2, frameColor); + ds->vLine(xe, y + 1, y + 1 + h - 2, frameColor); + + x++; + y++; + xe--; + ye--; + w -= 2; + h -= 2; + ds->vLine(x, y, y + h - 1, odl); + ds->hLine(x, ye, x + w - 1, odl); + ds->vLine(xe, y, y + h - 1, our); + ds->hLine(x + 1, y, x + 1 + w - 2, our); + + x++; + y++; + xe--; + ye--; + w -= 2; + h -= 2; + ((byte *)ds->getBasePtr(x, y))[0] = fillColor; + ((byte *)ds->getBasePtr(xe, ye))[0] = fillColor; + ds->vLine(x, y + 1, y + 1 + h - 2, idl); + ds->hLine(x + 1, ye, x + 1 + w - 2, idl); + ds->vLine(xe, y, y + h - 1, iur); + ds->hLine(x + 1, y, x + 1 + w - 2, iur); + + x++; y++; + w -= 2; h -= 2; + + Common::Rect fill(x, y, x + w, y + h); + ds->fillRect(fill, solidColor); +} + +static const int readingSpeeds[] = { kTextFast, kTextMid, kTextSlow, kTextClick }; + +void Interface::drawPanelButtonText(Surface *ds, InterfacePanel *panel, PanelButton *panelButton) { + const char *text; + int textId; + int textWidth; + int textHeight; + Point point; + KnownColor textColor; + Rect rect; + + textId = panelButton->id; + switch(panelButton->id) { + case kTextReadingSpeed: + if (_vm->getFeatures() & GF_CD_FX) { + if (_vm->_subtitlesEnabled) + textId = kTextOn; + else + textId = kTextOff; + } else { + textId = readingSpeeds[_vm->_readingSpeed]; + } + break; + case kTextMusic: + if (_vm->_musicVolume) + textId = kText10Percent + _vm->_musicVolume - 1; + else + textId = kTextOff; + break; + case kTextSound: + if (_vm->_soundVolume) + textId = kText10Percent + _vm->_soundVolume - 1; + else + textId = kTextOff; + break; + } + text = _vm->getTextString(textId); + + textWidth = _vm->_font->getStringWidth(kKnownFontMedium, text, 0, kFontNormal); + textHeight = _vm->_font->getHeight(kKnownFontMedium); + + point.x = panel->x + panelButton->xOffset + (panelButton->width / 2) - (textWidth / 2); + point.y = panel->y + panelButton->yOffset + (panelButton->height / 2) - (textHeight / 2); + + if (panelButton == panel->currentButton) { + textColor = kKnownColorVerbTextActive; + } else { + textColor = kKnownColorVerbText; + } + + panel->calcPanelButtonRect(panelButton, rect); + drawButtonBox(ds, rect, kButton, panelButton->state > 0); + + _vm->_font->textDraw(kKnownFontMedium, ds, text, point, + _vm->KnownColor2ColorId(textColor), _vm->KnownColor2ColorId(kKnownColorVerbTextShadow), kFontShadow); +} + +void Interface::drawPanelButtonArrow(Surface *ds, InterfacePanel *panel, PanelButton *panelButton) { + Point point; + int spriteNumber; + + if (panel->currentButton == panelButton) { + if (panelButton->state != 0) { + spriteNumber = panelButton->downSpriteNumber; + } else { + spriteNumber = panelButton->overSpriteNumber; + } + } else { + spriteNumber = panelButton->upSpriteNumber; + } + + point.x = panel->x + panelButton->xOffset; + point.y = panel->y + panelButton->yOffset; + + _vm->_sprite->draw(ds, _vm->getDisplayClip(), _vm->_sprite->_mainSprites, spriteNumber, point, 256); +} + +void Interface::drawVerbPanelText(Surface *ds, PanelButton *panelButton, KnownColor textKnownColor, KnownColor textShadowKnownColor) { + const char *text; + int textWidth; + Point point; + int textId; + + if (_vm->getGameType() == GType_ITE) { + textId = verbTypeToTextStringsIdLUT[0][panelButton->id]; + text = _vm->getTextString(textId); + } else { + textId = verbTypeToTextStringsIdLUT[1][panelButton->id]; + text = _vm->_script->_mainStrings.getString(textId + 1); + textShadowKnownColor = kKnownColorTransparent; + } + + textWidth = _vm->_font->getStringWidth(kKnownFontVerb, text, 0, kFontNormal); + + if (_vm->getGameType() == GType_ITE) { + point.x = _mainPanel.x + panelButton->xOffset + 1 + (panelButton->width - 1 - textWidth) / 2; + point.y = _mainPanel.y + panelButton->yOffset + 1; + } else { + point.x = _mainPanel.x + panelButton->xOffset + 1 + (panelButton->width - textWidth) / 2; + point.y = _mainPanel.y + panelButton->yOffset + 12; + } + + _vm->_font->textDraw(kKnownFontVerb, ds, text, point, _vm->KnownColor2ColorId(textKnownColor),_vm->KnownColor2ColorId(textShadowKnownColor), (textShadowKnownColor != kKnownColorTransparent) ? kFontShadow : kFontNormal); +} + + +// Converse stuff +void Interface::converseInit(void) { + for (int i = 0; i < CONVERSE_MAX_TEXTS; i++) + _converseText[i].text = NULL; + converseClear(); +} + +void Interface::converseClear(void) { + for (int i = 0; i < CONVERSE_MAX_TEXTS; i++) { + if (_converseText[i].text != NULL) { + free(_converseText[i].text); + _converseText[i].text = NULL; + } + _converseText[i].stringNum = -1; + _converseText[i].replyId = 0; + _converseText[i].replyFlags = 0; + _converseText[i].replyBit = 0; + } + + _converseTextCount = 0; + _converseStrCount = 0; + _converseStartPos = 0; + _converseEndPos = 0; + _conversePos = -1; +} + +bool Interface::converseAddText(const char *text, int replyId, byte replyFlags, int replyBit) { + int count = 0; // count how many pieces of text per string + int i; + int len; + byte c; + + assert(strlen(text) < CONVERSE_MAX_WORK_STRING); + + strncpy(_converseWorkString, text, CONVERSE_MAX_WORK_STRING); + + while (1) { + len = strlen(_converseWorkString); + + for (i = len; i >= 0; i--) { + c = _converseWorkString[i]; + + if ((c == ' ' || c == '\0') && (_vm->_font->getStringWidth(kKnownFontSmall, _converseWorkString, i, kFontNormal) <= _vm->getDisplayInfo().converseMaxTextWidth)) { + break; + } + } + if (i < 0) { + return true; + } + + if (_converseTextCount == CONVERSE_MAX_TEXTS) { + return true; + } + + _converseText[_converseTextCount].text = (char *)malloc(i + 1); + strncpy(_converseText[_converseTextCount].text, _converseWorkString, i); + + _converseText[_converseTextCount].text[i] = 0; + _converseText[_converseTextCount].textNum = count; + _converseText[_converseTextCount].stringNum = _converseStrCount; + _converseText[_converseTextCount].replyId = replyId; + _converseText[_converseTextCount].replyFlags = replyFlags; + _converseText[_converseTextCount].replyBit = replyBit; + + _converseTextCount++; + count++; + + if (len == i) + break; + + strncpy(_converseWorkString, &_converseWorkString[i + 1], len - i); + } + + _converseStrCount++; + + return false; +} + +void Interface::converseDisplayText() { + int end; + + _converseStartPos = 0; + + end = _converseTextCount - _vm->getDisplayInfo().converseTextLines; + + if (end < 0) + end = 0; + + _converseEndPos = end; + draw(); +} + + +void Interface::converseSetTextLines(int row) { + int pos = row + _converseStartPos; + if (pos >= _converseTextCount) + pos = -1; + if (pos != _conversePos) { + _conversePos = pos; + draw(); + } +} + +void Interface::converseDisplayTextLines(Surface *ds) { + int relPos; + byte foregnd; + byte backgnd; + byte bulletForegnd; + byte bulletBackgnd; + const char *str; + char bullet[2] = { + (char)0xb7, 0 + }; + Rect rect(8, _vm->getDisplayInfo().converseTextLines * _vm->getDisplayInfo().converseTextHeight); + Point textPoint; + + assert(_conversePanel.buttonsCount >= 6); + + bulletForegnd = kITEColorGreen; + bulletBackgnd = kITEColorBlack; + + rect.moveTo(_conversePanel.x + _conversePanel.buttons[0].xOffset, + _conversePanel.y + _conversePanel.buttons[0].yOffset); + + ds->drawRect(rect, kITEColorDarkGrey); //fill bullet place + + for (int i = 0; i < _vm->getDisplayInfo().converseTextLines; i++) { + relPos = _converseStartPos + i; + + if (_converseTextCount <= relPos) { + break; + } + + if (_conversePos >= 0 && _converseText[_conversePos].stringNum == _converseText[relPos].stringNum) { + foregnd = kITEColorBrightWhite; + backgnd = (!_vm->leftMouseButtonPressed()) ? kITEColorDarkGrey : kITEColorGrey; + } else { + foregnd = kITEColorBlue; + backgnd = kITEColorDarkGrey; + } + + _conversePanel.calcPanelButtonRect(&_conversePanel.buttons[i], rect); + rect.left += 8; + ds->drawRect(rect, backgnd); + + str = _converseText[relPos].text; + + if (_converseText[relPos].textNum == 0) { // first entry + textPoint.x = rect.left - 6; + textPoint.y = rect.top; + + _vm->_font->textDraw(kKnownFontSmall, ds, bullet, textPoint, bulletForegnd, bulletBackgnd, (FontEffectFlags)(kFontShadow | kFontDontmap)); + } + textPoint.x = rect.left + 1; + textPoint.y = rect.top; + _vm->_font->textDraw(kKnownFontSmall, ds, str, textPoint, foregnd, kITEColorBlack, kFontShadow); + } + + if (_converseStartPos != 0) { + drawPanelButtonArrow(ds, &_conversePanel, _converseUpButton); + } + + if (_converseStartPos != _converseEndPos) { + drawPanelButtonArrow(ds, &_conversePanel, _converseDownButton); + } +} + +void Interface::converseChangePos(int chg) { + if ((chg < 0 && _converseStartPos + chg >= 0) || + (chg > 0 && _converseStartPos < _converseEndPos)) { + _converseStartPos += chg; + draw(); + } +} + +void Interface::converseSetPos(int key) { + Converse *ct; + int selection = key - '1'; + + if (selection >= _converseTextCount) + return; + + converseSetTextLines(selection); + + ct = &_converseText[_conversePos]; + + _vm->_script->finishDialog(ct->replyId, ct->replyFlags, ct->replyBit); + + if (_vm->_puzzle->isActive()) + _vm->_puzzle->handleReply(ct->replyId); + + _conversePos = -1; +} + + +void Interface::handleConverseUpdate(const Point& mousePoint) { + bool changed; + + PanelButton *last = _conversePanel.currentButton; + + if (!_vm->mouseButtonPressed()) { // remove pressed flag + if (_converseUpButton) { + _converseUpButton->state = 0; + _converseDownButton->state = 0; + } + } + + _conversePanel.currentButton = converseHitTest(mousePoint); + changed = last != _conversePanel.currentButton; + + + if (_conversePanel.currentButton == NULL) { + _conversePos = -1; + if (changed) { + draw(); + } + return; + } + + if (_conversePanel.currentButton->type == kPanelButtonConverseText) { + converseSetTextLines(_conversePanel.currentButton->id); + } + + if (_conversePanel.currentButton->type == kPanelButtonArrow) { + if (_conversePanel.currentButton->state == 1) { + //TODO: insert timeout catchup + converseChangePos(_conversePanel.currentButton->id); + } + draw(); + } +} + + +void Interface::handleConverseClick(const Point& mousePoint) { + _conversePanel.currentButton = converseHitTest(mousePoint); + + if (_conversePanel.currentButton == NULL) { + return; + } + + if (_conversePanel.currentButton->type == kPanelButtonConverseText) { + converseSetPos(_conversePanel.currentButton->ascii); + } + + if (_conversePanel.currentButton->type == kPanelButtonArrow) { + _conversePanel.currentButton->state = 1; + converseChangePos(_conversePanel.currentButton->id); + } + +} + +void Interface::saveState(Common::OutSaveFile *out) { + out->writeUint16LE(_inventoryCount); + + for (int i = 0; i < _inventoryCount; i++) { + out->writeUint16LE(_inventory[i]); + } +} + +void Interface::loadState(Common::InSaveFile *in) { + _inventoryCount = in->readUint16LE(); + + for (int i = 0; i < _inventoryCount; i++) { + _inventory[i] = in->readUint16LE(); + } + + updateInventory(0); +} + +void Interface::mapPanelShow() { + int i; + byte *resource; + size_t resourceLength, imageLength; + Surface *backBuffer; + Rect rect; + byte *image; + int imageWidth, imageHeight; + const byte *pal; + PalEntry cPal[PAL_ENTRIES]; + + _vm->_gfx->showCursor(false); + + backBuffer = _vm->_gfx->getBackBuffer(); + + rect.left = rect.top = 0; + + _vm->_resource->loadResource(_interfaceContext, + _vm->_resource->convertResourceId(RID_ITE_TYCHO_MAP), resource, resourceLength); + if (resourceLength == 0) { + error("Interface::mapPanelShow() unable to load Tycho map resource"); + } + + _vm->_gfx->getCurrentPal(_mapSavedPal); + + for (i = 0; i < 6 ; i++) { + _vm->_gfx->palToBlack(_mapSavedPal, 0.2 * i); + _vm->_render->drawScene(); + _vm->_system->delayMillis(5); + } + + _vm->_render->setFlag(RF_MAP); + + _vm->decodeBGImage(resource, resourceLength, &image, &imageLength, &imageWidth, &imageHeight); + pal = _vm->getImagePal(resource, resourceLength); + + for (i = 0; i < PAL_ENTRIES; i++) { + cPal[i].red = *pal++; + cPal[i].green = *pal++; + cPal[i].blue = *pal++; + } + + rect.setWidth(imageWidth); + rect.setHeight(imageHeight); + + backBuffer->blit(rect, image); + + // Evil Evil + for (i = 0; i < 6 ; i++) { + _vm->_gfx->blackToPal(cPal, 0.2 * i); + _vm->_render->drawScene(); + _vm->_system->delayMillis(5); + } + + free(resource); + free(image); + + setSaveReminderState(false); + + _mapPanelCrossHairState = true; +} + +void Interface::mapPanelClean() { + PalEntry pal[PAL_ENTRIES]; + int i; + + _vm->_gfx->getCurrentPal(pal); + + for (i = 0; i < 6 ; i++) { + _vm->_gfx->palToBlack(pal, 0.2 * i); + _vm->_render->drawScene(); + _vm->_system->delayMillis(5); + } + + _vm->_render->clearFlag(RF_MAP); + setMode(kPanelMain); + + _vm->_gfx->showCursor(true); + _vm->_render->drawScene(); + + for (i = 0; i < 6 ; i++) { + _vm->_gfx->blackToPal(_mapSavedPal, 0.2 * i); + _vm->_render->drawScene(); + _vm->_system->delayMillis(5); + } +} + +void Interface::mapPanelDrawCrossHair() { + Surface *backBuffer; + + backBuffer = _vm->_gfx->getBackBuffer(); + _mapPanelCrossHairState = !_mapPanelCrossHairState; + + Point mapPosition = _vm->_isoMap->getMapPosition(); + Rect screen(_vm->getDisplayWidth(), _vm->_scene->getHeight()); + + if (screen.contains(mapPosition)) { + _vm->_sprite->draw(backBuffer, _vm->getDisplayClip(), _vm->_sprite->_mainSprites, + _mapPanelCrossHairState? RID_ITE_SPR_XHAIR1 : RID_ITE_SPR_XHAIR2, + mapPosition, 256); + } +} + +void Interface::keyBoss() { + if (_vm->getGameType() != GType_IHNM) + return; + + if (_bossMode != -1 || _fadeMode != kNoFade) + return; + + _vm->_sound->pauseVoice(); + _vm->_sound->pauseSound(); + _vm->_music->pause(); + + int i; + byte *resource; + size_t resourceLength, imageLength; + Surface *backBuffer; + Rect rect; + byte *image; + int imageWidth, imageHeight; + const byte *pal; + PalEntry cPal[PAL_ENTRIES]; + + _vm->_gfx->showCursor(false); + + backBuffer = _vm->_gfx->getBackBuffer(); + + rect.left = rect.top = 0; + + _vm->_resource->loadResource(_interfaceContext, RID_IHNM_BOSS_SCREEN, resource, resourceLength); + if (resourceLength == 0) { + error("Interface::bossKey() unable to load Boss image resource"); + } + + _bossMode = _panelMode; + setMode(kPanelBoss); + + _vm->decodeBGImage(resource, resourceLength, &image, &imageLength, &imageWidth, &imageHeight); + rect.setWidth(imageWidth); + rect.setHeight(imageHeight); + + _vm->_gfx->getCurrentPal(_mapSavedPal); + pal = _vm->getImagePal(resource, resourceLength); + + for (i = 0; i < PAL_ENTRIES; i++) { + cPal[i].red = *pal++; + cPal[i].green = *pal++; + cPal[i].blue = *pal++; + } + + backBuffer->blit(rect, image); + + _vm->_gfx->setPalette(cPal); + + free(resource); + free(image); +} + + +void Interface::keyBossExit() { + PalEntry pal[PAL_ENTRIES]; + + _vm->_sound->resumeVoice(); + _vm->_sound->resumeSound(); + _vm->_music->resume(); + + _vm->_gfx->getCurrentPal(pal); + + _vm->_gfx->palToBlack(pal, 1); + setMode(_bossMode); + + _vm->_render->drawScene(); + + _vm->_gfx->blackToPal(_mapSavedPal, 1); + + _vm->_gfx->showCursor(true); + + _bossMode = -1; +} + + +} // End of namespace Saga diff --git a/engines/saga/interface.h b/engines/saga/interface.h new file mode 100644 index 0000000000..bee3fd4a2a --- /dev/null +++ b/engines/saga/interface.h @@ -0,0 +1,466 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Game interface module private header file + +#ifndef SAGA_INTERFACE_H__ +#define SAGA_INTERFACE_H__ + +#include "common/savefile.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 128 + +#define ITE_INVENTORY_SIZE 24 + +#define VERB_STRLIMIT 32 + +#define STATUS_TEXT_LEN 128 +#define STATUS_TEXT_INPUT_MAX 256 + +// 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; + byte *image; + size_t imageLength; + int imageWidth; + int imageHeight; + + PanelButton *currentButton; + int buttonsCount; + PanelButton *buttons; + SpriteList sprites; + + InterfacePanel() { + x = y = 0; + image = NULL; + imageLength = 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 { + char *text; + int stringNum; + int textNum; + int replyId; + int replyFlags; + int replyBit; +}; + + +enum StatusTextInputState { + kStatusTextInputFirstRun, + kStatusTextInputEntered, + kStatusTextInputAborted +}; + +class Interface { +public: + Interface(SagaEngine *vm); + ~Interface(void); + + 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(void) const { return _panelMode; } + void setFadeMode(int fadeMode) { + _fadeMode = fadeMode; + draw(); + } + int getFadeMode() const { + return _fadeMode; + } + void rememberMode(); + void restoreMode(); + 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(uint16 ascii, bool synthetic = false); + void processKeyUp(uint16 ascii); + + void keyBoss(); + void keyBossExit(); + + void disableAbortSpeeches(bool d) { _disableAbortSpeeches = d; } + + bool _textInput; + + bool _statusTextInput; + StatusTextInputState _statusTextInputState; + char _statusTextInputString[STATUS_TEXT_INPUT_MAX]; + void enterStatusString() { + _statusTextInput = true; + _statusTextInputPos = 0; + _statusTextInputString[0] = 0; + setStatusText(_statusTextInputString); + } + +private: + static void textInputRepeatCallback(void *refCon); + + void drawInventory(Surface *backBuffer); + 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; } + +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(Surface *ds, InterfacePanel *panel, PanelButton *panelButton); + void drawPanelText(Surface *ds, InterfacePanel *panel, PanelButton *panelButton); + void drawPanelButtonText(Surface *ds, InterfacePanel *panel, PanelButton *panelButton); + enum ButtonKind { + kButton, + kSlider, + kEdit + }; + void drawButtonBox(Surface *ds, const Rect &rect, ButtonKind kind, bool down); + void drawPanelButtonArrow(Surface *ds, InterfacePanel *panel, PanelButton *panelButton); + void drawVerbPanelText(Surface *ds, PanelButton *panelButton, KnownColor textKnownColor, KnownColor textShadowKnownColor); + void drawVerbPanel(Surface *backBuffer, PanelButton* panelButton); + void calcOptionSaveSlider(); + bool processTextInput(uint16 ascii); + void processStatusTextInput(uint16 ascii); + void textInputStartRepeat(uint16 ascii); + void textInputRepeat(void); + +public: + void converseInit(void); + void converseClear(void); + bool converseAddText(const char *text, int replyId, byte replyFlags, int replyBit); + void converseDisplayText(); + void converseSetTextLines(int row); + void converseChangePos(int chg); + void converseSetPos(int key); + +private: + void converseDisplayTextLines(Surface *ds); + 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; + +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; + PalEntry _portraitBgColor; + + Point _lastMousePoint; + + uint16 *_inventory; + int _inventorySize; + 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; + + int _textInputRepeatPhase; + uint16 _textInputRepeatChar; + + PalEntry _mapSavedPal[PAL_ENTRIES]; + bool _mapPanelCrossHairState; + + int32 _protectHash; +}; + +} // End of namespace Saga + +#endif /* INTERFACE_H__ */ diff --git a/engines/saga/isomap.cpp b/engines/saga/isomap.cpp new file mode 100644 index 0000000000..6acbf8ba70 --- /dev/null +++ b/engines/saga/isomap.cpp @@ -0,0 +1,1694 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Isometric level module +#include "saga/saga.h" + +#include "saga/gfx.h" + +#include "saga/resnames.h" +#include "saga/scene.h" +#include "saga/isomap.h" +#include "saga/stream.h" + +namespace Saga { + +enum MaskRules { + kMaskRuleNever = 0, + kMaskRuleAlways, + kMaskRuleUMIN, + kMaskRuleUMID, + kMaskRuleUMAX, + kMaskRuleVMIN, + kMaskRuleVMID, + kMaskRuleVMAX, + kMaskRuleYMIN, + kMaskRuleYMID, + kMaskRuleYMAX, + kMaskRuleUVMAX, + kMaskRuleUVMIN, + kMaskRuleUorV, + kMaskRuleUandV +}; + + +static const IsoMap::TilePoint normalDirTable[8] = { + { 1, 1, 0, SAGA_DIAG_NORMAL_COST}, + { 1, 0, 0, SAGA_STRAIGHT_NORMAL_COST}, + { 1,-1, 0, SAGA_DIAG_NORMAL_COST}, + { 0,-1, 0, SAGA_STRAIGHT_NORMAL_COST}, + {-1,-1, 0, SAGA_DIAG_NORMAL_COST}, + {-1, 0, 0, SAGA_STRAIGHT_NORMAL_COST}, + {-1, 1, 0, SAGA_DIAG_NORMAL_COST}, + { 0, 1, 0, SAGA_STRAIGHT_NORMAL_COST}, +}; + +static const IsoMap::TilePoint easyDirTable[8] = { + { 1, 1, 0, SAGA_DIAG_EASY_COST}, + { 1, 0, 0, SAGA_STRAIGHT_EASY_COST}, + { 1,-1, 0, SAGA_DIAG_EASY_COST}, + { 0,-1, 0, SAGA_STRAIGHT_EASY_COST}, + {-1,-1, 0, SAGA_DIAG_EASY_COST}, + {-1, 0, 0, SAGA_STRAIGHT_EASY_COST}, + {-1, 1, 0, SAGA_DIAG_EASY_COST}, + { 0, 1, 0, SAGA_STRAIGHT_EASY_COST}, +}; + +static const IsoMap::TilePoint hardDirTable[8] = { + { 1, 1, 0, SAGA_DIAG_HARD_COST}, + { 1, 0, 0, SAGA_STRAIGHT_HARD_COST}, + { 1,-1, 0, SAGA_DIAG_HARD_COST}, + { 0,-1, 0, SAGA_STRAIGHT_HARD_COST}, + {-1,-1, 0, SAGA_DIAG_HARD_COST}, + {-1, 0, 0, SAGA_STRAIGHT_HARD_COST}, + {-1, 1, 0, SAGA_DIAG_HARD_COST}, + { 0, 1, 0, SAGA_STRAIGHT_HARD_COST}, +}; + +IsoMap::IsoMap(SagaEngine *vm) : _vm(vm) { + _tileData = NULL; + _tilesCount = 0; + _tilePlatformList = NULL; + _tilePlatformsCount = 0; + _metaTileList = NULL; + _metaTilesCount = 0; + _multiTable = NULL; + _multiCount = 0; + _multiTableData = NULL; + _multiDataCount = 0; + _viewScroll.x = (128 - 8) * 16; + _viewScroll.x = (128 - 8) * 16 - 64; + _viewDiff = 1; + +} + +void IsoMap::loadImages(const byte *resourcePointer, size_t resourceLength) { + IsoTileData *tileData; + uint16 i; + + if (resourceLength == 0) { + error("IsoMap::loadImages wrong resourceLength"); + } + + _tileData = (byte*)malloc(resourceLength); + _tileDataLength = resourceLength; + memcpy(_tileData, resourcePointer, resourceLength); + + MemoryReadStreamEndian readS(_tileData, _tileDataLength, _vm->isBigEndian()); + readS.readUint16(); // skip + _tilesCount = readS.readUint16(); + _tilesCount = _tilesCount / SAGA_ISOTILEDATA_LEN; + + readS.seek(0); + + _tilesTable = (IsoTileData *)malloc(_tilesCount * sizeof(*_tilesTable)); + if (_tilesTable == NULL) { + memoryError("IsoMap::loadImages"); + } + + for (i = 0; i < _tilesCount; i++) { + tileData = &_tilesTable[i]; + tileData->height = readS.readByte(); + tileData->attributes = readS.readSByte(); + tileData->offset = readS.readUint16(); + tileData->terrainMask = readS.readUint16(); + tileData->FGDBGDAttr = readS.readByte(); + readS.readByte(); //skip + } + +} + +void IsoMap::loadPlatforms(const byte * resourcePointer, size_t resourceLength) { + TilePlatformData *tilePlatformData; + uint16 i, x, y; + + if (resourceLength == 0) { + error("IsoMap::loadPlatforms wrong resourceLength"); + } + + MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian()); + + _tilePlatformsCount = resourceLength / SAGA_TILEPLATFORMDATA_LEN; + _tilePlatformList = (TilePlatformData *)malloc(_tilePlatformsCount * sizeof(*_tilePlatformList)); + if (_tilePlatformList == NULL) { + memoryError("IsoMap::loadPlatforms"); + } + + for (i = 0; i < _tilePlatformsCount; i++) { + tilePlatformData = &_tilePlatformList[i]; + tilePlatformData->metaTile = readS.readSint16(); + tilePlatformData->height = readS.readSint16(); + tilePlatformData->highestPixel = readS.readSint16(); + tilePlatformData->vBits = readS.readByte(); + tilePlatformData->uBits = readS.readByte(); + for (x = 0; x < SAGA_PLATFORM_W; x++) { + for (y = 0; y < SAGA_PLATFORM_W; y++) { + tilePlatformData->tiles[x][y] = readS.readSint16(); + } + } + } + +} + +void IsoMap::loadMap(const byte * resourcePointer, size_t resourceLength) { + uint16 x, y; + + if (resourceLength != SAGA_TILEMAP_LEN) { + error("IsoMap::loadMap wrong resourceLength"); + } + + MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian()); + _tileMap.edgeType = readS.readByte(); + readS.readByte(); //skip + + for (x = 0; x < SAGA_TILEMAP_W; x++) { + for (y = 0; y < SAGA_TILEMAP_H; y++) { + _tileMap.tilePlatforms[x][y] = readS.readSint16(); + } + } + +} + +void IsoMap::loadMetaTiles(const byte * resourcePointer, size_t resourceLength) { + MetaTileData *metaTileData; + uint16 i, j; + + if (resourceLength == 0) { + error("IsoMap::loadMetaTiles wrong resourceLength"); + } + + MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian()); + _metaTilesCount = resourceLength / SAGA_METATILEDATA_LEN; + + _metaTileList = (MetaTileData *)malloc(_metaTilesCount * sizeof(*_metaTileList)); + if (_metaTileList == NULL) { + memoryError("IsoMap::loadMetaTiles"); + } + + for (i = 0; i < _metaTilesCount; i++) { + metaTileData = &_metaTileList[i]; + metaTileData->highestPlatform = readS.readUint16(); + metaTileData->highestPixel = readS.readUint16(); + for (j = 0; j < SAGA_MAX_PLATFORM_H; j++) { + metaTileData->stack[j] = readS.readSint16(); + } + } +} + +void IsoMap::loadMulti(const byte * resourcePointer, size_t resourceLength) { + MultiTileEntryData *multiTileEntryData; + uint16 i; + int16 offsetDiff; + + if (resourceLength < 2) { + error("IsoMap::loadMetaTiles wrong resourceLength"); + } + + MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian()); + _multiCount = readS.readUint16(); + _multiTable = (MultiTileEntryData *)malloc(_multiCount * sizeof(*_multiTable)); + if (_multiTable == NULL) { + memoryError("IsoMap::loadMulti"); + } + + for (i = 0; i < _multiCount; i++) { + multiTileEntryData = &_multiTable[i]; + readS.readUint32();//skip + multiTileEntryData->offset = readS.readSint16(); + multiTileEntryData->u = readS.readByte(); + multiTileEntryData->v = readS.readByte(); + multiTileEntryData->h = readS.readByte(); + multiTileEntryData->uSize = readS.readByte(); + multiTileEntryData->vSize = readS.readByte(); + multiTileEntryData->numStates = readS.readByte(); + multiTileEntryData->currentState = readS.readByte(); + readS.readByte();//skip + } + + offsetDiff = (readS.pos() - 2); + + for (i = 0; i < _multiCount; i++) { + _multiTable[i].offset -= offsetDiff; + } + + _multiDataCount = (readS.size() - readS.pos()) / 2; + + _multiTableData = (int16 *)malloc(_multiDataCount * sizeof(*_multiTableData)); + for (i = 0; i < _multiDataCount; i++) { + _multiTableData[i] = readS.readSint16(); + } +} + +void IsoMap::freeMem() { + free(_tileData); + _tileData = NULL; + _tilesCount = 0; + free(_tilePlatformList); + _tilePlatformList = NULL; + _tilePlatformsCount = 0; + free(_metaTileList); + _metaTileList = NULL; + _metaTilesCount = 0; + free(_multiTable); + _multiTable = NULL; + _multiCount = 0; + free(_multiTableData); + _multiTableData = NULL; + _multiDataCount = 0; +} + +void IsoMap::adjustScroll(bool jump) { + Point playerPoint; + Point minScrollPos; + Point maxScrollPos; + + + tileCoordsToScreenPoint(_vm->_actor->_centerActor->_location, playerPoint); + + if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) { + _mapPosition.x = (playerPoint.x + _viewScroll.x) * 30 / 100 - (381); + _mapPosition.y = (playerPoint.y + _viewScroll.y) * 30 / 100 - (342); + } + + if (_vm->_actor->_centerActor != _vm->_actor->_protagonist) { + playerPoint.y -= 24; + } + playerPoint.y -= 28; + + playerPoint.x += _viewScroll.x - _vm->getDisplayWidth()/2; + playerPoint.y += _viewScroll.y - _vm->_scene->getHeight()/2; + + minScrollPos.x = playerPoint.x - SAGA_SCROLL_LIMIT_X1; + minScrollPos.y = playerPoint.y - SAGA_SCROLL_LIMIT_Y1; + + maxScrollPos.x = playerPoint.x + SAGA_SCROLL_LIMIT_X1; + maxScrollPos.y = playerPoint.y + SAGA_SCROLL_LIMIT_Y2; + + if (jump) { + if (_viewScroll.y < minScrollPos.y) { + _viewScroll.y = minScrollPos.y; + } + if (_viewScroll.y > maxScrollPos.y) { + _viewScroll.y = maxScrollPos.y; + } + if (_viewScroll.x < minScrollPos.x) { + _viewScroll.x = minScrollPos.x; + } + if (_viewScroll.x > maxScrollPos.x) { + _viewScroll.x = maxScrollPos.x; + } + } else { + _viewScroll.y = smoothSlide( _viewScroll.y, minScrollPos.y, maxScrollPos.y ); + _viewScroll.x = smoothSlide( _viewScroll.x, minScrollPos.x, maxScrollPos.x ); + } + + if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE) { + ObjectData *obj; + uint16 objectId; + objectId = _vm->_actor->objIndexToId(ITE_OBJ_MAP); + obj = _vm->_actor->getObj(objectId); + if (obj->_sceneNumber != ITE_SCENE_INV) { + _viewScroll.x = 1552 + 8; + _viewScroll.y = 1456 + 8; + } + } +} + +int16 IsoMap::findMulti(int16 tileIndex, int16 absU, int16 absV, int16 absH) { + MultiTileEntryData *multiTileEntryData; + int16 ru; + int16 rv; + int16 mu; + int16 mv; + int16 state; + uint16 i, offset; + int16 *tiles; + + ru = (tileIndex >> 13) & 0x03; + rv = (tileIndex >> 11) & 0x03; + mu = absU - ru; + mv = absV - rv; + + tileIndex = 0; + for (i = 0; i < _multiCount; i++) { + multiTileEntryData = &_multiTable[i]; + + if ((multiTileEntryData->u == mu) && + (multiTileEntryData->v == mv) && + (multiTileEntryData->h == absH)) { + state = multiTileEntryData->currentState; + + offset = (ru + state * multiTileEntryData->uSize) * multiTileEntryData->vSize + rv; + offset *= sizeof(*_multiTableData); + offset += multiTileEntryData->offset; + if (offset + sizeof(*_multiTableData) - 1 >= _multiDataCount * sizeof(*_multiTableData)) { + error("wrong multiTileEntryData->offset"); + } + tiles = (int16*)((byte*)_multiTableData + offset); + tileIndex = *tiles; + if (tileIndex >= 256) { + warning("something terrible happened"); + return 1; + } + return tileIndex; + } + } + + return 1; +} + +void IsoMap::draw(Surface *ds) { + + _tileClip = _vm->_scene->getSceneClip(); + ds->drawRect(_tileClip, 0); + drawTiles(ds, NULL); +} + +void IsoMap::setMapPosition(int x, int y) { + _mapPosition.x = x; + _mapPosition.y = y; +} + +void IsoMap::drawSprite(Surface *ds, SpriteList &spriteList, int spriteNumber, const Location &location, const Point &screenPosition, int scale) { + int width; + int height; + int xAlign; + int yAlign; + const byte *spriteBuffer; + Point spritePointer; + Rect clip(_vm->_scene->getSceneClip()); + + _vm->_sprite->getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer); + + spritePointer.x = screenPosition.x + xAlign; + spritePointer.y = screenPosition.y + yAlign; + + _tileClip.left = spritePointer.x; + _tileClip.top = spritePointer.y; + _tileClip.right = spritePointer.x + width; + _tileClip.bottom = spritePointer.y + height; + + if (_tileClip.left < 0) { + _tileClip.left = 0; + } + if (_tileClip.right > _vm->getDisplayWidth()) { + _tileClip.right = _vm->getDisplayWidth(); + } + if (_tileClip.top < 0) { + _tileClip.top = 0; + } + if (_tileClip.bottom > _vm->_scene->getHeight()) { + _tileClip.bottom = _vm->_scene->getHeight(); + } + + _vm->_sprite->drawClip(ds, clip, spritePointer, width, height, spriteBuffer); + drawTiles(ds, &location); +} + + +void IsoMap::drawTiles(Surface *ds, const Location *location) { + Point view1; + Point fineScroll; + Point tileScroll; + Point metaTileY; + Point metaTileX; + int16 u0, v0, + u1, v1, + u2, v2, + uc, vc; + uint16 metaTileIndex; + Location rLocation; + int16 workAreaWidth; + int16 workAreaHeight; + + tileScroll.x = _viewScroll.x >> 4; + tileScroll.y = _viewScroll.y >> 4; + + fineScroll.x = _viewScroll.x & 0xf; + fineScroll.y = _viewScroll.y & 0xf; + + view1.x = tileScroll.x - (8 * SAGA_TILEMAP_W); + view1.y = (8 * SAGA_TILEMAP_W) - tileScroll.y; + + u0 = ((view1.y + 64) * 2 + view1.x) >> 4; + v0 = ((view1.y + 64) * 2 - view1.x) >> 4; + + metaTileY.x = (u0 - v0) * 128 - (view1.x * 16 + fineScroll.x); + metaTileY.y = (view1.y * 16 - fineScroll.y) - (u0 + v0) * 64; + + workAreaWidth = _vm->getDisplayWidth() + 128; + workAreaHeight = _vm->_scene->getHeight() + 128 + 80; + + for (u1 = u0, v1 = v0; metaTileY.y < workAreaHeight; u1--, v1-- ) { + metaTileX = metaTileY; + + for (u2 = u1, v2 = v1; metaTileX.x < workAreaWidth; u2++, v2--, metaTileX.x += 256) { + + uc = u2 & (SAGA_TILEMAP_W - 1); + vc = v2 & (SAGA_TILEMAP_W - 1); + + if (uc != u2 || vc != v2) { + metaTileIndex = 0; + switch ( _tileMap.edgeType) { + case kEdgeTypeBlack: + continue; + case kEdgeTypeFill0: + break; + case kEdgeTypeFill1: + metaTileIndex = 1; + break; + case kEdgeTypeRpt: + uc = clamp( 0, u2, SAGA_TILEMAP_W - 1); + vc = clamp( 0, v2, SAGA_TILEMAP_W - 1); + metaTileIndex = _tileMap.tilePlatforms[uc][vc]; + break; + case kEdgeTypeWrap: + metaTileIndex = _tileMap.tilePlatforms[uc][vc]; + break; + } + } else { + metaTileIndex = _tileMap.tilePlatforms[uc][vc]; + } + + if (location != NULL) { + rLocation.u() = location->u() - (u2 << 7); + rLocation.v() = location->v() - (v2 << 7); + rLocation.z = location->z; + drawSpriteMetaTile(ds, metaTileIndex, metaTileX, rLocation, u2 << 3, v2 << 3); + } else { + drawMetaTile(ds, metaTileIndex, metaTileX, u2 << 3, v2 << 3); + } + } + + metaTileY.y += 64; + + metaTileX = metaTileY; + + metaTileX.x -= 128; + + for (u2 = u1 - 1, v2 = v1; metaTileX.x < workAreaWidth; u2++, v2--, metaTileX.x += 256) { + + uc = u2 & (SAGA_TILEMAP_W - 1); + vc = v2 & (SAGA_TILEMAP_W - 1); + + if (uc != u2 || vc != v2) { + metaTileIndex = 0; + switch ( _tileMap.edgeType) { + case kEdgeTypeBlack: + continue; + case kEdgeTypeFill0: + break; + case kEdgeTypeFill1: + metaTileIndex = 1; + break; + case kEdgeTypeRpt: + uc = clamp( 0, u2, SAGA_TILEMAP_W - 1); + vc = clamp( 0, v2, SAGA_TILEMAP_W - 1); + metaTileIndex = _tileMap.tilePlatforms[uc][vc]; + break; + case kEdgeTypeWrap: + metaTileIndex = _tileMap.tilePlatforms[uc][vc]; + break; + } + } else { + metaTileIndex = _tileMap.tilePlatforms[uc][vc]; + } + + if (location != NULL) { + rLocation.u() = location->u() - (u2 << 7); + rLocation.v() = location->v() - (v2 << 7); + rLocation.z = location->z; + drawSpriteMetaTile(ds, metaTileIndex, metaTileX, rLocation, u2 << 3, v2 << 3); + } else { + drawMetaTile(ds, metaTileIndex, metaTileX, u2 << 3, v2 << 3); + } + } + metaTileY.y += 64; + } + +} + +void IsoMap::drawSpriteMetaTile(Surface *ds, uint16 metaTileIndex, const Point &point, Location &location, int16 absU, int16 absV) { + MetaTileData * metaTile; + uint16 high; + int16 platformIndex; + Point platformPoint; + platformPoint = point; + + if (_metaTilesCount <= metaTileIndex) { + error("IsoMap::drawMetaTile wrong metaTileIndex"); + } + + metaTile = &_metaTileList[metaTileIndex]; + + if (metaTile->highestPlatform > 18) { + metaTile->highestPlatform = 0; + } + + for (high = 0; high <= metaTile->highestPlatform; high++, platformPoint.y -= 8, location.z -= 8) { + assert(SAGA_MAX_PLATFORM_H > high); + platformIndex = metaTile->stack[high]; + + if (platformIndex >= 0) { + drawSpritePlatform( ds, platformIndex, platformPoint, location, absU, absV, high ); + } + } +} + +void IsoMap::drawMetaTile(Surface *ds, uint16 metaTileIndex, const Point &point, int16 absU, int16 absV) { + MetaTileData * metaTile; + uint16 high; + int16 platformIndex; + Point platformPoint; + platformPoint = point; + + if (_metaTilesCount <= metaTileIndex) { + error("IsoMap::drawMetaTile wrong metaTileIndex"); + } + + metaTile = &_metaTileList[metaTileIndex]; + + if (metaTile->highestPlatform > 18) { + metaTile->highestPlatform = 0; + } + + for (high = 0; high <= metaTile->highestPlatform; high++, platformPoint.y -= 8) { + assert(SAGA_MAX_PLATFORM_H > high); + platformIndex = metaTile->stack[high]; + + if (platformIndex >= 0) { + drawPlatform( ds, platformIndex, platformPoint, absU, absV, high ); + } + } +} + +void IsoMap::drawSpritePlatform(Surface *ds, uint16 platformIndex, const Point &point, const Location &location, int16 absU, int16 absV, int16 absH) { + TilePlatformData *tilePlatform; + int16 u, v; + Point s; + Point s0; + uint16 tileIndex; + Location copyLocation(location); + + if (_tilePlatformsCount <= platformIndex) { + error("IsoMap::drawPlatform wrong platformIndex"); + } + + tilePlatform = &_tilePlatformList[platformIndex]; + + if ((point.y <= _tileClip.top) || (point.y - SAGA_MAX_TILE_H - SAGA_PLATFORM_W * SAGA_TILE_NOMINAL_H >= _tileClip.bottom)) { + return; + } + + s0 = point; + s0.y -= (((SAGA_PLATFORM_W - 1) + (SAGA_PLATFORM_W - 1)) * 8); + + for (v = SAGA_PLATFORM_W - 1, + copyLocation.v() = location.v() - ((SAGA_PLATFORM_W - 1) << 4); + v >= 0 && s0.y - SAGA_MAX_TILE_H < _tileClip.bottom && s0.x - 128 < _tileClip.right; + v--, copyLocation.v() += 16, s0.x += 16, s0.y += 8) { + + if ((tilePlatform->vBits & (1 << v)) == 0) { + continue; + } + + if (s0.x + 128 + 32 < _tileClip.left) { + continue; + } + + s = s0; + + for (u = SAGA_PLATFORM_W - 1, + copyLocation.u() = location.u() - ((SAGA_PLATFORM_W - 1) << 4); + u >= 0 && s.x + 32 > _tileClip.left && s.y - SAGA_MAX_TILE_H < _tileClip.bottom; + u--, copyLocation.u() += 16, s.x -= 16, s.y += 8 ) { + if (s.x < _tileClip.right && s.y > _tileClip.top) { + + tileIndex = tilePlatform->tiles[u][v]; + if (tileIndex != 0) { + if (tileIndex & SAGA_MULTI_TILE) { + tileIndex = findMulti(tileIndex, absU + u, absV + v, absH); + } + + drawTile(ds, tileIndex, s, ©Location); + } + } + } + } +} + +void IsoMap::drawPlatform(Surface *ds, uint16 platformIndex, const Point &point, int16 absU, int16 absV, int16 absH) { + TilePlatformData *tilePlatform; + int16 u, v; + Point s; + Point s0; + uint16 tileIndex; + + if (_tilePlatformsCount <= platformIndex) { + error("IsoMap::drawPlatform wrong platformIndex"); + } + + tilePlatform = &_tilePlatformList[platformIndex]; + + if ((point.y <= _tileClip.top) || (point.y - SAGA_MAX_TILE_H - SAGA_PLATFORM_W * SAGA_TILE_NOMINAL_H >= _tileClip.bottom)) { + return; + } + + s0 = point; + s0.y -= (((SAGA_PLATFORM_W - 1) + (SAGA_PLATFORM_W - 1)) * 8); + + for (v = SAGA_PLATFORM_W - 1; + v >= 0 && s0.y - SAGA_MAX_TILE_H < _tileClip.bottom && s0.x - 128 < _tileClip.right; + v--, s0.x += 16, s0.y += 8) { + + if ((tilePlatform->vBits & (1 << v)) == 0) { + continue; + } + + if (s0.x + 128 + 32 < _tileClip.left) { + continue; + } + + s = s0; + + for (u = SAGA_PLATFORM_W - 1; + u >= 0 && s.x + 32 > _tileClip.left && s.y - SAGA_MAX_TILE_H < _tileClip.bottom; + u--, s.x -= 16, s.y += 8 ) { + if (s.x < _tileClip.right && s.y > _tileClip.top) { + + tileIndex = tilePlatform->tiles[u][v]; + if (tileIndex > 1) { + if (tileIndex & SAGA_MULTI_TILE) { + tileIndex = findMulti(tileIndex, absU + u, absV + v, absH); + } + + drawTile(ds, tileIndex, s, NULL); + } + } + } + } +} + +#define THRESH0 0 +#define THRESH8 8 +#define THRESH16 16 + +void IsoMap::drawTile(Surface *ds, uint16 tileIndex, const Point &point, const Location *location) { + const byte *tilePointer; + const byte *readPointer; + byte *drawPointer; + Point drawPoint; + int height; + int widthCount = 0; + int row, col, count, lowBound; + int bgRunCount; + int fgRunCount; + + + if (tileIndex >= _tilesCount) { + error("IsoMap::drawTile wrong tileIndex"); + } + + + if (point.x + SAGA_ISOTILE_WIDTH < _tileClip.left) { + return; + } + + if (point.x - SAGA_ISOTILE_WIDTH >= _tileClip.right) { + return; + } + + tilePointer = _tileData + _tilesTable[tileIndex].offset; + height = _tilesTable[tileIndex].height; + + if ((height <= 8) || (height > 64)) { + return; + } + + drawPoint = point; + drawPoint.y -= height; + + if (drawPoint.y >= _tileClip.bottom) { + return; + } + + if (location != NULL) { + if (location->z <= -16) { + if (location->z <= -48) { + if (location->u() < -THRESH8 || location->v() < -THRESH8) { + return; + } + } else { + if (location->u() < THRESH0 || location->v() < THRESH0) { + return; + } + } + } else { + if (location->z >= 16) { + return; + } else { + switch (_tilesTable[tileIndex].GetMaskRule()) { + case kMaskRuleNever: + return; + case kMaskRuleAlways: + break; + case kMaskRuleUMIN: + if (location->u() < THRESH0) { + return; + } + break; + case kMaskRuleUMID: + if (location->u() < THRESH8) { + return; + } + break; + case kMaskRuleUMAX: + if (location->u() < THRESH16) { + return; + } + break; + case kMaskRuleVMIN: + if (location->v() < THRESH0) { + return; + } + break; + case kMaskRuleVMID: + if (location->v() < THRESH8) { + return; + } + break; + case kMaskRuleVMAX: + if (location->v() < THRESH16) { + return; + } + break; + case kMaskRuleYMIN: + if (location->uv() < THRESH0 * 2) { + return; + } + break; + case kMaskRuleYMID: + if (location->uv() < THRESH8 * 2) { + return; + } + break; + case kMaskRuleYMAX: + if (location->uv() < THRESH16 * 2) { + return; + } + break; + case kMaskRuleUVMAX: + if (location->u() < THRESH16 && location->v() < THRESH16) { + return; + } + break; + case kMaskRuleUVMIN: + if (location->u() < THRESH0 || location->v() < THRESH0) { + return; + } + break; + case kMaskRuleUorV: + if (location->u() < THRESH8 && location->v() < THRESH8) { + return; + } + break; + case kMaskRuleUandV: + if (location->u() < THRESH8 || location->v() < THRESH8) { + return; + } + break; + } + } + } + } + + readPointer = tilePointer; + lowBound = MIN((int)(drawPoint.y + height), (int)_tileClip.bottom); + for (row = drawPoint.y; row < lowBound; row++) { + widthCount = 0; + if (row >= _tileClip.top) { + drawPointer = (byte *)ds->pixels + drawPoint.x + (row * ds->pitch); + col = drawPoint.x; + for (;;) { + bgRunCount = *readPointer++; + widthCount += bgRunCount; + if (widthCount >= SAGA_ISOTILE_WIDTH) { + break; + } + + drawPointer += bgRunCount; + col += bgRunCount; + fgRunCount = *readPointer++; + widthCount += fgRunCount; + + count = 0; + while ((col < _tileClip.left) && (count < fgRunCount)) { + count++; + col++; + } + while ((col < _tileClip.right) && (count < fgRunCount)) { + assert((byte *)ds->pixels <= (byte *)(drawPointer + count)); + assert((byte *)((byte *)ds->pixels + (_vm->getDisplayWidth() * + _vm->getDisplayHeight())) > (byte *)(drawPointer + count)); + drawPointer[count] = readPointer[count]; + count++; + col++; + } + readPointer += fgRunCount; + drawPointer += fgRunCount; + } + } else { + for (;;) { + bgRunCount = *readPointer++; + widthCount += bgRunCount; + if (widthCount >= SAGA_ISOTILE_WIDTH) { + break; + } + + fgRunCount = *readPointer++; + widthCount += fgRunCount; + + readPointer += fgRunCount; + } + } + } + +} + +bool IsoMap::checkDragonPoint(int16 u, int16 v, uint16 direction) { + DragonPathCell *pathCell; + + if ((u < 1) || (u >= SAGA_DRAGON_SEARCH_DIAMETER - 1) || (v < 1) || (v >= SAGA_DRAGON_SEARCH_DIAMETER - 1)) { + return false; + } + + pathCell = _dragonSearchArray.getPathCell(u, v); + + if (pathCell->visited) { + return false; + } + + pathCell->visited = 1; + pathCell->direction = direction; + return true; +} + +void IsoMap::pushDragonPoint(int16 u, int16 v, uint16 direction) { + DragonTilePoint *tilePoint; + DragonPathCell *pathCell; + + if ((u < 1) || (u >= SAGA_DRAGON_SEARCH_DIAMETER - 1) || (v < 1) || (v >= SAGA_DRAGON_SEARCH_DIAMETER - 1)) { + return; + } + + pathCell = _dragonSearchArray.getPathCell(u, v); + + if (pathCell->visited) { + return; + } + + tilePoint = _dragonSearchArray.getQueue(_queueCount); + _queueCount++; + if (_queueCount >= SAGA_SEARCH_QUEUE_SIZE) { + _queueCount = 0; + } + + tilePoint->u = u; + tilePoint->v = v; + tilePoint->direction = direction; + + pathCell->visited = 1; + pathCell->direction = direction; +} + +void IsoMap::pushPoint(int16 u, int16 v, uint16 cost, uint16 direction) { + int16 upper; + int16 lower; + int16 mid; + TilePoint *tilePoint; + PathCell *pathCell; + + upper = _queueCount; + lower = 0; + + if ((u < 1) || (u >= SAGA_SEARCH_DIAMETER - 1) || (v < 1) || (v >= SAGA_SEARCH_DIAMETER - 1)) { + return; + } + + pathCell = _searchArray.getPathCell(u, v); + + if ((pathCell->visited) && (pathCell->cost <= cost)) { + return; + } + + if (_queueCount >= SAGA_SEARCH_QUEUE_SIZE) { + return; + } + + while (1) { + mid = (upper + lower) / 2; + tilePoint = _searchArray.getQueue(mid); + + if (upper <= lower) { + break; + } + + if (cost < tilePoint->cost) { + lower = mid + 1; + } else { + upper = mid; + } + } + + if (mid < _queueCount ) { + memmove(tilePoint + 1, tilePoint, (_queueCount - mid) * sizeof (*tilePoint)); + } + _queueCount++; + + tilePoint->u = u; + tilePoint->v = v; + tilePoint->cost = cost; + tilePoint->direction = direction; + + pathCell->visited = 1; + pathCell->direction = direction; + pathCell->cost = cost; +} + +int16 IsoMap::getTileIndex(int16 u, int16 v, int16 z) { + int16 mtileU; + int16 mtileV; + int16 uc; + int16 vc; + int16 u0; + int16 v0; + int16 platformIndex; + int16 metaTileIndex; + + mtileU = u >> 3; + mtileV = v >> 3; + uc = mtileU & (SAGA_TILEMAP_W - 1); + vc = mtileV & (SAGA_TILEMAP_W - 1); + u0 = u & (SAGA_PLATFORM_W - 1); + v0 = v & (SAGA_PLATFORM_W - 1); + + if ((uc != mtileU) || (vc != mtileV)) { + metaTileIndex = 0; + switch ( _tileMap.edgeType) { + case kEdgeTypeBlack: + return 0; + case kEdgeTypeFill0: + break; + case kEdgeTypeFill1: + metaTileIndex = 1; + break; + case kEdgeTypeRpt: + uc = clamp( 0, mtileU, SAGA_TILEMAP_W - 1); + vc = clamp( 0, mtileV, SAGA_TILEMAP_W - 1); + metaTileIndex = _tileMap.tilePlatforms[uc][vc]; + break; + case kEdgeTypeWrap: + metaTileIndex = _tileMap.tilePlatforms[uc][vc]; + break; + } + } else { + metaTileIndex = _tileMap.tilePlatforms[uc][vc]; + } + + if (_metaTilesCount <= metaTileIndex) { + error("IsoMap::getTile wrong metaTileIndex"); + } + + platformIndex = _metaTileList[metaTileIndex].stack[z]; + if (platformIndex < 0) { + return 0; + } + + if (_tilePlatformsCount <= platformIndex) { + error("IsoMap::getTile wrong platformIndex"); + } + + return _tilePlatformList[platformIndex].tiles[u0][v0]; +} + +IsoTileData *IsoMap::getTile(int16 u, int16 v, int16 z) { + int16 tileIndex; + + tileIndex = getTileIndex(u, v, z); + + if (tileIndex == 0) { + return NULL; + } + + if (tileIndex & SAGA_MULTI_TILE) { + tileIndex = findMulti(tileIndex, u, v, z); + } + + return &_tilesTable[tileIndex]; +} + +void IsoMap::testPossibleDirections(int16 u, int16 v, uint16 terraComp[8], int skipCenter) { + IsoTileData *tile; + uint16 fgdMask; + uint16 bgdMask; + uint16 mask; + + + memset(terraComp, 0, 8 * sizeof(uint16)); + +#define FILL_MASK(index, testMask) \ + if ( mask & testMask) { \ + terraComp[index] |= fgdMask; \ + } \ + if (~mask & testMask) { \ + terraComp[index] |= bgdMask; \ + } + +#define TEST_TILE_PROLOG(offsetU, offsetV) \ + tile = getTile(u + offsetU, v + offsetV , _platformHeight); \ + if (tile != NULL) { \ + fgdMask = tile->GetFGDMask(); \ + bgdMask = tile->GetBGDMask(); \ + mask = tile->terrainMask; + +#define TEST_TILE_EPILOG(index) \ + } else { \ + if (_vm->_actor->_protagonist->_location.z > 0) { \ + terraComp[index] = SAGA_IMPASSABLE; \ + } \ + } + +#define TEST_TILE_END } + + TEST_TILE_PROLOG(0, 0) + if (skipCenter) { + if ((mask & 0x0660) && (fgdMask & SAGA_IMPASSABLE)) { + fgdMask = 0; + } + if ((~mask & 0x0660) && (bgdMask & SAGA_IMPASSABLE)) { + bgdMask = 0; + } + } + + FILL_MASK(0, 0xcc00) + FILL_MASK(1, 0x6600) + FILL_MASK(2, 0x3300) + FILL_MASK(3, 0x0330) + FILL_MASK(4, 0x0033) + FILL_MASK(5, 0x0066) + FILL_MASK(6, 0x00cc) + FILL_MASK(7, 0x0cc0) + TEST_TILE_END + + TEST_TILE_PROLOG(1, 1) + FILL_MASK(0, 0x0673) + TEST_TILE_EPILOG(0) + + + TEST_TILE_PROLOG(1, 0) + FILL_MASK(0, 0x0008) + FILL_MASK(1, 0x0666) + FILL_MASK(2, 0x0001) + TEST_TILE_EPILOG(1) + + + TEST_TILE_PROLOG(1, -1) + FILL_MASK(2, 0x06ec) + TEST_TILE_EPILOG(2) + + TEST_TILE_PROLOG(0, 1) + FILL_MASK(0, 0x1000) + FILL_MASK(7, 0x0770) + FILL_MASK(6, 0x0001) + TEST_TILE_EPILOG(7) + + + TEST_TILE_PROLOG(0, -1) + FILL_MASK(2, 0x8000) + FILL_MASK(3, 0x0ee0) + FILL_MASK(4, 0x0008) + TEST_TILE_EPILOG(3) + + + TEST_TILE_PROLOG(-1, 1) + FILL_MASK(6, 0x3670) + TEST_TILE_EPILOG(6) + + + TEST_TILE_PROLOG(-1, 0) + FILL_MASK(6, 0x8000) + FILL_MASK(5, 0x6660) + FILL_MASK(4, 0x1000) + TEST_TILE_EPILOG(5) + + TEST_TILE_PROLOG(-1, -1) + FILL_MASK(4, 0xce60) + TEST_TILE_EPILOG(4) +} + +void IsoMap::placeOnTileMap(const Location &start, Location &result, int16 distance, uint16 direction) { + int16 bestDistance; + int16 bestU; + int16 bestV; + int16 uBase; + int16 vBase; + int16 u; + int16 v; + int i; + ActorData *actor; + TilePoint tilePoint; + uint16 dir; + int16 dist; + uint16 terraComp[8]; + const TilePoint *tdir; + uint16 terrainMask; + + bestDistance = 0; + + + uBase = (start.u() >> 4) - SAGA_SEARCH_CENTER; + vBase = (start.v() >> 4) - SAGA_SEARCH_CENTER; + + bestU = SAGA_SEARCH_CENTER; + bestV = SAGA_SEARCH_CENTER; + + _platformHeight = _vm->_actor->_protagonist->_location.z / 8; + + memset( &_searchArray, 0, sizeof(_searchArray)); + + for (i = 0; i < _vm->_actor->_actorsCount; i++) { + actor = _vm->_actor->_actors[i]; + if (!actor->_inScene) continue; + + u = (actor->_location.u() >> 4) - uBase; + v = (actor->_location.v() >> 4) - vBase; + if ((u >= 0) && (u < SAGA_SEARCH_DIAMETER) && + (v >= 0) && (v < SAGA_SEARCH_DIAMETER) && + ((u != SAGA_SEARCH_CENTER) || (v != SAGA_SEARCH_CENTER))) { + _searchArray.getPathCell(u, v)->visited = 1; + } + } + + _queueCount = 0; + pushPoint(SAGA_SEARCH_CENTER, SAGA_SEARCH_CENTER, 0, 0); + + while (_queueCount > 0) { + + _queueCount--; + tilePoint = *_searchArray.getQueue(_queueCount); + + + dist = ABS(tilePoint.u - SAGA_SEARCH_CENTER) + ABS(tilePoint.v - SAGA_SEARCH_CENTER); + + if (dist > bestDistance) { + bestU = tilePoint.u; + bestV = tilePoint.v; + bestDistance = dist; + + if (dist >= distance) { + break; + } + } + + testPossibleDirections(uBase + tilePoint.u, vBase + tilePoint.v, terraComp, 0); + + + for (dir = 0; dir < 8; dir++) { + terrainMask = terraComp[dir]; + + if (terrainMask & SAGA_IMPASSABLE ) { + continue; + } + + if (dir == direction) { + tdir = &easyDirTable[ dir ]; + } else { + if (dir + 1 == direction || dir - 1 == direction) { + tdir = &normalDirTable[ dir ]; + } else { + tdir = &hardDirTable[ dir ]; + } + } + + pushPoint(tilePoint.u + tdir->u,tilePoint.v + tdir->v, tilePoint.cost + tdir->cost, dir); + } + } + + result.u() = ((uBase + bestU) << 4) + 8; + result.v() = ((vBase + bestV) << 4) + 8; +} + +bool IsoMap::findNearestChasm(int16 &u0, int16 &v0, uint16 &direction) { + int16 u, v; + uint16 i; + u = u0; + v = v0; + + for (i = 1; i < 5; i++) { + if (getTile( u - i, v, 6) == NULL) { + u0 = u - i - 1; + v0 = v; + direction = kDirDownLeft; + return true; + } + + if (getTile( u, v - i, 6) == NULL) { + u0 = u; + v0 = v - i - 1; + direction = kDirDownRight; + return true; + } + + if (getTile( u - i, v - i, 6) == NULL) { + u0 = u - i - 1; + v0 = v - i - 1; + direction = kDirDown; + return true; + } + + if (getTile( u + i, v - i, 6) == NULL) { + u0 = u + i + 1; + v0 = v - i - 1; + direction = kDirDownRight; + return true; + } + + if (getTile( u - i, v + i, 6) == NULL) { + u0 = u + i + 1; + v0 = v - i - 1; + direction = kDirLeft; + return true; + } + } + + for (i = 1; i < 5; i++) { + if (getTile( u + i, v, 6) == NULL) { + u0 = u + i + 1; + v0 = v; + direction = kDirUpRight; + return true; + } + + if (getTile( u, v + i, 6) == NULL) { + u0 = u; + v0 = v + i + 1; + direction = kDirUpLeft; + return true; + } + + if (getTile( u + i, v + i, 6) == NULL) { + u0 = u + i + 1; + v0 = v + i + 1; + direction = kDirUp; + return true; + } + } + return false; +} + +void IsoMap::findDragonTilePath(ActorData* actor,const Location &start, const Location &end, uint16 initialDirection) { + byte *res; + int i; + int16 u; + int16 v; + int16 u1; + int16 v1; + uint16 dir; + + int16 bestDistance; + int16 bestU; + int16 bestV; + + int16 uBase; + int16 vBase; + int16 uFinish; + int16 vFinish; + DragonPathCell *pcell; + IsoTileData *tile; + uint16 mask; + DragonTilePoint *tilePoint; + + int16 dist; + bool first; + + bestDistance = SAGA_DRAGON_SEARCH_DIAMETER; + bestU = SAGA_DRAGON_SEARCH_CENTER, + bestV = SAGA_DRAGON_SEARCH_CENTER; + + uBase = (start.u() >> 4) - SAGA_DRAGON_SEARCH_CENTER; + vBase = (start.v() >> 4) - SAGA_DRAGON_SEARCH_CENTER; + uFinish = (end.u() >> 4) - uBase; + vFinish = (end.v() >> 4) - vBase; + + _platformHeight = _vm->_actor->_protagonist->_location.z / 8; + + memset( &_dragonSearchArray, 0, sizeof(_dragonSearchArray)); + + for (u = 0; u < SAGA_DRAGON_SEARCH_DIAMETER; u++) { + for (v = 0; v < SAGA_DRAGON_SEARCH_DIAMETER; v++) { + + pcell = _dragonSearchArray.getPathCell(u, v); + + u1 = uBase + u; + v1 = vBase + v; + + if ((u1 > 127) || (u1 < 48) || (v1 > 127) || (v1 < 0)) { + pcell->visited = 1; + continue; + } + + tile = getTile(u1, v1, _platformHeight ); + if (tile != NULL) { + mask = tile->terrainMask; + if ( ((mask != 0) && (tile->GetFGDAttr() >= kTerrBlock)) || + ((mask != 0xFFFF) && (tile->GetBGDAttr() >= kTerrBlock)) ) { + pcell->visited = 1; + } + } else { + pcell->visited = 1; + } + } + } + + first = true; + _queueCount = _readCount = 0; + pushDragonPoint( SAGA_DRAGON_SEARCH_CENTER, SAGA_DRAGON_SEARCH_CENTER, initialDirection); + + while (_queueCount != _readCount) { + + tilePoint = _dragonSearchArray.getQueue(_readCount++); + if (_readCount >= SAGA_SEARCH_QUEUE_SIZE) { + _readCount = 0; + } + + + dist = ABS(tilePoint->u - uFinish) + ABS(tilePoint->v - vFinish); + + if (dist < bestDistance) { + + bestU = tilePoint->u; + bestV = tilePoint->v; + bestDistance = dist; + if (dist == 0) { + break; + } + } + + switch (tilePoint->direction) { + case kDirUpRight: + if (checkDragonPoint( tilePoint->u + 1, tilePoint->v + 0, kDirUpRight)) { + pushDragonPoint( tilePoint->u + 2, tilePoint->v + 0, kDirUpRight); + pushDragonPoint( tilePoint->u + 1, tilePoint->v + 1, kDirUpLeft); + pushDragonPoint( tilePoint->u + 1, tilePoint->v - 1, kDirDownRight); + } + break; + case kDirDownRight: + if (checkDragonPoint( tilePoint->u + 0, tilePoint->v - 1, kDirDownRight)) { + pushDragonPoint( tilePoint->u + 0, tilePoint->v - 2, kDirDownRight); + pushDragonPoint( tilePoint->u + 1, tilePoint->v - 1, kDirUpRight); + pushDragonPoint( tilePoint->u - 1, tilePoint->v - 1, kDirDownLeft); + } + break; + case kDirDownLeft: + if (checkDragonPoint( tilePoint->u - 1, tilePoint->v + 0, kDirDownLeft)) { + pushDragonPoint( tilePoint->u - 2, tilePoint->v + 0, kDirDownLeft); + pushDragonPoint( tilePoint->u - 1, tilePoint->v - 1, kDirDownRight); + pushDragonPoint( tilePoint->u - 1, tilePoint->v + 1, kDirUpLeft); + } + break; + case kDirUpLeft: + if (checkDragonPoint( tilePoint->u + 0, tilePoint->v + 1, kDirUpLeft)) { + pushDragonPoint( tilePoint->u + 0, tilePoint->v + 2, kDirUpLeft); + pushDragonPoint( tilePoint->u - 1, tilePoint->v + 1, kDirDownLeft); + pushDragonPoint( tilePoint->u + 1, tilePoint->v + 1, kDirUpRight); + } + break; + } + + if (first && (_queueCount == _readCount)) { + pushDragonPoint( tilePoint->u + 1, tilePoint->v + 0, kDirUpRight); + pushDragonPoint( tilePoint->u + 0, tilePoint->v - 1, kDirDownRight); + pushDragonPoint( tilePoint->u - 1, tilePoint->v + 0, kDirDownLeft); + pushDragonPoint( tilePoint->u + 0, tilePoint->v + 1, kDirUpLeft); + } + first = false; + } + + res = &_pathDirections[SAGA_MAX_PATH_DIRECTIONS]; + i = 0; + while ((bestU != SAGA_DRAGON_SEARCH_CENTER) || (bestV != SAGA_DRAGON_SEARCH_CENTER)) { + pcell = _dragonSearchArray.getPathCell(bestU, bestV); + + *--res = pcell->direction; + i++; + if (i >= SAGA_MAX_PATH_DIRECTIONS) { + break; + } + + dir = (pcell->direction + 4) & 0x07; + + bestU += normalDirTable[dir].u; + bestV += normalDirTable[dir].v; + } + +/* if (i > 64) { + i = 64; + }*/ + + actor->_walkStepsCount = i; + if (i) { + actor->setTileDirectionsSize(i, false); + memcpy(actor->_tileDirections, res, i ); + } + +} + +void IsoMap::findTilePath(ActorData* actor, const Location &start, const Location &end) { + ActorData *other; + int i; + int16 u; + int16 v; + int16 bestDistance; + int16 bestU; + int16 bestV; + + int16 uBase; + int16 vBase; + int16 uFinish; + int16 vFinish; + + TilePoint tilePoint; + uint16 dir; + int16 dist; + uint16 terraComp[8]; + const TilePoint *tdir; + uint16 terrainMask; + const PathCell *pcell; + byte *res; + + + bestDistance = SAGA_SEARCH_DIAMETER; + bestU = SAGA_SEARCH_CENTER, + bestV = SAGA_SEARCH_CENTER; + + uBase = (start.u() >> 4) - SAGA_SEARCH_CENTER; + vBase = (start.v() >> 4) - SAGA_SEARCH_CENTER; + uFinish = (end.u() >> 4) - uBase; + vFinish = (end.v() >> 4) - vBase; + + _platformHeight = _vm->_actor->_protagonist->_location.z / 8; + + + + memset( &_searchArray, 0, sizeof(_searchArray)); + + if (!(actor->_actorFlags & kActorNoCollide) && + (_vm->_scene->currentSceneResourceId() != RID_ITE_OVERMAP_SCENE)) { + for (i = 0; i < _vm->_actor->_actorsCount; i++) { + other = _vm->_actor->_actors[i]; + if (!other->_inScene) continue; + if (other == actor) continue; + + u = (other->_location.u() >> 4) - uBase; + v = (other->_location.v() >> 4) - vBase; + if ((u >= 1) && (u < SAGA_SEARCH_DIAMETER) && + (v >= 1) && (v < SAGA_SEARCH_DIAMETER) && + ((u != SAGA_SEARCH_CENTER) || (v != SAGA_SEARCH_CENTER))) { + _searchArray.getPathCell(u, v)->visited = 1; + } + } + } + + _queueCount = 0; + pushPoint(SAGA_SEARCH_CENTER, SAGA_SEARCH_CENTER, 0, 0); + + + while (_queueCount > 0) { + + _queueCount--; + tilePoint = *_searchArray.getQueue(_queueCount); + + if (tilePoint.cost > 100 && actor == _vm->_actor->_protagonist) continue; + + dist = ABS(tilePoint.u - uFinish) + ABS(tilePoint.v - vFinish); + + if (dist < bestDistance) { + bestU = tilePoint.u; + bestV = tilePoint.v; + bestDistance = dist; + + if (dist == 0) { + break; + } + } + + testPossibleDirections(uBase + tilePoint.u, vBase + tilePoint.v, terraComp, + (tilePoint.u == SAGA_SEARCH_CENTER && tilePoint.v == SAGA_SEARCH_CENTER)); + + for (dir = 0; dir < 8; dir++) { + terrainMask = terraComp[dir]; + + if (terrainMask & SAGA_IMPASSABLE) { + continue; + } else { + if (terrainMask & (1 << kTerrRough)) { + tdir = &hardDirTable[ dir ]; + } else { + if (terrainMask & (1 << kTerrNone)) { + tdir = &normalDirTable[ dir ]; + } else { + tdir = &easyDirTable[ dir ]; + } + } + } + + + pushPoint(tilePoint.u + tdir->u, tilePoint.v + tdir->v, tilePoint.cost + tdir->cost, dir); + } + } + + res = &_pathDirections[SAGA_MAX_PATH_DIRECTIONS]; + i = 0; + while ((bestU != SAGA_SEARCH_CENTER) || (bestV != SAGA_SEARCH_CENTER)) { + pcell = _searchArray.getPathCell(bestU, bestV); + + *--res = pcell->direction; + i++; + if (i >= SAGA_MAX_PATH_DIRECTIONS) { + break; + } + + dir = (pcell->direction + 4) & 0x07; + + bestU += normalDirTable[dir].u; + bestV += normalDirTable[dir].v; + } + +/* if (i > 64) { + i = 64; + }*/ + actor->_walkStepsCount = i; + if (i) { + actor->setTileDirectionsSize(i, false); + memcpy(actor->_tileDirections, res, i ); + } +} + +void IsoMap::setTileDoorState(int doorNumber, int doorState) { + MultiTileEntryData *multiTileEntryData; + + if ((doorNumber < 0) || (doorNumber >= _multiCount)) { + error("setTileDoorState: doorNumber >= _multiCount"); + } + + multiTileEntryData = &_multiTable[doorNumber]; + multiTileEntryData->currentState = doorState; +} + +static const int16 directions[8][2] = { + { 16, 16}, + { 16, 0}, + { 16, -16}, + { 0, -16}, + { -16, -16}, + { -16, 0}, + { -16, 16}, + { 0, 16} +}; + + + +bool IsoMap::nextTileTarget(ActorData* actor) { + uint16 dir; + + if (actor->_walkStepIndex >= actor->_walkStepsCount) { + return false; + } + + + actor->_actionDirection = dir = actor->_tileDirections[actor->_walkStepIndex++]; + + actor->_partialTarget.u() = + (actor->_location.u() & ~0x0f) + 8 + directions[dir][0]; + + actor->_partialTarget.v() = + (actor->_location.v() & ~0x0f) + 8 + directions[dir][1]; + + + if (dir == 0) { + actor->_facingDirection = kDirUp; + } else { + if (dir == 4) { + actor->_facingDirection = kDirDown; + } else { + if (dir < 4) { + actor->_facingDirection = kDirRight; + } else { + actor->_facingDirection = kDirLeft; + } + } + } + + return true; +} + +void IsoMap::screenPointToTileCoords(const Point &position, Location &location) { + Point mPos(position); + int x,y; + + if (_vm->_scene->currentSceneResourceId() == RID_ITE_OVERMAP_SCENE){ + if (mPos.y < 16) { + mPos.y = 16; + } + } + + x = mPos.x + _viewScroll.x - (128 * SAGA_TILEMAP_W) - 16; + y = mPos.y + _viewScroll.y - (128 * SAGA_TILEMAP_W) + _vm->_actor->_protagonist->_location.z; + + location.u() = (x - y * 2) >> 1; + location.v() = - (x + y * 2) >> 1; + location.z = _vm->_actor->_protagonist->_location.z; +} + +} // End of namespace Saga diff --git a/engines/saga/isomap.h b/engines/saga/isomap.h new file mode 100644 index 0000000000..9264c20fbe --- /dev/null +++ b/engines/saga/isomap.h @@ -0,0 +1,294 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Isometric level module - private header + +#ifndef SAGA_ISOMAP_H_ +#define SAGA_ISOMAP_H_ + +#include "saga/actor.h" + +namespace Saga { + +#define SAGA_ISOTILEDATA_LEN 8 +#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; + int8 attributes; + size_t offset; + 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() { + freeMem(); + } + void loadImages(const byte * resourcePointer, size_t resourceLength); + void loadMap(const byte * resourcePointer, size_t resourceLength); + void loadPlatforms(const byte * resourcePointer, size_t resourceLength); + void loadMetaTiles(const byte * resourcePointer, size_t resourceLength); + void loadMulti(const byte * resourcePointer, size_t resourceLength); + void freeMem(); + void draw(Surface *ds); + void drawSprite(Surface *ds, 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(Surface *ds, const Location *location); + void drawMetaTile(Surface *ds, uint16 metaTileIndex, const Point &point, int16 absU, int16 absV); + void drawSpriteMetaTile(Surface *ds, uint16 metaTileIndex, const Point &point, Location &location, int16 absU, int16 absV); + void drawPlatform(Surface *ds, uint16 platformIndex, const Point &point, int16 absU, int16 absV, int16 absH); + void drawSpritePlatform(Surface *ds, uint16 platformIndex, const Point &point, const Location &location, int16 absU, int16 absV, int16 absH); + void drawTile(Surface *ds, 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); + + + byte *_tileData; + size_t _tileDataLength; + uint16 _tilesCount; + IsoTileData *_tilesTable; + + uint16 _tilePlatformsCount; + TilePlatformData *_tilePlatformList; + uint16 _metaTilesCount; + MetaTileData *_metaTileList; + + uint16 _multiCount; + MultiTileEntryData *_multiTable; + uint16 _multiDataCount; + 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 diff --git a/engines/saga/ite_introproc.cpp b/engines/saga/ite_introproc.cpp new file mode 100644 index 0000000000..abe2deb4a5 --- /dev/null +++ b/engines/saga/ite_introproc.cpp @@ -0,0 +1,1028 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + + +// 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/sndres.h" +#include "saga/palanim.h" +#include "saga/music.h" + +#include "saga/scene.h" +#include "saga/resnames.h" +#include "saga/rscfile.h" + +namespace Saga { + +using Common::UNK_LANG; +using Common::EN_USA; +using Common::DE_DEU; + +LoadSceneParams ITE_IntroList[] = { + {RID_ITE_INTRO_ANIM_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroAnimProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_CAVE_SCENE_1, kLoadByResourceId, NULL, Scene::SC_ITEIntroCave1Proc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_CAVE_SCENE_2, kLoadByResourceId, NULL, Scene::SC_ITEIntroCave2Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_CAVE_SCENE_3, kLoadByResourceId, NULL, Scene::SC_ITEIntroCave3Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_CAVE_SCENE_4, kLoadByResourceId, NULL, Scene::SC_ITEIntroCave4Proc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_VALLEY_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroValleyProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_TREEHOUSE_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroTreeHouseProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_FAIREPATH_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroFairePathProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_FAIRETENT_SCENE, kLoadByResourceId, NULL, Scene::SC_ITEIntroFaireTentProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE} +}; + +int Scene::ITEStartProc() { + size_t scenesCount; + size_t i; + + LoadSceneParams firstScene; + LoadSceneParams tempScene; + + scenesCount = ARRAYSIZE(ITE_IntroList); + + for (i = 0; i < scenesCount; i++) { + tempScene = ITE_IntroList[i]; + tempScene.sceneDescriptor = _vm->_resource->convertResourceId(tempScene.sceneDescriptor); + _vm->_scene->queueScene(&tempScene); + } + + + firstScene.loadFlag = kLoadBySceneNumber; + firstScene.sceneDescriptor = _vm->getStartSceneNumber(); + firstScene.sceneDescription = NULL; + firstScene.sceneSkipTarget = true; + firstScene.sceneProc = NULL; + firstScene.transitionType = kTransitionFade; + firstScene.actorsEntrance = 0; + firstScene.chapter = -1; + + _vm->_scene->queueScene(&firstScene); + + return SUCCESS; +} + +Event *Scene::ITEQueueDialogue(Event *q_event, int n_dialogues, const IntroDialogue dialogue[]) { + TextListEntry textEntry; + TextListEntry *entry; + Event event; + int voice_len; + int i; + + // Queue narrator dialogue list + textEntry.knownColor = kKnownColorSubtitleTextColor; + textEntry.effectKnownColor = kKnownColorTransparent; + textEntry.useRect = true; + textEntry.rect.left = 0; + textEntry.rect.right = _vm->getDisplayWidth(); + textEntry.rect.top = (_vm->getLanguage() == Common::DE_DEU) ? INTRO_DE_CAPTION_Y : INTRO_CAPTION_Y; + textEntry.rect.bottom = _vm->getDisplayHeight(); + textEntry.font = kKnownFontMedium; + textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered); + + for (i = 0; i < n_dialogues; i++) { + textEntry.text = dialogue[i].i_str; + entry = _vm->_scene->_textList.addEntry(textEntry); + + // Display text + event.type = kEvTOneshot; + event.code = kTextEvent; + event.op = kEventDisplay; + event.data = entry; + event.time = (i == 0) ? 0 : VOICE_PAD; + + q_event = _vm->_events->chain(q_event, &event); + + // Play voice + event.type = kEvTOneshot; + event.code = kVoiceEvent; + event.op = kEventPlay; + event.param = dialogue[i].i_voice_rn; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + voice_len = _vm->_sndRes->getVoiceLength(dialogue[i].i_voice_rn); + if (voice_len < 0) { + voice_len = strlen(dialogue[i].i_str) * VOICE_LETTERLEN; + } + + // Remove text + event.type = kEvTOneshot; + event.code = kTextEvent; + event.op = kEventRemove; + event.data = entry; + event.time = voice_len; + + q_event = _vm->_events->chain(q_event, &event); + } + + return q_event; +} + +enum { + kCHeader, + kCText +}; + +enum { + kITEPC = (1 << 0), + kITEPCCD = (1 << 1), + kITEMac = (1 << 2), + kITEWyrmKeep = (1 << 3), + kITEAny = 0xffff, + kITENotWyrmKeep = kITEAny & ~kITEWyrmKeep +}; + +// Queue a page of credits text. The original interpreter did word-wrapping +// automatically. We currently don't. + +Event *Scene::ITEQueueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]) { + int game; + Common::Language lang; + + // The assumption here is that all WyrmKeep versions have the same + // credits, regardless of which operating system they're for. + + lang = _vm->getLanguage(); + + if (_vm->getFeatures() & GF_WYRMKEEP) { + game = kITEWyrmKeep; + } else if (_vm->getPlatform() == Common::kPlatformMacintosh) { + game = kITEMac; + } else if (_vm->getGameId() == GID_ITE_CD_G) { + game = kITEPCCD; + } else { + game = kITEPC; + } + + int line_spacing = 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 != UNK_LANG) { + continue; + } + + if (!(credits[i].game & game)) { + continue; + } + + switch (credits[i].type) { + case kCHeader: + font = kKnownFontSmall; + line_spacing = 4; + n_paragraphs++; + break; + case kCText: + font = kKnownFontMedium; + line_spacing = 2; + break; + default: + error("Unknown credit type"); + } + + credits_height += (_vm->_font->getHeight(font) + line_spacing); + } + + paragraph_spacing = (200 - credits_height) / (n_paragraphs + 3); + credits_height += (n_paragraphs * paragraph_spacing); + + int y = paragraph_spacing; + + TextListEntry textEntry; + TextListEntry *entry; + Event event; + Event *q_event = NULL; + + textEntry.knownColor = kKnownColorSubtitleTextColor; + textEntry.effectKnownColor = kKnownColorTransparent; + textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered); + textEntry.point.x = 160; + + for (i = 0; i < n_credits; i++) { + if (credits[i].lang != lang && credits[i].lang != UNK_LANG) { + continue; + } + + if (!(credits[i].game & game)) { + continue; + } + + switch (credits[i].type) { + case kCHeader: + font = kKnownFontSmall; + line_spacing = 4; + y += paragraph_spacing; + break; + case kCText: + font = kKnownFontMedium; + line_spacing = 2; + break; + default: + break; + } + + textEntry.text = credits[i].string; + textEntry.font = font; + textEntry.point.y = y; + + entry = _vm->_scene->_textList.addEntry(textEntry); + + // Display text + event.type = kEvTOneshot; + event.code = kTextEvent; + event.op = kEventDisplay; + event.data = entry; + event.time = delta_time; + + q_event = _vm->_events->queue(&event); + + // Remove text + event.type = kEvTOneshot; + event.code = kTextEvent; + event.op = kEventRemove; + event.data = entry; + event.time = duration; + + q_event = _vm->_events->chain(q_event, &event); + + y += (_vm->_font->getHeight(font) + line_spacing); + } + + return q_event; +} + +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; + Event *q_event; + + 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; + + q_event = _vm->_events->queue(&event); + + debug(3, "Intro animation procedure started."); + debug(3, "Linking animation resources..."); + + _vm->_anim->setFrameTime(0, ITE_INTRO_FRAMETIME); + + // Link this scene's animation resources for continuous + // playback + int lastAnim; + + if (_vm->getFeatures() & GF_WYRMKEEP) { + if (_vm->getPlatform() == Common::kPlatformMacintosh) { + lastAnim = 3; + } else { + lastAnim = 2; + } + } else { + if (_vm->getPlatform() == Common::kPlatformMacintosh) { + lastAnim = 4; + } else { + lastAnim = 5; + } + } + + for (int i = 0; i < lastAnim; i++) + _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; + + q_event = _vm->_events->chain(q_event, &event); + + // Queue intro music playback + event.type = kEvTOneshot; + event.code = kMusicEvent; + event.param = MUSIC_1; + event.param2 = MUSIC_LOOP; + event.op = kEventPlay; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + } + break; + case SCENE_END: + break; + default: + warning("Illegal scene procedure parameter"); + break; + } + + return 0; +} + +int Scene::SC_ITEIntroCave1Proc(int param, void *refCon) { + return ((Scene *)refCon)->ITEIntroCave1Proc(param); +} + +// Handles first introductory cave painting scene +int Scene::ITEIntroCave1Proc(int param) { + Event event; + Event *q_event; + int lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0; + + static const IntroDialogue dialogue[][4] = { + { { // English + RID_CAVE_VOICE_0, + "We see the sky, we see the land, we see the water, " + "and we wonder: Are we the only ones?" + }, + { + RID_CAVE_VOICE_1, + "Long before we came to exist, the humans ruled the " + "Earth." + }, + { + RID_CAVE_VOICE_2, + "They made marvelous things, and moved whole " + "mountains." + }, + { + RID_CAVE_VOICE_3, + "They knew the Secret of Flight, the Secret of " + "Happiness, and other secrets beyond our imagining." + } }, + { { // German + RID_CAVE_VOICE_0, + "Um uns sind der Himmel, das Land und die Seen; und " + "wir fragen uns - sind wir die einzigen?" + }, + { + RID_CAVE_VOICE_1, + "Lange vor unserer Zeit herrschten die Menschen " + "\201ber die Erde." + }, + { + RID_CAVE_VOICE_2, + "Sie taten wundersame Dinge und versetzten ganze " + "Berge." + }, + { + RID_CAVE_VOICE_3, + "Sie kannten das Geheimnis des Fluges, das Geheimnis " + "der Fr\224hlichkeit und andere Geheimnisse, die " + "unsere Vorstellungskraft \201bersteigen." + } } + }; + + int n_dialogues = ARRAYSIZE(dialogue[lang]); + + switch (param) { + case SCENE_BEGIN: + // Begin palette cycling animation for candles + event.type = kEvTOneshot; + event.code = kPalAnimEvent; + event.op = kEventCycleStart; + event.time = 0; + + q_event = _vm->_events->queue(&event); + + // Queue narrator dialogue list + q_event = ITEQueueDialogue(q_event, n_dialogues, dialogue[lang]); + + // End scene after last dialogue over + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = VOICE_PAD; + + q_event = _vm->_events->chain(q_event, &event); + break; + case SCENE_END: + break; + + default: + warning("Illegal scene procedure paramater"); + break; + } + + return 0; +} + +int Scene::SC_ITEIntroCave2Proc(int param, void *refCon) { + return ((Scene *)refCon)->ITEIntroCave2Proc(param); +} + +// Handles second introductory cave painting scene +int Scene::ITEIntroCave2Proc(int param) { + Event event; + Event *q_event; + int lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0; + + static const IntroDialogue dialogue[][3] = { + { { // English + RID_CAVE_VOICE_4, + "The humans also knew the Secret of Life, and they " + "used it to give us the Four Great Gifts:" + }, + { + RID_CAVE_VOICE_5, + "Thinking minds, feeling hearts, speaking mouths, and " + "reaching hands." + }, + { + RID_CAVE_VOICE_6, + "We are their children." + } }, + { { // German + RID_CAVE_VOICE_4, + "Au$erdem kannten die Menschen das Geheimnis des " + "Lebens. Und sie nutzten es, um uns die vier gro$en " + "Geschenke zu geben -" + }, + { + RID_CAVE_VOICE_5, + "den denkenden Geist, das f\201hlende Herz, den " + "sprechenden Mund und die greifende Hand." + }, + { + RID_CAVE_VOICE_6, + "Wir sind ihre Kinder." + } } + }; + + int n_dialogues = ARRAYSIZE(dialogue[lang]); + + 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; + + q_event = _vm->_events->queue(&event); + + // Begin palette cycling animation for candles + event.type = kEvTOneshot; + event.code = kPalAnimEvent; + event.op = kEventCycleStart; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + // Queue narrator dialogue list + q_event = ITEQueueDialogue(q_event, n_dialogues, dialogue[lang]); + + // End scene after last dialogue over + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = VOICE_PAD; + + q_event = _vm->_events->chain(q_event, &event); + break; + case SCENE_END: + break; + default: + warning("Illegal scene procedure paramater"); + break; + } + + return 0; +} + +int Scene::SC_ITEIntroCave3Proc(int param, void *refCon) { + return ((Scene *)refCon)->ITEIntroCave3Proc(param); +} + +// Handles third introductory cave painting scene +int Scene::ITEIntroCave3Proc(int param) { + Event event; + Event *q_event; + int lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0; + + static const IntroDialogue dialogue[][3] = { + { { // English + RID_CAVE_VOICE_7, + "They taught us how to use our hands, and how to " + "speak." + }, + { + RID_CAVE_VOICE_8, + "They showed us the joy of using our minds." + }, + { + RID_CAVE_VOICE_9, + "They loved us, and when we were ready, they surely " + "would have given us the Secret of Happiness." + } }, + { { // German + RID_CAVE_VOICE_7, + "Sie lehrten uns zu sprechen und unsere H\204nde zu " + "benutzen." + }, + { + RID_CAVE_VOICE_8, + "Sie zeigten uns die Freude am Denken." + }, + { + RID_CAVE_VOICE_9, + "Sie liebten uns, und w\204ren wir bereit gewesen, " + "h\204tten sie uns sicherlich das Geheimnis der " + "Fr\224hlichkeit offenbart." + } } + }; + + int n_dialogues = ARRAYSIZE(dialogue[lang]); + + 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; + + q_event = _vm->_events->queue(&event); + + // Begin palette cycling animation for candles + event.type = kEvTOneshot; + event.code = kPalAnimEvent; + event.op = kEventCycleStart; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + // Queue narrator dialogue list + q_event = ITEQueueDialogue(q_event, n_dialogues, dialogue[lang]); + + // End scene after last dialogue over + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = VOICE_PAD; + + q_event = _vm->_events->chain(q_event, &event); + break; + case SCENE_END: + break; + default: + warning("Illegal scene procedure paramater"); + break; + } + + return 0; +} + +int Scene::SC_ITEIntroCave4Proc(int param, void *refCon) { + return ((Scene *)refCon)->ITEIntroCave4Proc(param); +} + +// Handles fourth introductory cave painting scene +int Scene::ITEIntroCave4Proc(int param) { + Event event; + Event *q_event; + int lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0; + + static const IntroDialogue dialogue[][4] = { + { { // English + RID_CAVE_VOICE_10, + "And now we see the sky, the land, and the water that " + "we are heirs to, and we wonder: why did they leave?" + }, + { + RID_CAVE_VOICE_11, + "Do they live still, in the stars? In the oceans " + "depths? In the wind?" + }, + { + RID_CAVE_VOICE_12, + "We wonder, was their fate good or evil?" + }, + { + RID_CAVE_VOICE_13, + "And will we also share the same fate one day?" + } }, + { { // German + RID_CAVE_VOICE_10, + "Und nun sehen wir den Himmel, das Land und die " + "Seen - unser Erbe. Und wir fragen uns - warum " + "verschwanden sie?" + }, + { + RID_CAVE_VOICE_11, + "Leben sie noch in den Sternen? In den Tiefen des " + "Ozeans? Im Wind?" + }, + { + RID_CAVE_VOICE_12, + "Wir fragen uns - war ihr Schicksal gut oder b\224se?" + }, + { + RID_CAVE_VOICE_13, + "Und wird uns eines Tages das gleiche Schicksal " + "ereilen?" + } } + }; + + int n_dialogues = ARRAYSIZE(dialogue[lang]); + + 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; + + q_event = _vm->_events->queue(&event); + + // Begin palette cycling animation for candles + event.type = kEvTOneshot; + event.code = kPalAnimEvent; + event.op = kEventCycleStart; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + // Queue narrator dialogue list + q_event = ITEQueueDialogue(q_event, n_dialogues, dialogue[lang]); + + // End scene after last dialogue over + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = VOICE_PAD; + + q_event = _vm->_events->chain(q_event, &event); + break; + case SCENE_END: + break; + default: + warning("Illegal scene procedure paramater"); + break; + } + + return 0; +} + +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; + Event *q_event; + + static const IntroCredit credits[] = { + {EN_USA, kITEAny, kCHeader, "Producer"}, + {DE_DEU, kITEAny, kCHeader, "Produzent"}, + {UNK_LANG, kITEAny, kCText, "Walter Hochbrueckner"}, + {EN_USA, kITEAny, kCHeader, "Executive Producer"}, + {DE_DEU, kITEAny, kCHeader, "Ausf\201hrender Produzent"}, + {UNK_LANG, kITEAny, kCText, "Robert McNally"}, + {UNK_LANG, kITEWyrmKeep, kCHeader, "2nd Executive Producer"}, + {EN_USA, kITENotWyrmKeep, kCHeader, "Publisher"}, + {DE_DEU, kITENotWyrmKeep, kCHeader, "Herausgeber"}, + {UNK_LANG, kITEAny, kCText, "Jon Van Caneghem"} + }; + + int n_credits = ARRAYSIZE(credits); + + 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; + + q_event = _vm->_events->queue(&event); + + // Begin ITE title theme music + _vm->_music->stop(); + + event.type = kEvTOneshot; + event.code = kMusicEvent; + event.param = MUSIC_2; + event.param2 = MUSIC_NORMAL; + event.op = kEventPlay; + event.time = 0; + + q_event = _vm->_events->chain(q_event, &event); + + // Pause animation before logo + event.type = kEvTOneshot; + event.code = kAnimEvent; + event.op = kEventStop; + event.param = 0; + event.time = 3000; + + q_event = _vm->_events->chain(q_event, &event); + + // Display logo + event.type = kEvTContinuous; + event.code = kTransitionEvent; + event.op = kEventDissolveBGMask; + event.time = 0; + event.duration = LOGO_DISSOLVE_DURATION; + + q_event = _vm->_events->chain(q_event, &event); + + // Remove logo + event.type = kEvTContinuous; + event.code = kTransitionEvent; + event.op = kEventDissolve; + event.time = 3000; + event.duration = LOGO_DISSOLVE_DURATION; + + q_event = _vm->_events->chain(q_event, &event); + + // Unpause animation before logo + event.type = kEvTOneshot; + event.code = kAnimEvent; + event.op = kEventPlay; + event.time = 0; + event.param = 0; + + q_event = _vm->_events->chain(q_event, &event); + + // Queue game credits list + q_event = ITEQueueCredits(9000, CREDIT_DURATION1, n_credits, credits); + + // End scene after credit display + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = 1000; + + q_event = _vm->_events->chain(q_event, &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; + Event *q_event; + + static const IntroCredit credits1[] = { + {EN_USA, kITEAny, kCHeader, "Game Design"}, + {DE_DEU, kITEAny, kCHeader, "Spielentwurf"}, + {UNK_LANG, kITEAny, kCText, "Talin, Joe Pearce, Robert McNally"}, + {EN_USA, kITEAny, kCText, "and Carolly Hauksdottir"}, + {DE_DEU, kITEAny, kCText, "und Carolly Hauksdottir"}, + {EN_USA, kITEAny, kCHeader, "Screenplay and Dialog"}, + {EN_USA, kITEAny, kCText, "Robert Leh, Len Wein, and Bill Rotsler"}, + {DE_DEU, kITEAny, kCHeader, "Geschichte und Dialoge"}, + {DE_DEU, kITEAny, kCText, "Robert Leh, Len Wein und Bill Rotsler"} + }; + + int n_credits1 = ARRAYSIZE(credits1); + + static const IntroCredit credits2[] = { + {UNK_LANG, kITEWyrmKeep, kCHeader, "Art Direction"}, + {UNK_LANG, kITEWyrmKeep, kCText, "Allison Hershey"}, + {EN_USA, kITEAny, kCHeader, "Art"}, + {DE_DEU, kITEAny, kCHeader, "Grafiken"}, + {UNK_LANG, kITEWyrmKeep, kCText, "Ed Lacabanne, Glenn Price, April Lee,"}, + {UNK_LANG, kITENotWyrmKeep, kCText, "Edward Lacabanne, Glenn Price, April Lee,"}, + {UNK_LANG, kITEWyrmKeep, kCText, "Lisa Sample, Brian Dowrick, Reed Waller,"}, + {EN_USA, kITEWyrmKeep, kCText, "Allison Hershey and Talin"}, + {DE_DEU, kITEWyrmKeep, kCText, "Allison Hershey und Talin"}, + {EN_USA, kITENotWyrmKeep, kCText, "Lisa Iennaco, Brian Dowrick, Reed"}, + {EN_USA, kITENotWyrmKeep, kCText, "Waller, Allison Hershey and Talin"}, + {DE_DEU, kITEAny, kCText, "Waller, Allison Hershey und Talin"}, + {EN_USA, kITENotWyrmKeep, kCHeader, "Art Direction"}, + {DE_DEU, kITENotWyrmKeep, kCHeader, "Grafische Leitung"}, + {UNK_LANG, kITENotWyrmKeep, kCText, "Allison Hershey"} + }; + + int n_credits2 = ARRAYSIZE(credits2); + + 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; + + q_event = _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; + + q_event = _vm->_events->chain(q_event, &event); + } + + // Queue game credits list + q_event = ITEQueueCredits(DISSOLVE_DURATION + 2000, CREDIT_DURATION1, n_credits1, credits1); + q_event = ITEQueueCredits(DISSOLVE_DURATION + 7000, CREDIT_DURATION1, n_credits2, credits2); + + // End scene after credit display + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = 1000; + + q_event = _vm->_events->chain(q_event, &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; + Event *q_event; + + static const IntroCredit credits1[] = { + {EN_USA, kITEAny, kCHeader, "Programming"}, + {DE_DEU, kITEAny, kCHeader, "Programmiert von"}, + {UNK_LANG, kITEAny, kCText, "Talin, Walter Hochbrueckner,"}, + {EN_USA, kITEAny, kCText, "Joe Burks and Robert Wiggins"}, + {DE_DEU, kITEAny, kCText, "Joe Burks und Robert Wiggins"}, + {EN_USA, kITEPCCD | kITEWyrmKeep, kCHeader, "Additional Programming"}, + {EN_USA, kITEPCCD | kITEWyrmKeep, kCText, "John Bolton"}, + {UNK_LANG, kITEMac, kCHeader, "Macintosh Version"}, + {UNK_LANG, kITEMac, kCText, "Michael McNally and Robert McNally"}, + {EN_USA, kITEAny, kCHeader, "Music and Sound"}, + {DE_DEU, kITEAny, kCHeader, "Musik und Sound"}, + {UNK_LANG, kITEAny, kCText, "Matt Nathan"} + }; + + int n_credits1 = ARRAYSIZE(credits1); + + static const IntroCredit credits2[] = { + {EN_USA, kITEAny, kCHeader, "Directed by"}, + {DE_DEU, kITEAny, kCHeader, "Regie"}, + {UNK_LANG, kITEAny, kCText, "Talin"} + }; + + int n_credits2 = ARRAYSIZE(credits2); + + 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; + + q_event = _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; + + q_event = _vm->_events->chain(q_event, &event); + + // Queue game credits list + q_event = ITEQueueCredits(DISSOLVE_DURATION + 2000, CREDIT_DURATION1, n_credits1, credits1); + q_event = ITEQueueCredits(DISSOLVE_DURATION + 7000, CREDIT_DURATION1, n_credits2, credits2); + + // End scene after credit display + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = 1000; + + q_event = _vm->_events->chain(q_event, &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; + Event *q_event; + Event *q_event_start; + + 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; + + q_event_start = _vm->_events->queue(&event); + + // End scene after momentary pause + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = 5000; + q_event = _vm->_events->chain(q_event_start, &event); + break; + case SCENE_END: + break; + default: + warning("Illegal scene procedure parameter"); + break; + } + + return 0; +} + +} // End of namespace Saga diff --git a/engines/saga/itedata.cpp b/engines/saga/itedata.cpp new file mode 100644 index 0000000000..3329ab1bbd --- /dev/null +++ b/engines/saga/itedata.cpp @@ -0,0 +1,484 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Actor and Object data tables +#include "saga/saga.h" +#include "saga/itedata.h" +#include "saga/resnames.h" +#include "saga/sndres.h" + +namespace Saga { + +ActorTableData ITE_ActorTable[ITE_ACTORCOUNT] = { + // Original used so called permanent actors for first three and that was designed by + // EXTENDED object flag. They contained frames in more than one resource. We use + // different technique here see "Appending to sprite list" in loadActorResources() + +// flags name scene x y z spr frm scp col +// ------------ ---- ---- ---- ----- ---- ---- ---- --- ---- -- -- -- + { kProtagonist | kExtended, + 0, 1, 0, 0, 0, 37, 135, 0, 1, 0, 0, 0}, // map party + // spr and frm numbers taken from permanent actors list + { kFollower | kExtended, + 1, 0, 0, 0, 0, 45, 177, 1, 132, 0, 0, 0}, // Okk + { kFollower | kExtended, + 2, 0, 0, 0, 0, 48, 143, 2, 161, 0, 0, 0}, // Eeah + { 0, 3, 0, 240, 480, 0, 115, 206, 0, 25, 0, 0, 0}, // albino ferret + { 0, 4, 17, 368, 400, 0, 115, 206, 4, 49, 0, 0, 0}, // moneychanger + { 0, 5, 11, 552, 412, 0, 54, 152, 1, 171, 0, 0, 0}, // Sist + { 0, 17, 2, 1192, 888, 0, 57, 153, 17, 49, 0, 0, 0}, // worker ferret 1 + { 0, 17, 2, 816, 1052, 0, 57, 153, 18, 49, 0, 0, 0}, // worker ferret 2 + { 0, 17, 2, 928, 932, 0, 58, 153, 19, 49, 0, 0, 0}, // worker ferret 3 + { 0, 17, 2, 1416, 1160, 0, 58, 153, 20, 49, 0, 0, 0}, // worker ferret 4 + { 0, 19, 49, 1592, 1336, 0, 92, 175, 15, 162, 0, 0, 0}, // faire merchant 1 (bear) + { 0, 20, 49, 744, 824, 0, 63, 156, 19, 112, 0, 4, 4}, // faire merchant 2 (ferret) + { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire merchant 3 + { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire merchant 4 + { 0, 9, 49, 1560, 1624, 0, 94, 147, 18, 132, 0, 4, 4}, // faire goer 1a (rat) + { 0, 56, 49, 1384, 792, 0, 95, 193, 20, 72, 0, 0, 0}, // faire goer 1b (otter) + { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 2a + { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 2b + { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 3a + { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 3b + { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 4a + { 0, 19, 0, 1592, 1336, 0, 92, 175, 0, 171, 0, 0, 0}, // faire goer 4b + { 0, 18, 32, 764, 448, 0, 55, 150, 0, 48, 10, 4, 4}, // Scorry + { 0, 35, 32, 0, 0, 0, 56, 151, 0, 112, 0, 0, 0}, // grand puzzler + { 0, 36, 32, 0, 0, 0, 105, 142, 0, 155, 0, 0, 0}, // Rhene + { 0, 32, 32, 0, 0, 0, 91, 190, 0, 98, 0, 0, 0}, // elk captain + { 0, 31, 32, 0, 0, 0, 90, 189, 0, 171, 0, 0, 0}, // elk guard 1 + { 0, 31, 32, 0, 0, 0, 90, 189, 0, 171, 0, 0, 0}, // elk guard 2 + { 0, 31, 32, 0, 0, 0, 90, 189, 0, 171, 0, 0, 0}, // elk guard 3 + { 0, 31, 32, 0, 0, 0, 79, 172, 0, 18, 0, 0, 0}, // boar sergeant + { 0, 21, 50, 664, 400, 0, 76, 171, 2, 74, 0, 4, 4}, // boar sentry 1 + { 0, 21, 50, 892, 428, 0, 76, 171, 2, 74, 0, 4, 4}, // boar sentry 2 + { 0, 9, 51, 904, 936, 0, 51, 145, 35, 5, 0, 0, 0}, // hall rat 1 + { 0, 9, 51, 872, 840, 0, 51, 145, 36, 5, 0, 0, 0}, // hall rat 2 + { 0, 9, 51, 1432, 344, 0, 51, 145, 37, 5, 0, 0, 0}, // hall rat 3 + { 0, 9, 51, 664, 472, 0, 51, 145, 38, 5, 0, 0, 0}, // hall rat 4 + { 0, 10, 51, 1368, 1464, 0, 80, 146, 39, 147, 0, 0, 0}, // book rat 1 + { 0, 10, 51, 1416, 1624, 0, 80, 146, 40, 147, 0, 0, 0}, // book rat 2 + { 0, 10, 51, 1752, 120, 0, 80, 146, 41, 147, 0, 0, 0}, // book rat 3 + { 0, 10, 51, 984, 408, 0, 80, 146, 42, 147, 0, 0, 0}, // book rat 4 + { 0, 14, 52, 856, 376, 0, 82, 174, 8, 73, 0, 0, 0}, // grounds servant 1 + { 0, 14, 52, 808, 664, 0, 82, 174, 9, 73, 0, 0, 0}, // grounds servant 2 + { 0, 14, 52, 440, 568, 0, 82, 174, 10, 73, 0, 0, 0}, // grounds servant 3 + { 0, 14, 52, 392, 776, 0, 82, 174, 11, 73, 0, 0, 0}, // grounds servant 4 + { 0, 21, 4, 240, 384, 0, 79, 172, 0, 18, 0, 2, 2}, // boar sentry 3 (by doorway) + { 0, 23, 4, 636, 268, 0, 77, 173, 0, 74, 0, 4, 4}, // boar courtier + { 0, 22, 4, 900, 320, 0, 78, 179, 0, 60, 0, 4, 4}, // boar king + { 0, 14, 4, 788, 264, 0, 75, 170, 0, 171, 0, 2, 2}, // boar servant 1 + { 0, 14, 4, 1088, 264, 0, 75, 170, 0, 171, 0, 6, 6}, // boar servant 2 + { 0, 24, 19, 728, 396, 0, 65, 181, 47, 146, 0, 6, 6}, // glass master + { 0, 24, 21, -20, -20, 0, 66, 182, 0, 146, 0, 4, 4}, // glass master (with orb) + { kCycle, 25, 19, 372, 464, 0, 67, 183, 73, 146, 0, 2, 2}, // glass worker + { 0, 26, 5, 564, 476, 27, 53, 149, 1, 5, 0, 4, 4}, // door rat + { kCycle, 27, 31, 868, 344, 0, 81, 180, 0, 171, 0, 4, 4}, // bees + { 0, 28, 73, 568, 380, 0, 83, 176, 30, 120, 0, 4, 4}, // fortune teller + { 0, 14, 7, 808, 480, 0, 82, 174, 9, 73, 0, 0, 0}, // orb messenger + { 0, 29, 10, 508, 432, 0, 84, 186, 6, 112, 0, 4, 4}, // elk king + { 0, 33, 10, 676, 420, 0, 86, 184, 6, 171, 0, 4, 4}, // elk chancellor + { 0, 30, 10, 388, 452, 0, 88, 185, 6, 171, 0, 4, 4}, // elk courtier 1 + { 0, 30, 10, 608, 444, 0, 89, 185, 6, 171, 0, 4, 4}, // elk courtier 2 + { 0, 31, 10, 192, 468, 0, 90, 189, 6, 171, 0, 4, 4}, // elk throne guard 1 + { 0, 31, 10, 772, 432, 0, 90, 189, 6, 171, 0, 4, 4}, // elk throne guard 2 + { 0, 14, 10, 1340, 444, 0, 87, 188, 6, 171, 0, 4, 4}, // elk servant + { 0, 20, 18, 808, 360, 7, 60, 154, 64, 88, 0, 4, 4}, // hardware ferret + { 0, 34, 49, 1128, 1256, 0, 96, 191, 16, 35, 0, 4, 4}, // porcupine + { 0, 34, 49, 1384, 792, 0, 93, 192, 17, 66, 0, 4, 4}, // faire ram + { 0, 24, 21, 0, -40, 0, 65, 181, 50, 146, 0, 6, 6}, // glass master 2 + { 0, 3, 21, 0, -40, 0, 64, 158, 49, 112, 0, 0, 0}, // Sakka + { 0, 17, 21, 0, -40, 0, 62, 157, 74, 48, 0, 0, 0}, // lodge ferret 1 + { 0, 17, 21, 0, -40, 0, 62, 157, 74, 49, 0, 0, 0}, // lodge ferret 2 + { 0, 17, 21, 0, -40, 0, 62, 157, 74, 50, 0, 0, 0}, // lodge ferret 3 + { 0, 12, 244, 1056, 504, 0, 107, 167, 21, 124, 0, 6, 6}, // Elara + { 0, 8, 33, 248, 440, 0, 68, 169, 14, 112, 0, 0, 0}, // Tycho + { 0, 11, 23, 308, 424, 0, 106, 166, 6, 48, 0, 2, 2}, // Alamma + { 0, 17, 2, 1864, 1336, 0, 58, 153, 21, 49, 0, 0, 0}, // worker ferret 5 + { 0, 17, 2, 760, 216, 0, 58, 153, 22, 49, 0, 0, 0}, // worker ferret 6 + { 0, 44, 29, 0, 0, 0, 72, 159, 0, 112, 0, 0, 0}, // Prince + { 0, 45, 29, 0, 0, 0, 71, 163, 0, 146, 0, 6, 6}, // harem girl 1 + { 0, 45, 29, 0, 0, 0, 71, 163, 0, 124, 0, 2, 2}, // harem girl 2 + { 0, 45, 29, 0, 0, 0, 71, 163, 0, 169, 0, 0, 0}, // harem girl 3 + { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // dog sergeant + { 0, 7, 29, 0, 0, 0, 70, 165, 0, 4, 0, 0, 0}, // throne dog guard 1 + { 0, 7, 257, 552, 408, 0, 70, 165, 0, 4, 0, 2, 2}, // throne dog guard 2 + { 0, 7, 29, 0, 0, 0, 70, 165, 0, 4, 0, 0, 0}, // throne dog guard 3 + { 0, 7, 29, 0, 0, 0, 70, 165, 0, 4, 0, 0, 0}, // throne dog guard 4 + { 0, 7, 257, 712, 380, 0, 69, 164, 0, 4, 0, 4, 4}, // throne dog guard 5 + { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 6 + { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 7 + { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 8 + { 0, 7, 29, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 9 + { 0, 7, 0, 0, 0, 0, 69, 164, 0, 4, 0, 0, 0}, // throne dog guard 10 + { 0, 7, 29, 0, 0, 0, 70, 165, 0, 4, 0, 0, 0}, // throne dog guard 11 + { 0, 47, 30, 0, 0, 0, 102, 199, 1, 186, 0, 0, 0}, // old wolf ferryman + { 0, 48, 69, 0, 0, 0, 109, 202, 35, 26, 0, 0, 0}, // cat village wildcat + { 0, 49, 69, 0, 0, 0, 109, 202, 35, 26, 0, 0, 0}, // cat village attendant + { 0, 50, 69, 0, 0, 0, 111, 203, 16, 67, 0, 0, 0}, // cat village Prowwa + { 0, 51, 20, 0, 0, 0, 112, 204, 15, 26, 0, 0, 0}, // Prowwa hut Mirrhp + { 0, 50, 20, 0, 0, 0, 111, 203, 14, 67, 0, 0, 0}, // Prowwa hut Prowwa + { 0, 49, 20, 0, 0, 0, 109, 202, 35, 26, 0, 0, 0}, // Prowwa hut attendant + { 0, 48, 256, 0, 0, 0, 109, 202, 35, 26, 0, 0, 0}, // wildcat sentry + { 0, 21, 32, 0, 0, 0, 76, 171, 0, 171, 0, 0, 0}, // boar warrior 1 + { 0, 21, 32, 0, 0, 0, 76, 171, 0, 171, 0, 0, 0}, // boar warrior 2 + { 0, 21, 32, 0, 0, 0, 76, 171, 0, 171, 0, 0, 0}, // boar warrior 3 + { 0, 52, 15, 152, 400, 0, 108, 168, 19, 48, 10, 2, 2}, // Alamma's voice + { 0, 47, 251, 640, 360, 0, 113, 205, 5, 186, 10, 2, 2}, // ferry on ocean + { 0, 41, 75, 152, 400, 0, 100, 197, 5, 81, 0, 0, 0}, // Shiala + { 0, 44, 9, 0, 0, 0, 73, 160, 54, 112, 0, 0, 0}, // Prince (asleep) + { 0, 0, 22, -20, -20, 0, 118, 209, 0, 171, 0, 0, 0}, // Rif and Eeah (at rockslide) + { 0, 1, 22, 0, 0, 0, 119, 210, 0, 171, 0, 0, 0}, // Okk (at rockslide) + { 0, 0, 22, -20, -20, 0, 118, 209, 0, 171, 0, 0, 0}, // Rif and Eeah (at rockslide w. rope) + { 0, 1, 22, 0, 0, 0, 119, 210, 0, 171, 0, 0, 0}, // Okk (at rockslide w. rope) + { 0, 53, 42, 640, 400, 0, 104, 201, 8, 141, 0, 0, 0}, // Kylas Honeyfoot + { 0, 54, 21, -20, -20, 0, 120, 211, 48, 238, 0, 0, 0}, // Orb of Hands + { 0, 0, 4, -20, -20, 0, 42, 140, 0, 1, 0, 0, 0}, // Rif (muddy) + { 0, 26, 5, -20, -20, 27, 52, 148, 1, 5, 0, 4, 4}, // door rat (standing) + { 0, 36, 4, -20, -20, 0, 116, 207, 0, 155, 0, 0, 0}, // boar with Rhene 1 + { 0, 36, 0, -20, -20, 0, 117, 208, 0, 155, 0, 0, 0}, // boar with Rhene 2 + { 0, 46, 252, -20, -20, 0, 74, 162, 29, 34, 0, 0, 0}, // dog jailer + { 0, 0, 32, -20, -20, 0, 41, 137, 0, 1, 0, 0, 0}, // Rif (tourney) + { 0, 0, 259, -20, -20, 0, 44, 138, 0, 1, 0, 0, 0}, // cliff rat + { 0, 0, 5, -20, -20, 0, 43, 139, 0, 1, 0, 0, 0}, // Rif (cloaked) + { 0, 0, 31, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (oak tree scene) + { 0, 0, 252, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (jail cell scene) + { 0, 0, 15, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (outside Alamma's) + { 0, 0, 20, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (sick tent) + { 0, 0, 25, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (gem room) + { 0, 0, 272, -20, -20, 0, 40, 141, 0, 1, 0, 0, 0}, // Rif (dragon maze) + { 0, 0, 50, -20, -20, 0, 39, 136, 0, 1, 0, 0, 0}, // Rif (boar entry gate) + { 0, 50, 71, -20, -20, 0, 111, 203, 0, 67, 0, 0, 0}, // Prowwa (dog castle back) + { 0, 50, 274, -20, -20, 0, 111, 203, 0, 67, 0, 0, 0}, // Prowwa (cat festival) + { 0, 50, 274, -20, -20, 0, 110, 212, 0, 171, 0, 0, 0}, // cat festival dancer 1 + { 0, 50, 274, -20, -20, 0, 110, 212, 0, 171, 0, 0, 0}, // cat festival dancer 2 + { 0, 50, 274, -20, -20, 0, 110, 212, 0, 171, 0, 0, 0}, // cat festival dancer 3 + { 0, 57, 272, 909, 909, 48, 121, 213, 0, 171, 0, 0, 0}, // komodo dragon + { 0, 58, 15, -20, -20, 0, 122, 214, 0, 171, 0, 0, 0}, // letter from Elara + { 0, 37, 246, -20, -20, 0, 97, 194, 0, 141, 0, 0, 0}, // Gar (wolves' cage) + { 0, 38, 246, -20, -20, 0, 98, 195, 0, 27, 0, 0, 0}, // Wrah (wolves' cage) + { 0, 59, 246, -20, -20, 0, 103, 200, 0, 26, 0, 0, 0}, // Chota (wolves' cage) + { 0, 41, 245, -20, -20, 0, 100, 197, 0, 81, 0, 0, 0}, // Shiala (wolves' cage) + { 0, 47, 250, 640, 360, 0, 114, 205, 0, 186, 10, 2, 2}, // ferry on ocean + { 0, 0, 278, -20, -20, 0, 40, 141, 0, 1, 0, 0, 0}, // Rif (falling in tunnel trap door) + { 0, 0, 272, -20, -20, 0, 40, 141, 0, 1, 0, 0, 0}, // Rif (falling in dragon maze) + { 0, 41, 77, -20, -20, 0, 100, 197, 24, 81, 0, 0, 0}, // Shiala (grotto) + { 0, 37, 261, -20, -20, 0, 97, 194, 0, 141, 0, 0, 0}, // Gar (ambush) + { 0, 38, 261, -20, -20, 0, 98, 195, 0, 27, 0, 0, 0}, // Wrah (ambush) + { 0, 39, 261, -20, -20, 0, 99, 196, 0, 5, 0, 0, 0}, // dark claw wolf (ambush) + { 0, 39, 261, -20, -20, 0, 99, 196, 0, 5, 0, 0, 0}, // dark claw wolf (ambush) + { 0, 39, 261, -20, -20, 0, 99, 196, 0, 5, 0, 0, 0}, // dark claw wolf (ambush) + { 0, 39, 261, -20, -20, 0, 99, 196, 0, 5, 0, 0, 0}, // dark claw wolf (ambush) + { 0, 59, 279, -20, -20, 0, 103, 200, 0, 26, 0, 0, 0}, // Chota (top of dam) + { 0, 38, 279, -20, -20, 0, 98, 195, 0, 27, 0, 0, 0}, // Wrah (top of dam) + { 0, 42, 77, -20, -20, 0, 101, 198, 25, 171, 0, 0, 0}, // Shiala's spear + { 0, 59, 281, -20, -20, 0, 103, 200, 26, 26, 0, 0, 0}, // Chota (lab) + { 0, 59, 279, -20, -20, 0, 123, 215, 0, 1, 0, 0, 0}, // Rif (finale) + { 0, 59, 279, -20, -20, 0, 123, 215, 0, 132, 0, 0, 0}, // Okk (finale) + { 0, 59, 279, -20, -20, 0, 123, 215, 0, 161, 0, 0, 0}, // Eeah (finale) + { 0, 54, 279, -20, -20, 0, 120, 211, 0, 133, 0, 6, 6}, // Orb of Storms (top of dam) + { 0, 44, 9, -20, -20, 0, 124, 161, 0, 171, 0, 6, 6}, // Prince's snores + { 0, 7, 255, 588, 252, 0, 70, 165, 0, 3, 0, 2, 2}, // hall dog guard 1 + { 0, 7, 255, 696, 252, 0, 70, 165, 0, 5, 0, 6, 6}, // hall dog guard 2 + { 0, 36, 4, 0, 0, 0, 105, 142, 0, 155, 0, 0, 0}, // Rhene + { 0, 44, 272, 1124, 1124, 120, 72, 159, 0, 112, 0, 0, 0}, // Prince (dragon maze) + { 0, 7, 272, 1124, 1108, 120, 70, 165, 0, 4, 0, 0, 0}, // dog heckler 1 (dragon maze) + { 0, 7, 272, 1108, 1124, 120, 70, 165, 0, 4, 0, 0, 0}, // dog heckler 2 (dragon maze) + { 0, 29, 288, 508, 432, 0, 85, 187, 0, 112, 0, 4, 4}, // elk king (finale) + { 0, 29, 0, 508, 432, 0, 84, 186, 0, 99, 0, 4, 4}, // crowd voice 1 (finale) + { 0, 29, 0, 508, 432, 0, 84, 186, 0, 98, 0, 4, 4}, // crowd voice 2 (finale) + { 0, 29, 0, 508, 432, 0, 84, 186, 0, 104, 0, 4, 4}, // crowd voice 3 (finale) + { 0, 29, 0, 508, 432, 0, 84, 186, 0, 99, 0, 4, 4}, // crowd voice 4 (finale) + { 0, 36, 288, 0, 0, 0, 105, 142, 0, 155, 0, 0, 0}, // Rhene (finale) + { 0, 1, 27, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (temple gate) + { 0, 1, 252, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (jail cell) + { 0, 1, 25, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (gem room) + { 0, 1, 259, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (cliff) + { 0, 1, 279, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (dam top) + { 0, 1, 273, -20, -20, 0, 47, 178, 0, 132, 0, 0, 0}, // Okk (human ruins) + { 0, 1, 26, -20, -20, 0, 8, 178, 0, 171, 0, 0, 0}, // puzzle pieces + { 0, 1, 0, -20, -20, 0, 0, 0, 0, 50, 0, 0, 0}, // poker dog 1 + { 0, 1, 0, -20, -20, 0, 0, 0, 0, 82, 0, 0, 0}, // poker dog 2 + { 0, 1, 0, -20, -20, 0, 0, 0, 0, 35, 0, 0, 0}, // poker dog 3 + { 0, 9, 74, -20, -20, 0, 51, 145, 0, 5, 0, 0, 0} // sundial rat +}; + + +ObjectTableData ITE_ObjectTable[ITE_OBJECTCOUNT] = { + { 8, 49, 1256, 760, 0, 9, 5, kObjNotFlat }, // Magic Hat + { 9, 52, 1080, 1864, 0, 68, 4, kObjUseWith }, // Berries + { 10, 259, 744, 524, 0, 79, 42, kObjUseWith }, // Card Key + { 11, 0, 480, 480, 0, 69, 6, 0 }, // Foot Print + { 12, 0, 480, 480, 0, 13, 38, kObjUseWith }, // Power Cell + { 13, 28, 640, 412, 40, 14, 15, kObjUseWith }, // Digital Clock + { 14, 0, 480, 480, 0, 15, 41, kObjUseWith }, // Oil Lamp + { 15, 24, 868, 456, 35, 46, 13, kObjUseWith }, // Magnetic Key + { 16, 0, 480, 480, 0, 17, 7, kObjUseWith }, // Plaster + { 17, 249, 320, 476, 45, 18, 44, 0 }, // Trophy + { 18, 0, 480, 480, 0, 19, 20, 0 }, // Coins + { 19, 19, 600, 480, 0, 20, 8, 0 }, // Lens Fragments + { 20, 0, 1012, 568, 80, 44, 10, kObjUseWith }, // Key to jail cell + { 21, 0, 480, 480, 0, 22, 9, 0 }, // Remade lens + { 22, 0, 480, 480, 0, 23, 21, 0 }, // Tycho's Map + { 23, 0, 480, 480, 0, 24, 23, 0 }, // Silver Medallion + { 24, 0, 480, 480, 0, 25, 24, 0 }, // Mud in Fur + { 25, 0, 480, 480, 0, 26, 25, 0 }, // Gold Ring + { 27, 13, 1036, 572, 40, 47, 14, kObjUseWith }, // Screwdriver + { 28, 0, 480, 480, 0, 29, 26, 0 }, // Apple Token + { 29, 0, 480, 480, 0, 30, 22, kObjUseWith }, // Letter from Elara + { 30, 0, 164, 440, 0, 31, 16, kObjUseWith }, // Spoon + { 32, 0, 480, 480, 0, 33, 43, 0 }, // Catnip + { 33, 31, 580, 392, 0, 45, 11, 0 }, // Twigs + { 35, 0, 468, 480, 0, 36, 12, kObjUseWith }, // Empty Bowl (also bowl of honey) + { 37, 0, 480, 480, 0, 38, 45, kObjUseWith }, // Needle and Thread + { 38, 25, 332, 328, 0, 48, 19, 0 }, // Rock Crystal + { 39, 0, 480, 480, 0, 40, 0, kObjUseWith }, // Salve + { 40, 269, 644, 416, 0, 41, 39, kObjNotFlat }, // Electrical Cable + { 41, 12, 280, 516, 0, 43, 17, kObjUseWith }, // Piece of flint + { 42, 5, 876, 332, 32, 65, 18, 0 }, // Rat Cloak + { 43, 52, 556, 1612, 0, 49, 28, kObjUseWith | kObjNotFlat }, // Bucket + { 48, 52, 732, 948, 0, 50, 27, kObjUseWith }, // Cup + { 49, 52, 520, 1872, 0, 53, 29, 0 }, // Fertilizer + { 50, 52, 1012, 1268, 0, 52, 30, 0 }, // Feeder + { 51, 252, -20, -20, 0, 71, 32, kObjUseWith | kObjNotFlat }, // Bowl in jail cell + { 53, 252, 1148, 388, 0, 70, 33, 0 }, // Loose stone block in jail cell + { 26, 12, 496, 368, 0, 76, 31, 0 }, // Coil of Rope from Quarry + { 54, 281, 620, 352, 0, 80, 46, 0 } // Orb of Storms in Dam Lab +}; + +FxTable ITE_SfxTable[ITE_SFXCOUNT] = { + { FX_DOOR_OPEN, 127 }, + { FX_DOOR_CLOSE, 127 }, + { FX_RUSH_WATER, 63 }, // Floppy volume: 127 + { FX_RUSH_WATER, 26 }, // Floppy volume: 40 + { FX_CRICKET, 64 }, + { FX_PORTICULLIS, 84 }, // Floppy volume: 127 + { FX_CLOCK_1, 64 }, + { FX_CLOCK_2, 64 }, + { FX_DAM_MACHINE, 64 }, + { FX_DAM_MACHINE, 40 }, + { FX_HUM1, 64 }, + { FX_HUM2, 64 }, + { FX_HUM3, 64 }, + { FX_HUM4, 64 }, + { FX_WATER_LOOP_S, 32 }, // Floppy volume: 64 + { FX_SURF, 42 }, // Floppy volume: 127 + { FX_SURF, 32 }, // Floppy volume: 64 + { FX_FIRELOOP, 64 }, // Floppy volume: 96 + { FX_SCRAPING, 84 }, // Floppy volume: 127 + { FX_BEE_SWARM, 64 }, // Floppy volume: 96 + { FX_BEE_SWARM, 26 }, // Floppy volume: 40 + { FX_SQUEAKBOARD, 64 }, + { FX_KNOCK, 127 }, + { FX_COINS, 32 }, // Floppy volume: 48 + { FX_STORM, 84 }, // Floppy volume: 127 + { FX_DOOR_CLOSE_2, 84 }, // Floppy volume: 127 + { FX_ARCWELD, 84 }, // Floppy volume: 127 + { FX_RETRACT_ORB, 127 }, + { FX_DRAGON, 127 }, + { FX_SNORES, 127 }, + { FX_SPLASH, 127 }, + { FX_LOBBY_DOOR, 127 }, + { FX_CHIRP_LOOP, 26 }, // Floppy volume: 40 + { FX_DOOR_CREAK, 96 }, + { FX_SPOON_DIG, 64 }, + { FX_CROW, 96 }, + { FX_COLDWIND, 42 }, // Floppy volume: 64 + { FX_TOOL_SND_1, 96 }, + { FX_TOOL_SND_2, 127 }, + { FX_TOOL_SND_3, 64 }, + { FX_DOOR_METAL, 96 }, + { FX_WATER_LOOP_S, 32 }, + { FX_WATER_LOOP_L, 32 }, // Floppy volume: 64 + { FX_DOOR_OPEN_2, 127 }, + { FX_JAIL_DOOR, 64 }, + { FX_KILN_FIRE, 53 }, // Floppy volume: 80 + + // Only in the CD version + { FX_CROWD_01, 64 }, + { FX_CROWD_02, 64 }, + { FX_CROWD_03, 64 }, + { FX_CROWD_04, 64 }, + { FX_CROWD_05, 64 }, + { FX_CROWD_06, 64 }, + { FX_CROWD_07, 64 }, + { FX_CROWD_08, 64 }, + { FX_CROWD_09, 64 }, + { FX_CROWD_10, 64 }, + { FX_CROWD_11, 64 }, + { FX_CROWD_12, 64 }, + { FX_CROWD_13, 64 }, + { FX_CROWD_14, 64 }, + { FX_CROWD_15, 64 }, + { FX_CROWD_16, 64 }, + { FX_CROWD_17, 64 } +}; + +const char *ITEinterfaceTextStrings[][52] = { + { + "Walk to", "Look At", "Pick Up", "Talk to", "Open", + "Close", "Use", "Give", "Options", "Test", + "Demo", "Help", "Quit Game", "Fast", "Slow", + "On", "Off", "Continue Playing", "Load", "Save", + "Game Options", "Reading Speed", "Music", "Sound", "Cancel", + "Quit", "OK", "Mid", "Click", "10%", + "20%", "30%", "40%", "50%", "60%", + "70%", "80%", "90%", "Max", "Quit the Game?", + "Load Successful!", "Enter Save Game Name", "Give %s to %s", "Use %s with %s", + "[New Save Game]", + "I can't pick that up.", + "I see nothing special about it.", + "There's no place to open it.", + "There's no opening to close.", + "I don't know how to do that.", + "Show Dialog", + "What is Rif's reply?" + }, + // German + { + "Gehe zu", "Schau an", "Nimm", "Rede mit", "\231ffne", + "Schlie$e", "Benutze", "Gib", "Optionen", "Test", + "Demo", "Hilfe", "Spiel beenden", "S", "L", + "An", "Aus", "Weiterspielen", "Laden", "Sichern", + "Spieleoptionen", "Lesegeschw.", "Musik", "Sound", "Abbr.", + "Beenden", NULL, "M", "Klick", "10%", + "20%", "30%", "40%", "50%", "60%", + "70%", "80%", "90%", "Max", "Spiel beenden?", + "Spielstand geladen!", "Bitte Namen eingeben", "Gib %s zu %s", "Benutze %s mit %s", + "[Neuer Spielstand]", + "Das kann ich nicht aufnehmen.", + "Ich sehe nichts besonderes.", + "Das kann man nicht \224ffnen.", + "Hier ist keine \231ffnung zum Schlie$en.", + "Ich wei$ nicht, wie ich das machen soll.", + "Text zeigen", + "Wie lautet die Antwort?" + } +}; + +Point pieceOrigins[PUZZLE_PIECES] = { + Point(268, 18), + Point(270, 51), + Point( 19, 51), + Point( 73, 0), + Point( 0, 34), + Point(215, 0), + Point(159, 0), + Point( 9, 69), + Point(288, 18), + Point(112, 0), + Point( 27, 88), + Point( 43, 0), + Point( 0, 0), + Point(262, 0), + Point(271, 103) +}; + +const char *pieceNames[][PUZZLE_PIECES] = { + { "screwdriver", "pliers", "c-clamp", "wood clamp", "level", + "twine", "wood plane", "claw hammer", "tape measure", "hatchet", + "shears", "ruler", "saw", "mallet", "paint brush" + }, + { "Schraubendreher", "Zange", "Schraubzwinge", "Holzzwinge", "Wasserwaage", + "Bindfaden", "Hobel", "Schusterhammer", "Bandma$", "Beil", + "Schere", "Winkel", "S\204ge", "Hammer", "Pinsel" + } +}; + +const char *hintStr[][4] = { + { "Check which pieces could fit in each corner first.", + "Check which corner has the least number of pieces that can fit and start from there.", + "Check each new corner and any new side for pieces that fit.", + "I don't see anything out of place." + }, + { "\232berpr\201fe zun\204chst, welche die Eckteile sein k\224nnten.", + "Schau, in welche Ecke die wenigsten Teile passen, und fang dort an.", + "Untersuche jede Ecke und jede Seite auf Teile, die dort passen k\224nnen.", + "Ich sehe nichts an der falschen Stelle." + } +}; + +const char *solicitStr[][NUM_SOLICIT_REPLIES] = { + { "Hey, Fox! Would you like a hint?", + "Would you like some help?", + "Umm...Umm...", + "Psst! want a hint?", + "I would have done this differently, you know." + }, + { "Hey, Fuchs! Brauchst Du \047nen Tip?", + "M\224chtest Du etwas Hilfe?" + "\231hm...\216hm..." + "Psst! \047n Tip vielleicht?" + "Ja, wei$t Du... ich h\204tte das anders gemacht." + } +}; + +const char portraitList[] = { + RID_ITE_JFERRET_SERIOUS, + RID_ITE_JFERRET_GOOFY, + RID_ITE_JFERRET_SERIOUS, + RID_ITE_JFERRET_GOOFY, + RID_ITE_JFERRET_ALOOF +}; + +const char *sakkaStr[][NUM_SAKKA] = { + { "Hey, you're not supposed to help the applicants!", + "Guys! This is supposed to be a test!", + "C'mon fellows, that's not in the rules!" + }, + { "Hey, Du darfst dem Pr\201fling nicht helfen!", + "Hallo?! Dies soll eine Pr\201fung sein!", + "Also, Jungs. Schummeln steht nicht in den Regeln!" + } +}; + +const char *whineStr[][NUM_WHINES] = { + { "Aww, c'mon Sakka!", + "One hint won't hurt, will it?", + "Sigh...", + "I think that clipboard has gone to your head, Sakka!", + "Well, I don't recall any specific rule against hinting." + }, + { "Och, sei nicht so, Sakka!" + "EIN Tip wird schon nicht schaden, oder?", + "Seufz..." + "Ich glaube, Du hast ein Brett vor dem Kopf, Sakka!", + "Hm, ich kann mich an keine Regel erinnern, die Tips verbietet." + } +}; + +const char *optionsStr[][4] = { + { "\"I'll do this puzzle later.\"", + "\"Yes, I'd like a hint please.\"", + "\"No, thank you, I'd like to try and solve it myself.\"", + "I think the %s is in the wrong place." + }, + { "\"Ich l\224se das Puzzle sp\204ter.\"", + "\"Ja, ich m\224chte einen Tip, bitte.\"", + "\"Nein danke, ich m\224chte das alleine l\224sen.\"", + "Pssst... %s... falsche Stelle..." + } +}; + +} // End of namespace Saga diff --git a/engines/saga/itedata.h b/engines/saga/itedata.h new file mode 100644 index 0000000000..45d045356d --- /dev/null +++ b/engines/saga/itedata.h @@ -0,0 +1,113 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Actor data table header file + +#ifndef SAGA_ITEDATA_H +#define SAGA_ITEDATA_H + +namespace Saga { + +enum ActorFlags { + kProtagonist = 0x01, // Actor is protagonist + kFollower = 0x02, // Actor is follower + kCycle = 0x04, // Actor stand has a cycle + kFaster = 0x08, // Actor is fast + kFastest = 0x10, // Actor is faster + kExtended = 0x20 // Actor uses extended sprites +}; + +// TODO: This doesn't quite correspond to the original Actor struct, so I'm not +// sure if I got it right. + +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]; + +enum { + kObjUseWith = 0x01, + kObjNotFlat = 0x02 +}; + +struct ObjectTableData { + byte nameIndex; + int32 sceneIndex; + int16 x; + int16 y; + int16 z; + int32 spriteListResourceId; + byte scriptEntrypointNumber; + uint16 interactBits; +}; + +struct FxTable { + int res; + int vol; +}; + +#define ITE_OBJECTCOUNT 39 +#define ITE_SFXCOUNT 63 + +extern ObjectTableData ITE_ObjectTable[ITE_OBJECTCOUNT]; +extern FxTable ITE_SfxTable[ITE_SFXCOUNT]; + +extern const char *ITEinterfaceTextStrings[][52]; + +#define PUZZLE_PIECES 15 + +extern Point 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]; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/list.h b/engines/saga/list.h new file mode 100644 index 0000000000..d002f405de --- /dev/null +++ b/engines/saga/list.h @@ -0,0 +1,156 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SAGA_LIST_H__ +#define SAGA_LIST_H__ + +#include "common/list.h" + +namespace Saga { + +template <class T> +class SortedList : public Common::List<T> { +public: + typedef int (*CompareFunction) (const T& a, const T& b); + + typedef typename Common::List<T>::iterator iterator; + typedef typename Common::List<T>::const_iterator const_iterator; + typedef typename Common::List<T> Common_List; + +public: + + iterator pushFront(const T& element) { + return insert(Common::List<T>::begin(), element); + } + + iterator pushBack(const T& element) { + return insert(Common_List::end(), element); + } + + iterator insert(iterator pos, const T& element) { + Common_List::insert(pos, element); + return --pos; + } + + iterator pushFront() { + return insert(Common_List::begin()); + } + + iterator pushBack() { + return insert(Common_List::end()); + } + + iterator insert(iterator pos) { + T init; + return insert(pos, init); + } + + iterator pushFront(const T& element, CompareFunction compareFunction) { + return insert(Common::List<T>::begin(), element, compareFunction); + } + + iterator pushBack(const T& element, CompareFunction compareFunction) { + return insert(Common_List::end(), element, compareFunction); + } + + iterator insert(iterator pos, const T& element, CompareFunction compareFunction) { + int res; + + for (iterator i = Common_List::begin(); i != Common_List::end(); ++i) { + res = compareFunction(element, i.operator*()); + if (res < 0) { + return insert(i, element); + } + } + return pushBack(element); + } + + iterator reorderUp(iterator pos, CompareFunction compareFunction) { + iterator i(pos); + int res; + + --i; + while (i != Common::List<T>::end()) { + res = compareFunction(i.operator*(), pos.operator*()); + if (res <= 0) { + + T temp(*pos); + erase(pos); + ++i; + return insert(i, temp); + } + --i; + } + return pos; + } + + iterator reorderDown(iterator pos, CompareFunction compareFunction) { + iterator i(pos); + int res; + + ++i; + while (i != Common::List<T>::end()) { + res = compareFunction(i.operator*(), pos.operator*()); + if (res >= 0) { + + T temp(*pos); + erase(pos); + return insert(i, temp); + } + ++i; + } + return pos; + } + + iterator eraseAndPrev(iterator pos) { + assert(pos != Common_List::end()); + iterator res(pos); + + --res; + erase(pos); + return res; + } + + void remove(const T* val) { + for (iterator i = Common_List::begin(); i != Common_List::end(); ++i) + if (val == i.operator->()) { + erase(i); + return; + } + } + + bool locate(const T* val, iterator& foundedIterator) { + + for (iterator i = Common::List<T>::begin(); i != Common::List<T>::end(); ++i) + if (val == i.operator->()) + { + foundedIterator = i; + return true; + } + + return false; + } +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/module.mk b/engines/saga/module.mk new file mode 100644 index 0000000000..3d39108444 --- /dev/null +++ b/engines/saga/module.mk @@ -0,0 +1,44 @@ +MODULE := engines/saga + +MODULE_OBJS := \ + engines/saga/actor.o \ + engines/saga/animation.o \ + engines/saga/console.o \ + engines/saga/events.o \ + engines/saga/font.o \ + engines/saga/font_map.o \ + engines/saga/game.o \ + engines/saga/gfx.o \ + engines/saga/ihnm_introproc.o \ + engines/saga/image.o \ + engines/saga/interface.o \ + engines/saga/isomap.o \ + engines/saga/ite_introproc.o \ + engines/saga/itedata.o \ + engines/saga/objectmap.o \ + engines/saga/puzzle.o \ + engines/saga/palanim.o \ + engines/saga/render.o \ + engines/saga/rscfile.o \ + engines/saga/saga.o \ + engines/saga/saveload.o \ + engines/saga/scene.o \ + engines/saga/script.o \ + engines/saga/sfuncs.o \ + engines/saga/sndres.o \ + engines/saga/sprite.o \ + engines/saga/sthread.o \ + engines/saga/input.o \ + engines/saga/music.o \ + engines/saga/sound.o + +MODULE_DIRS += \ + engines/saga + +# This module can be built as a plugin +ifdef BUILD_PLUGINS +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/common.rules diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp new file mode 100644 index 0000000000..e352818ee6 --- /dev/null +++ b/engines/saga/music.cpp @@ -0,0 +1,524 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ +#include "saga/saga.h" + +#include "saga/rscfile.h" +#include "saga/music.h" +#include "saga/stream.h" +#include "sound/audiostream.h" +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "sound/mp3.h" +#include "sound/vorbis.h" +#include "sound/flac.h" +#include "common/config-manager.h" +#include "common/file.h" + +namespace Saga { + +#define BUFFER_SIZE 4096 + +struct TrackFormat { + DigitalTrackInfo* (*openTrackFunction)(int); +}; + +static const TrackFormat TRACK_FORMATS[] = { +#ifdef USE_FLAC + { getFlacTrack }, +#endif +#ifdef USE_VORBIS + { getVorbisTrack }, +#endif +#ifdef USE_MAD + { getMP3Track }, +#endif + + { NULL } // Terminator +}; + +// I haven't decided yet if it's a good idea to make looping part of the audio +// stream class, or if I should use a "wrapper" class, like I did for Broken +// Sword 2, to make it easier to add support for compressed music... but I'll +// worry about that later. + +class RAWInputStream : public AudioStream { +private: + ResourceContext *_context; + Common::File *_file; + uint32 _filePos; + uint32 _startPos; + uint32 _endPos; + bool _finished; + bool _looping; + int16 _buf[BUFFER_SIZE]; + const int16 *_bufferEnd; + const int16 *_pos; + const GameSoundInfo *_musicInfo; + + void refill(); + bool eosIntern() const { + return _pos >= _bufferEnd; + } + +public: + RAWInputStream(SagaEngine *vm, ResourceContext *context, uint32 resourceId, bool looping); + + int readBuffer(int16 *buffer, const int numSamples); + + bool endOfData() const { return eosIntern(); } + bool isStereo() const { return _musicInfo->stereo; } + int getRate() const { return _musicInfo->frequency; } +}; + +RAWInputStream::RAWInputStream(SagaEngine *vm, ResourceContext *context, uint32 resourceId, bool looping) + : _context(context), _finished(false), _looping(looping), _bufferEnd(_buf + BUFFER_SIZE) { + + ResourceData * resourceData; + + resourceData = vm->_resource->getResourceData(context, resourceId); + _file = context->getFile(resourceData); + _musicInfo = vm->getMusicInfo(); + + if (_musicInfo == NULL) { + error("RAWInputStream() wrong musicInfo"); + } + + // Determine the end position + _startPos = resourceData->offset; + _endPos = _startPos + resourceData->size; + _filePos = _startPos; + + // Read in initial data + refill(); +} + +int RAWInputStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + while (samples < numSamples && !eosIntern()) { + const int len = MIN(numSamples - samples, (int) (_bufferEnd - _pos)); + memcpy(buffer, _pos, len * 2); + buffer += len; + _pos += len; + samples += len; + if (_pos >= _bufferEnd) { + refill(); + } + } + return samples; +} + +void RAWInputStream::refill() { + if (_finished) + return; + + uint32 lengthLeft; + byte *ptr = (byte *) _buf; + + + _file->seek(_filePos, SEEK_SET); + + if (_looping) + lengthLeft = 2 * BUFFER_SIZE; + else + lengthLeft = MIN((uint32) (2 * BUFFER_SIZE), _endPos - _filePos); + + while (lengthLeft > 0) { + uint32 len = _file->read(ptr, MIN(lengthLeft, _endPos - _file->pos())); + + if (len & 1) + len--; + +#ifdef SCUMM_BIG_ENDIAN + if (!_context->isBigEndian) { +#else + if (_context->isBigEndian) { +#endif + uint16 *ptr16 = (uint16 *)ptr; + for (uint32 i = 0; i < (len / 2); i++) + ptr16[i] = SWAP_BYTES_16(ptr16[i]); + } + + lengthLeft -= len; + ptr += len; + + if (lengthLeft > 0) + _file->seek(_startPos); + } + + _filePos = _file->pos(); + _pos = _buf; + _bufferEnd = (int16 *)ptr; + + if (!_looping && _filePos >= _endPos) { + _finished = true; + } +} + + +MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false), _passThrough(false), _isGM(false) { + memset(_channel, 0, sizeof(_channel)); + _masterVolume = 0; + this->open(); +} + +MusicPlayer::~MusicPlayer() { + _driver->setTimerCallback(NULL, NULL); + stopMusic(); + this->close(); +} + +void MusicPlayer::setVolume(int volume) { + if (volume < 0) + volume = 0; + else if (volume > 255) + volume = 255; + + if (_masterVolume == volume) + return; + + _masterVolume = volume; + + for (int i = 0; i < 16; ++i) { + if (_channel[i]) { + _channel[i]->volume(_channelVolume[i] * _masterVolume / 255); + } + } +} + +int MusicPlayer::open() { + // Don't ever call open without first setting the output driver! + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + _driver->setTimerCallback(this, &onTimer); + return 0; +} + +void MusicPlayer::close() { + stopMusic(); + if (_driver) + _driver->close(); + _driver = 0; +} + +void MusicPlayer::send(uint32 b) { + if (_passThrough) { + _driver->send(b); + return; + } + + byte channel = (byte)(b & 0x0F); + if ((b & 0xFFF0) == 0x07B0) { + // Adjust volume changes by master volume + byte volume = (byte)((b >> 16) & 0x7F); + _channelVolume[channel] = volume; + volume = volume * _masterVolume / 255; + b = (b & 0xFF00FFFF) | (volume << 16); + } else if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) { + b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; + } + else if ((b & 0xFFF0) == 0x007BB0) { + //Only respond to All Notes Off if this channel + //has currently been allocated + if (_channel[b & 0x0F]) + return; + } + + if (!_channel[channel]) + _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + + if (_channel[channel]) + _channel[channel]->send(b); +} + +void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) { + // FIXME: The "elkfanfare" is played much too quickly. There are some + // meta events that we don't handle. Perhaps there is a + // connection...? + + switch (type) { + case 0x2F: // End of Track + if (_looping) + _parser->jumpToTick(0); + else + stopMusic(); + break; + default: + //warning("Unhandled meta event: %02x", type); + break; + } +} + +void MusicPlayer::onTimer(void *refCon) { + MusicPlayer *music = (MusicPlayer *)refCon; + if (music->_isPlaying) + music->_parser->onTimer(); +} + +void MusicPlayer::playMusic() { + _isPlaying = true; +} + +void MusicPlayer::stopMusic() { + _isPlaying = false; + if (_parser) { + _parser->unloadMusic(); + _parser = NULL; + } +} + +Music::Music(SagaEngine *vm, Audio::Mixer *mixer, MidiDriver *driver, int enabled) : _vm(vm), _mixer(mixer), _enabled(enabled), _adlib(false) { + _player = new MusicPlayer(driver); + _currentVolume = 0; + + xmidiParser = MidiParser::createParser_XMIDI(); + smfParser = MidiParser::createParser_SMF(); + + _musicContext = _vm->_resource->getContext(GAME_MUSICFILE); + + _songTableLen = 0; + _songTable = 0; + + _track = NULL; +} + +Music::~Music() { + _mixer->stopHandle(_musicHandle); + delete _player; + xmidiParser->setMidiDriver(NULL); + smfParser->setMidiDriver(NULL); + delete xmidiParser; + delete smfParser; + + free(_songTable); +} + +void Music::musicVolumeGaugeCallback(void *refCon) { + ((Music *)refCon)->musicVolumeGauge(); +} + +void Music::musicVolumeGauge() { + 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; + + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); + _player->setVolume(volume); + + if (_currentVolumePercent == 100) { + Common::g_timer->removeTimerProc(&musicVolumeGaugeCallback); + _currentVolume = _targetVolume; + } +} + +void Music::setVolume(int volume, int time) { + _targetVolume = volume * 2; // ScummVM has different volume scale + _currentVolumePercent = 0; + + if (volume == -1) // Set Full volume + volume = 255; + + if (time == 1) { + _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); + _player->setVolume(volume); + Common::g_timer->removeTimerProc(&musicVolumeGaugeCallback); + _currentVolume = volume; + return; + } + + Common::g_timer->installTimerProc(&musicVolumeGaugeCallback, time * 100L, this); +} + +bool Music::isPlaying() { + return _mixer->isSoundHandleActive(_musicHandle) || _player->isPlaying(); +} + +void Music::play(uint32 resourceId, MusicFlags flags) { + AudioStream *audioStream = NULL; + MidiParser *parser; + ResourceContext *context; + byte *resourceData; + size_t resourceSize; + + debug(2, "Music::play %d, %d", resourceId, flags); + + if (!_enabled) { + return; + } + + if (isPlaying() && _trackNumber == resourceId) { + return; + } + + _trackNumber = resourceId; + _player->stopMusic(); + _mixer->stopHandle(_musicHandle); + + int realTrackNumber; + + if (_vm->getGameType() == GType_ITE) { + if (flags == MUSIC_DEFAULT) { + if (resourceId == 13 || resourceId == 19) { + flags = MUSIC_NORMAL; + } else { + flags = MUSIC_LOOP; + } + } + realTrackNumber = resourceId - 8; + } else { + realTrackNumber = resourceId + 1; + } + + // Try to open standalone digital track + for (int i = 0; i < ARRAYSIZE(TRACK_FORMATS) - 1; ++i) + if ((_track = TRACK_FORMATS[i].openTrackFunction(realTrackNumber))) { + break; + } + if (_track) { + _track->play(_mixer, &_musicHandle, (flags == MUSIC_LOOP) ? -1 : 1, 10000); + return; + } + + if (_vm->getGameType() == GType_ITE) { + if (resourceId >= 9 && resourceId <= 34) { + if (_musicContext != NULL) { + //TODO: check resource size + audioStream = new RAWInputStream(_vm, _musicContext, resourceId - 9, flags == MUSIC_LOOP); + } + } + } + + if (audioStream) { + debug(2, "Playing digitized music"); + _mixer->playInputStream(Audio::Mixer::kMusicSoundType, &_musicHandle, audioStream); + return; + } + + if (flags == MUSIC_DEFAULT) { + flags = MUSIC_NORMAL; + } + + // FIXME: Is resource_data ever freed? + // Load MIDI/XMI resource data + + if (_vm->getGameType() == GType_ITE) { + context = _vm->_resource->getContext(GAME_MUSICFILE_GM); + if (context == NULL) { + context = _vm->_resource->getContext(GAME_RESOURCEFILE); + } + } else { + // I've listened to music from both the FM and the GM + // file, and I've tentatively reached the conclusion + // that they are both General MIDI. My guess is that + // the FM file has been reorchestrated to sound better + // on Adlib and other FM synths. + // + // Sev says the Adlib music does not sound like in the + // original, but I still think assuming General MIDI is + // the right thing to do. Some music, like the End + // Title (song 0) sound absolutely atrocious when piped + // through our MT-32 to GM mapping. + // + // It is, however, quite possible that the original + // used a different GM to FM mapping. If the original + // sounded markedly better, perhaps we should add some + // way of replacing our stock mapping in adlib.cpp? + // + // For the composer's own recording of the End Title, + // see http://www.johnottman.com/ + + // Oddly enough, the intro music (song 1) is very + // different in the two files. I have no idea why. + + if (hasAdlib()) { + context = _vm->_resource->getContext(GAME_MUSICFILE_FM); + } else { + context = _vm->_resource->getContext(GAME_MUSICFILE_GM); + } + } + + _player->setGM(true); + + _vm->_resource->loadResource(context, resourceId, resourceData, resourceSize); + + if (resourceSize < 4) { + error("Music::play() wrong music resource size"); + } + + if (xmidiParser->loadMusic(resourceData, resourceSize)) { + if (_vm->getGameType() == GType_ITE) + _player->setGM(false); + + parser = xmidiParser; + } else { + if (smfParser->loadMusic(resourceData, resourceSize)) { + parser = smfParser; + } else { + error("Music::play() wrong music resource"); + } + } + + parser->setTrack(0); + parser->setMidiDriver(_player); + parser->setTimerRate(_player->getBaseTempo()); + parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + + _player->_parser = parser; + _player->setVolume(_vm->_musicVolume == 10 ? 255 : _vm->_musicVolume * 25); + + if (flags & MUSIC_LOOP) + _player->setLoop(true); + else + _player->setLoop(false); + + _player->playMusic(); +} + +void Music::pause(void) { + //TODO: do it +} + +void Music::resume(void) { + //TODO: do it} +} + +void Music::stop(void) { + //TODO: do it +} + +} // End of namespace Saga + diff --git a/engines/saga/music.h b/engines/saga/music.h new file mode 100644 index 0000000000..377a5bf1d8 --- /dev/null +++ b/engines/saga/music.h @@ -0,0 +1,148 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Music class + +#ifndef SAGA_MUSIC_H_ +#define SAGA_MUSIC_H_ + +#include "sound/audiocd.h" +#include "sound/mididrv.h" +#include "sound/midiparser.h" + +namespace Saga { + +enum MusicFlags { + MUSIC_NORMAL = 0, + MUSIC_LOOP = 0x0001, + MUSIC_DEFAULT = 0xffff +}; + +class MusicPlayer : public MidiDriver { +public: + MusicPlayer(MidiDriver *driver); + ~MusicPlayer(); + + bool isPlaying() { return _isPlaying; } + + void setVolume(int volume); + int getVolume() { return _masterVolume; } + + void setNativeMT32(bool b) { _nativeMT32 = b; } + bool hasNativeMT32() { return _nativeMT32; } + void playMusic(); + void stopMusic(); + void setLoop(bool loop) { _looping = loop; } + void setPassThrough(bool b) { _passThrough = b; } + + void setGM(bool isGM) { _isGM = isGM; } + + //MidiDriver interface implementation + int open(); + void close(); + void send(uint32 b); + + void metaEvent(byte type, byte *data, uint16 length); + + void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { } + uint32 getBaseTempo(void) { return _driver ? _driver->getBaseTempo() : 0; } + + //Channel allocation functions + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + MidiParser *_parser; + +protected: + + static void onTimer(void *data); + + MidiChannel *_channel[16]; + MidiDriver *_driver; + byte _channelVolume[16]; + bool _nativeMT32; + bool _isGM; + bool _passThrough; + + bool _isPlaying; + bool _looping; + bool _randomLoop; + byte _masterVolume; + + byte *_musicData; + uint16 *_buf; + uint32 _musicDataSize; +}; + +class Music { +public: + + Music(SagaEngine *vm, Audio::Mixer *mixer, MidiDriver *driver, int enabled); + ~Music(void); + void setNativeMT32(bool b) { _player->setNativeMT32(b); } + bool hasNativeMT32() { return _player->hasNativeMT32(); } + void setAdlib(bool b) { _adlib = b; } + bool hasAdlib() { return _adlib; } + void setPassThrough(bool b) { _player->setPassThrough(b); } + bool isPlaying(void); + + void play(uint32 resourceId, MusicFlags flags = MUSIC_DEFAULT); + void pause(void); + void resume(void); + void stop(void); + + void setVolume(int volume, int time = 1); + int getVolume() { return _currentVolume; } + + int32 *_songTable; + int _songTableLen; + +private: + SagaEngine *_vm; + Audio::Mixer *_mixer; + + MusicPlayer *_player; + Audio::SoundHandle _musicHandle; + uint32 _trackNumber; + + int _enabled; + bool _adlib; + + int _targetVolume; + int _currentVolume; + int _currentVolumePercent; + + ResourceContext *_musicContext; + MidiParser *xmidiParser; + MidiParser *smfParser; + + DigitalTrackInfo *_track; + + static void musicVolumeGaugeCallback(void *refCon); + void musicVolumeGauge(void); +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/objectmap.cpp b/engines/saga/objectmap.cpp new file mode 100644 index 0000000000..d0a302b0d7 --- /dev/null +++ b/engines/saga/objectmap.cpp @@ -0,0 +1,267 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// 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/stream.h" +#include "saga/actor.h" +#include "saga/scene.h" +#include "saga/isomap.h" + +namespace Saga { + +HitZone::HitZone(MemoryReadStreamEndian *readStream, int index): _index(index) { + int i, j; + HitZone::ClickArea *clickArea; + Point *point; + + _flags = readStream->readByte(); + _clickAreasCount = readStream->readByte(); + _rightButtonVerb = readStream->readByte(); + readStream->readByte(); // pad + _nameIndex = readStream->readUint16(); + _scriptNumber = readStream->readUint16(); + + _clickAreas = (HitZone::ClickArea *)malloc(_clickAreasCount * sizeof(*_clickAreas)); + + if (_clickAreas == NULL) { + memoryError("HitZone::HitZone"); + } + + for (i = 0; i < _clickAreasCount; i++) { + clickArea = &_clickAreas[i]; + clickArea->pointsCount = readStream->readUint16LE(); + + assert(clickArea->pointsCount); + + clickArea->points = (Point *)malloc(clickArea->pointsCount * sizeof(*(clickArea->points))); + if (clickArea->points == NULL) { + memoryError("HitZone::HitZone"); + } + + for (j = 0; j < clickArea->pointsCount; j++) { + point = &clickArea->points[j]; + point->x = readStream->readSint16(); + point->y = readStream->readSint16(); + } + } +} + +HitZone::~HitZone() { + for (int i = 0; i < _clickAreasCount; i++) { + free(_clickAreas[i].points); + } + free(_clickAreas); +} + +bool HitZone::getSpecialPoint(Point &specialPoint) const { + int i, pointsCount; + HitZone::ClickArea *clickArea; + Point *points; + + for (i = 0; i < _clickAreasCount; i++) { + clickArea = &_clickAreas[i]; + pointsCount = clickArea->pointsCount; + points = clickArea->points; + if (pointsCount == 1) { + specialPoint = points[0]; + return true; + } + } + return false; +} +bool HitZone::hitTest(const Point &testPoint) { + int i, pointsCount; + HitZone::ClickArea *clickArea; + Point *points; + + if (_flags & kHitZoneEnabled) { + for (i = 0; i < _clickAreasCount; i++) { + clickArea = &_clickAreas[i]; + pointsCount = clickArea->pointsCount; + points = clickArea->points; + + 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 { + if (pointsCount > 2) { + // Hit-test a polygon + if (hitTestPoly(points, pointsCount, testPoint)) { + return true; + } + } + } + } + } + return false; +} + +void HitZone::draw(SagaEngine *vm, Surface *ds, int color) { + int i, pointsCount, j; + Location location; + HitZone::ClickArea *clickArea; + Point *points; + for (i = 0; i < _clickAreasCount; i++) { + clickArea = &_clickAreas[i]; + pointsCount = clickArea->pointsCount; + if (vm->_scene->getFlags() & kSceneFlagISO) { + points = (Point*)malloc(sizeof(Point) * pointsCount); + for (j = 0; j < pointsCount; j++) { + location.u() = clickArea->points[j].x; + location.v() = clickArea->points[j].y; + location.z = 0; + vm->_isoMap->tileCoordsToScreenPoint(location, points[j]); + } + } else { + points = clickArea->points; + } + + if (pointsCount == 2) { + // 2 points represent a box + ds->drawFrame(points[0], points[1], color); + } else { + if (pointsCount > 2) { + // Otherwise draw a polyline + ds->drawPolyLine(points, pointsCount, color); + } + } + if (vm->_scene->getFlags() & kSceneFlagISO) { + free(points); + } + + } +} + + +// Loads an object map resource ( objects ( clickareas ( points ) ) ) +void ObjectMap::load(const byte *resourcePointer, size_t resourceLength) { + int i; + + if (resourceLength == 0) { + return; + } + + if (resourceLength < 4) { + error("ObjectMap::load wrong resourceLength"); + } + + MemoryReadStreamEndian readS(resourcePointer, resourceLength, _vm->isBigEndian()); + + _hitZoneListCount = readS.readSint16(); + if (_hitZoneListCount < 0) { + error("ObjectMap::load _hitZoneListCount < 0"); + } + + if (_hitZoneList) + error("ObjectMap::load _hitZoneList != NULL"); + + _hitZoneList = (HitZone **) malloc(_hitZoneListCount * sizeof(HitZone *)); + if (_hitZoneList == NULL) { + memoryError("ObjectMap::load"); + } + + for (i = 0; i < _hitZoneListCount; i++) { + _hitZoneList[i] = new HitZone(&readS, i); + } +} + +void ObjectMap::freeMem() { + int i; + + if (_hitZoneList) { + for (i = 0; i < _hitZoneListCount; i++) { + delete _hitZoneList[i]; + } + + free(_hitZoneList); + _hitZoneList = NULL; + } + _hitZoneListCount = 0; +} + + + +void ObjectMap::draw(Surface *ds, const Point& testPoint, int color, int color2) { + int i; + int hitZoneIndex; + char txtBuf[32]; + 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 (i = 0; i < _hitZoneListCount; i++) { + _hitZoneList[i]->draw(_vm, ds, (hitZoneIndex == i) ? color2 : color); + } + + if (hitZoneIndex != -1) { + snprintf(txtBuf, sizeof(txtBuf), "hitZone %d", hitZoneIndex); + textPoint.x = 2; + textPoint.y = 2; + _vm->_font->textDraw(kKnownFontSmall, ds, txtBuf, textPoint, kITEColorBrightWhite, kITEColorBlack, kFontOutline); + } +} + +int ObjectMap::hitTest(const Point& testPoint) { + int i; + + // Loop through all scene objects + for (i = 0; i < _hitZoneListCount; i++) { + if (_hitZoneList[i]->hitTest(testPoint)) { + return i; + } + } + + return -1; +} + +void ObjectMap::cmdInfo(void) { + _vm->_console->DebugPrintf("%d zone(s) loaded.\n\n", _hitZoneListCount); +} + +} // End of namespace Saga diff --git a/engines/saga/objectmap.h b/engines/saga/objectmap.h new file mode 100644 index 0000000000..43356d3579 --- /dev/null +++ b/engines/saga/objectmap.h @@ -0,0 +1,128 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Object map / Object click-area module header file + +#ifndef SAGA_OBJECTMAP_H_ +#define SAGA_OBJECTMAP_H_ + +#include "saga/stream.h" + +namespace Saga { + + +class HitZone { +private: + struct ClickArea { + int pointsCount; + Point *points; + }; + +public: + HitZone(MemoryReadStreamEndian *readStream, int index); + ~HitZone(); + + 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; + void draw(SagaEngine *vm, Surface *ds, int color); + bool hitTest(const Point &testPoint); + +private: + int _flags; // Saga::HitZoneFlags + int _clickAreasCount; + int _rightButtonVerb; + int _nameIndex; + int _scriptNumber; + int _index; + + ClickArea *_clickAreas; +}; + + +class ObjectMap { +public: + ObjectMap(SagaEngine *vm) : _vm(vm) { + _hitZoneList = NULL; + _hitZoneListCount = 0; + + } + ~ObjectMap(void) { + freeMem(); + } + void load(const byte *resourcePointer, size_t resourceLength); + void freeMem(void); + + void draw(Surface *drawSurface, const Point& testPoint, int color, int color2); + int hitTest(const Point& testPoint); + HitZone *getHitZone(int16 index) { + if ((index < 0) || (index >= _hitZoneListCount)) { + error("ObjectMap::getHitZone wrong index 0x%X", index); + } + return _hitZoneList[index]; + } + + void cmdInfo(void); + +private: + SagaEngine *_vm; + + int _hitZoneListCount; + HitZone **_hitZoneList; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/palanim.cpp b/engines/saga/palanim.cpp new file mode 100644 index 0000000000..4cfaf5e0fd --- /dev/null +++ b/engines/saga/palanim.cpp @@ -0,0 +1,209 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Palette animation module +#include "saga/saga.h" +#include "saga/gfx.h" + +#include "saga/events.h" + +#include "saga/palanim.h" +#include "saga/stream.h" + +namespace Saga { + +PalAnim::PalAnim(SagaEngine *vm) : _vm(vm) { + _loaded = false; + _entryCount = 0; + _entries = NULL; +} + +PalAnim::~PalAnim(void) { +} + +int PalAnim::loadPalAnim(const byte *resdata, size_t resdata_len) { + void *test_p; + + uint16 i; + + if (_loaded) { + freePalAnim(); + } + + if (resdata == NULL) { + return FAILURE; + } + + MemoryReadStreamEndian readS(resdata, resdata_len, _vm->isBigEndian()); + + if (_vm->getGameType() == GType_IHNM) { + return SUCCESS; + } + + _entryCount = readS.readUint16(); + + debug(3, "PalAnim::loadPalAnim(): Loading %d PALANIM entries.", _entryCount); + + test_p = calloc(_entryCount, sizeof(PalanimEntry)); + if (test_p == NULL) { + warning("PalAnim::loadPalAnim(): Allocation failure"); + return MEM; + } + + _entries = (PalanimEntry *)test_p; + + for (i = 0; i < _entryCount; i++) { + int color_count; + int pal_count; + int p, c; + + color_count = readS.readUint16(); + pal_count = readS.readUint16(); + + _entries[i].pal_count = pal_count; + _entries[i].color_count = color_count; + + debug(2, "PalAnim::loadPalAnim(): Entry %d: Loading %d palette indices.\n", i, pal_count); + + test_p = calloc(1, sizeof(char) * pal_count); + if (test_p == NULL) { + warning("PalAnim::loadPalAnim(): Allocation failure"); + return MEM; + } + + _entries[i].pal_index = (byte *)test_p; + + debug(2, "PalAnim::loadPalAnim(): Entry %d: Loading %d SAGA_COLOR structures.", i, color_count); + + test_p = calloc(1, sizeof(Color) * color_count); + if (test_p == NULL) { + warning("PalAnim::loadPalAnim(): Allocation failure"); + return MEM; + } + + _entries[i].colors = (Color *)test_p; + + for (p = 0; p < pal_count; p++) { + _entries[i].pal_index[p] = readS.readByte(); + } + + for (c = 0; c < color_count; c++) { + _entries[i].colors[c].red = readS.readByte(); + _entries[i].colors[c].green = readS.readByte(); + _entries[i].colors[c].blue = readS.readByte(); + } + } + + _loaded = true; + return SUCCESS; +} + +int PalAnim::cycleStart() { + Event event; + + if (!_loaded) { + return FAILURE; + } + + event.type = kEvTOneshot; + event.code = kPalAnimEvent; + event.op = kEventCycleStep; + event.time = PALANIM_CYCLETIME; + + _vm->_events->queue(&event); + + return SUCCESS; +} + +int PalAnim::cycleStep(int vectortime) { + static PalEntry pal[256]; + uint16 pal_index; + uint16 col_index; + + uint16 i, j; + uint16 cycle; + uint16 cycle_limit; + + Event event; + + if (!_loaded) { + return FAILURE; + } + + _vm->_gfx->getCurrentPal(pal); + + for (i = 0; i < _entryCount; i++) { + cycle = _entries[i].cycle; + cycle_limit = _entries[i].color_count; + for (j = 0; j < _entries[i].pal_count; j++) { + pal_index = (unsigned char)_entries[i].pal_index[j]; + col_index = (cycle + j) % cycle_limit; + pal[pal_index].red = (byte) _entries[i].colors[col_index].red; + pal[pal_index].green = (byte) _entries[i].colors[col_index].green; + pal[pal_index].blue = (byte) _entries[i].colors[col_index].blue; + } + + _entries[i].cycle++; + + if (_entries[i].cycle == cycle_limit) { + _entries[i].cycle = 0; + } + } + + _vm->_gfx->setPalette(pal); + + event.type = kEvTOneshot; + event.code = kPalAnimEvent; + event.op = kEventCycleStep; + event.time = vectortime + PALANIM_CYCLETIME; + + _vm->_events->queue(&event); + + return SUCCESS; +} + +int PalAnim::freePalAnim() { + uint16 i; + + if (!_loaded) { + return FAILURE; + } + + for (i = 0; i < _entryCount; i++) { + debug(2, "PalAnim::freePalAnim(): Entry %d: Freeing colors.", i); + free(_entries[i].colors); + debug(2, "PalAnim::freePalAnim(): Entry %d: Freeing indices.", i); + free(_entries[i].pal_index); + } + + debug(3, "PalAnim::freePalAnim(): Freeing entries."); + + free(_entries); + + _loaded = false; + + return SUCCESS; +} + +} // End of namespace Saga diff --git a/engines/saga/palanim.h b/engines/saga/palanim.h new file mode 100644 index 0000000000..e2ca695eb6 --- /dev/null +++ b/engines/saga/palanim.h @@ -0,0 +1,63 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Palette animation module header file + +#ifndef SAGA_PALANIM_H +#define SAGA_PALANIM_H + +namespace Saga { + +#define PALANIM_CYCLETIME 100 + +struct PalanimEntry { + uint16 pal_count; + uint16 color_count; + uint16 cycle; + byte *pal_index; + Color *colors; +}; + +class PalAnim { + public: + PalAnim(SagaEngine *vm); + ~PalAnim(void); + + int loadPalAnim(const byte *, size_t); + int cycleStart(); + int cycleStep(int vectortime); + int freePalAnim(); + + private: + SagaEngine *_vm; + + bool _loaded; + uint16 _entryCount; + PalanimEntry *_entries; +}; + +} // End of namespace Saga + +#endif + diff --git a/engines/saga/puzzle.cpp b/engines/saga/puzzle.cpp new file mode 100644 index 0000000000..c09a9c0462 --- /dev/null +++ b/engines/saga/puzzle.cpp @@ -0,0 +1,560 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#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 "saga/resnames.h" + +#include "common/system.h" +#include "common/timer.h" + +namespace Saga { + +#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 + + +enum rifOptions { + kROLater = 0, + kROAccept = 1, + kRODecline = 2, + kROHint = 3 +}; + +Puzzle::Puzzle(SagaEngine *vm) : _vm(vm), _solved(false), _active(false) { + _lang = (_vm->getLanguage() == Common::DE_DEU) ? 1 : 0; + + _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); + + 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(void) { + _active = true; + Common::g_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this); + + initPieces(); + + showPieces(); + + _vm->_interface->setMode(kPanelConverse); + clearHint(); + //_solved = true; // Cheat + //exitPuzzle(); +} + +void Puzzle::exitPuzzle(void) { + _active = false; + + Common::g_timer->removeTimerProc(&hintTimerCallback); + + _vm->_scene->changeScene(ITE_SCENE_LODGE, 0, kTransitionNoFade); + _vm->_interface->setMode(kPanelMain); +} + +void Puzzle::initPieces(void) { + 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->infoList[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(void) { + ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE)); + int frameNumber; + SpriteList *spriteList; + Surface *backBuffer = _vm->_gfx->getBackBuffer(); + _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(backBuffer, _vm->getDisplayClip(), *spriteList, num, Point(_pieceInfo[num].curX, _pieceInfo[num].curY), 256); + } + } +} + +void Puzzle::drawCurrentPiece() { + ActorData *puzzle = _vm->_actor->getActor(_vm->_actor->actorIndexToId(ITE_ACTOR_PUZZLE)); + Surface *backBuffer = _vm->_gfx->getBackBuffer(); + int frameNumber; + SpriteList *spriteList; + _vm->_actor->getSpriteParams(puzzle, frameNumber, spriteList); + + _vm->_sprite->draw(backBuffer, _vm->_scene->getSceneClip(), *spriteList, _puzzlePiece, + Point(_pieceInfo[_puzzlePiece].curX, _pieceInfo[_puzzlePiece].curY), 256); +} + +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(void) { + 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; + Point slidePoints[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->infoList[_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(void) { + int i; + + _vm->_actor->setSpeechColor(1, kITEColorBlack); + + Common::g_timer->removeTimerProc(&hintTimerCallback); + + switch (_hintRqState) { + case kRQSpeaking: + if (_vm->_actor->isSpeaking()) { + Common::g_timer->installTimerProc(&hintTimerCallback, 50000, this); + break; + } + + _hintRqState = _hintNextRqState; + Common::g_timer->installTimerProc(&hintTimerCallback, 333333, this); + 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; + Common::g_timer->installTimerProc(&hintTimerCallback, 200000, this); + } else { + _hintRqState = kRQSpeaking; + _hintNextRqState = kRQHintRequested; + Common::g_timer->installTimerProc(&hintTimerCallback, 50000, this); + } + + 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; + Common::g_timer->installTimerProc(&hintTimerCallback, 50000, this); + + _vm->_interface->converseClear(); + _vm->_interface->converseAddText(optionsStr[_lang][kROAccept], 1, 0, 0 ); + _vm->_interface->converseAddText(optionsStr[_lang][kRODecline], 2, 0, 0 ); + _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 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], 1, 0, 0); + _vm->_interface->converseAddText(optionsStr[_lang][kRODecline], 2, 0, 0); + _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0); + _vm->_interface->converseDisplayText(); + + Common::g_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this); + + _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; + Common::g_timer->removeTimerProc(&hintTimerCallback); + Common::g_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime * 2, this); + clearHint(); + break; + } +} + +void Puzzle::giveHint(void) { + 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, kITEColorBlack); + + 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; + sprintf(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); + _vm->_interface->converseDisplayText(); + + Common::g_timer->removeTimerProc(&hintTimerCallback); + Common::g_timer->installTimerProc(&hintTimerCallback, kPuzzleHintTime, this); +} + +void Puzzle::clearHint(void) { + _vm->_interface->converseClear(); + _vm->_interface->converseAddText(optionsStr[_lang][kROLater], 0, 0, 0); + _vm->_interface->converseDisplayText(); + _vm->_interface->setStatusText(" "); +} + +} // End of namespace Saga diff --git a/engines/saga/puzzle.h b/engines/saga/puzzle.h new file mode 100644 index 0000000000..b5c9b7c15c --- /dev/null +++ b/engines/saga/puzzle.h @@ -0,0 +1,120 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2005-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#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); + void exitPuzzle(void); + + bool isSolved(void) { return _solved; } + bool isActive(void) { 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); + + void initPieces(void); + void showPieces(void); + void slidePiece(int x1, int y1, int x2, int y2); + void dropPiece(Point mousePt); + void alterPiecePriority(void); + void drawCurrentPiece(void); + + void giveHint(void); + void clearHint(void); + +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 diff --git a/engines/saga/render.cpp b/engines/saga/render.cpp new file mode 100644 index 0000000000..7d312fb920 --- /dev/null +++ b/engines/saga/render.cpp @@ -0,0 +1,189 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// 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 *pauseString = "PAWS GAME"; + +Render::Render(SagaEngine *vm, OSystem *system) { + _vm = vm; + _system = system; + _initialized = false; + + // Initialize FPS timer callback + Common::g_timer->installTimerProc(&fpsTimerCallback, 1000000, this); + + _backGroundSurface.create(_vm->getDisplayWidth(), _vm->getDisplayHeight(), 1); + + _flags = 0; + + _initialized = true; +} + +Render::~Render(void) { + Common::g_timer->removeTimerProc(&fpsTimerCallback); + _backGroundSurface.free(); + + _initialized = false; +} + +bool Render::initialized() { + return _initialized; +} + +void Render::drawScene() { + Surface *backBufferSurface; + char txtBuffer[20]; + Point mousePoint; + Point textPoint; + + assert(_initialized); + + _frameCount++; + + backBufferSurface = _vm->_gfx->getBackBuffer(); + + // Get mouse coordinates + mousePoint = _vm->mousePos(); + + if (!(_flags & (RF_DEMO_SUBST | RF_PLACARD | RF_MAP))) { + // Display scene background + _vm->_scene->draw(); + + if (_vm->_interface->getFadeMode() != kFadeOut) { + if (_vm->_puzzle->isActive()) { + _vm->_puzzle->movePiece(mousePoint); + _vm->_actor->drawSpeech(); + } else { + // Draw queued actors + if (!(_flags & RF_DISABLE_ACTORS)) + _vm->_actor->drawActors(); + } + + if (getFlags() & RF_OBJECTMAP_TEST) { + if (_vm->_scene->_objectMap) + _vm->_scene->_objectMap->draw(backBufferSurface, mousePoint, kITEColorBrightWhite, kITEColorBlack); + if (_vm->_scene->_actionMap) + _vm->_scene->_actionMap->draw(backBufferSurface, mousePoint, kITEColorRed, kITEColorBlack); + } + if (getFlags() & RF_ACTOR_PATH_TEST) { + _vm->_actor->drawPathTest(); + } + } + } + + if (_flags & RF_MAP) + _vm->_interface->mapPanelDrawCrossHair(); + + if ((_vm->_interface->getMode() == kPanelOption) || + (_vm->_interface->getMode() == kPanelQuit) || + (_vm->_interface->getMode() == kPanelLoad) || + (_vm->_interface->getMode() == kPanelSave)) { + _vm->_interface->drawOption(); + + if (_vm->_interface->getMode() == kPanelQuit) { + _vm->_interface->drawQuit(); + } + if (_vm->_interface->getMode() == kPanelLoad) { + _vm->_interface->drawLoad(); + } + if (_vm->_interface->getMode() == kPanelSave) { + _vm->_interface->drawSave(); + } + } + + if (_vm->_interface->getMode() == kPanelProtect) { + _vm->_interface->drawProtect(); + } + + // Draw queued text strings + _vm->_scene->drawTextList(backBufferSurface); + + // Handle user input + _vm->processInput(); + + // Display rendering information + if (_flags & RF_SHOW_FPS) { + sprintf(txtBuffer, "%d", _fps); + textPoint.x = backBufferSurface->w - _vm->_font->getStringWidth(kKnownFontSmall, txtBuffer, 0, kFontOutline); + textPoint.y = 2; + + _vm->_font->textDraw(kKnownFontSmall, backBufferSurface, txtBuffer, textPoint, kITEColorBrightWhite, kITEColorBlack, kFontOutline); + } + + // Display "paused game" message, if applicable + if (_flags & RF_RENDERPAUSE) { + textPoint.x = (backBufferSurface->w - _vm->_font->getStringWidth(kKnownFontPause, pauseString, 0, kFontOutline)) / 2; + textPoint.y = 90; + + _vm->_font->textDraw(kKnownFontPause, backBufferSurface, pauseString, textPoint, kITEColorBrightWhite, kITEColorBlack, kFontOutline); + } + + // Update user interface + _vm->_interface->update(mousePoint, UPDATE_MOUSEMOVE); + + // 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, backBufferSurface, test_txt, rect, + kITEColorBrightWhite, kITEColorBlack, (FontEffectFlags)(kFontOutline | kFontCentered)); + } + + // Display palette test, if applicable + if (_flags & RF_PALETTE_TEST) { + backBufferSurface->drawPalette(); + } + + _system->copyRectToScreen((byte *)backBufferSurface->pixels, backBufferSurface->w, 0, 0, + backBufferSurface->w, backBufferSurface->h); + + _system->updateScreen(); +} + +void Render::fpsTimerCallback(void *refCon) { + ((Render *)refCon)->fpsTimer(); +} + +void Render::fpsTimer(void) { + _fps = _frameCount; + _frameCount = 0; +} + +} // End of namespace Saga diff --git a/engines/saga/render.h b/engines/saga/render.h new file mode 100644 index 0000000000..f6187393d2 --- /dev/null +++ b/engines/saga/render.h @@ -0,0 +1,93 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Main rendering loop - private header + +#ifndef SAGA_RENDER_H_ +#define SAGA_RENDER_H_ + +#include "saga/sprite.h" + +namespace Saga { + +enum RENDER_FLAGS { + RF_SHOW_FPS = (1 << 0), + RF_PALETTE_TEST = (1 << 1), + RF_TEXT_TEST = (1 << 2), + RF_OBJECTMAP_TEST = (1 << 3), + RF_RENDERPAUSE = (1 << 4), + RF_GAMEPAUSE = (1 << 5), + RF_PLACARD = (1 << 6), + RF_ACTOR_PATH_TEST = (1 << 7), + RF_MAP = (1 << 8), + RF_DISABLE_ACTORS = (1 << 9), + RF_DEMO_SUBST = (1 << 10) +}; + +class Render { +public: + Render(SagaEngine *vm, OSystem *system); + ~Render(void); + bool initialized(); + void drawScene(void); + + 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; + } + +private: + static void fpsTimerCallback(void *refCon); + void fpsTimer(void); + + SagaEngine *_vm; + OSystem *_system; + bool _initialized; + + // Module data + Surface _backGroundSurface; + + unsigned int _fps; + unsigned int _frameCount; + uint32 _flags; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/resnames.h b/engines/saga/resnames.h new file mode 100644 index 0000000000..01522a9ca7 --- /dev/null +++ b/engines/saga/resnames.h @@ -0,0 +1,253 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Descriptive names for game resource numbers + +#ifndef SAGA_RESOURCENAMES_H_ +#define SAGA_RESOURCENAMES_H_ + +namespace Saga { + +// Prefix RID_ means Resource Id + +// Lookup tables +#define RID_ITE_SCENE_LUT 1806 +#define RID_ITE_SCRIPT_LUT 216 + +#define RID_ITEDEMO_SCENE_LUT 318 +#define RID_ITEDEMO_SCRIPT_LUT 146 + +#define RID_IHNM_SCENE_LUT 1272 +#define RID_IHNM_SCRIPT_LUT 29 +#define RID_IHNM_SFX_LUT 265 + +#define RID_IHNMDEMO_SCENE_LUT 286 +#define RID_IHNMDEMO_SCRIPT_LUT 18 + +//obj names +#define ITE_OBJ_MAP 14 +#define ITE_OBJ_MAGIC_HAT 0 + +#define IHNM_OBJ_PROFILE 0x4000 + +#define RID_IHNM_DEFAULT_PALETTE 1 + +//actor names +#define ITE_ACTOR_PUZZLE 176 + +// SCENES +#define ITE_SCENE_INV -1 +#define ITE_SCENE_PUZZLE 26 +#define ITE_SCENE_LODGE 21 +#define ITE_SCENE_ENDCREDIT1 295 + +#define ITE_DEFAULT_SCENE 32 +#define IHNM_DEFAULT_SCENE 151 + +#define ITEDEMO_DEFAULT_SCENE 68 + +// FONTS +#define RID_MEDIUM_FONT 0 +#define RID_BIG_FONT 1 +#define RID_SMALL_FONT 2 + +// INTERFACE IMAGES +#define RID_ITE_MAIN_PANEL 3 +#define RID_ITE_CONVERSE_PANEL 4 +#define RID_ITE_OPTION_PANEL 5 +#define RID_ITE_MAIN_SPRITES 6 +#define RID_ITE_MAIN_PANEL_SPRITES 7 +#define RID_ITE_MAIN_STRINGS 35 //main strings +#define RID_ITE_ACTOR_NAMES 36 //actors names +#define RID_ITE_DEFAULT_PORTRAITS 125 + +#define RID_ITEDEMO_MAIN_PANEL 2 +#define RID_ITEDEMO_CONVERSE_PANEL 3 +#define RID_ITEDEMO_OPTION_PANEL 3 // FIXME: should be 4 but it is an empty resource. +#define RID_ITEDEMO_MAIN_SPRITES 5 // Proper fix would be not load options panel when demo is running +#define RID_ITEDEMO_MAIN_PANEL_SPRITES 6 +#define RID_ITEDEMO_MAIN_STRINGS 8 //main strings +#define RID_ITEDEMO_ACTOR_NAMES 9 //actors names +#define RID_ITEDEMO_DEFAULT_PORTRAITS 80 + +#define RID_ITE_TYCHO_MAP 1686 +#define RID_ITE_SPR_XHAIR1 (73 + 9) +#define RID_ITE_SPR_XHAIR2 (74 + 9) + +#define RID_IHNM_MAIN_PANEL 9 +#define RID_IHNM_CONVERSE_PANEL 10 +#define RID_IHNM_HOURGLASS_CURSOR 11 +#define RID_IHNM_MAIN_SPRITES 12 // TODO: verify this +#define RID_IHNM_MAIN_PANEL_SPRITES 12 +#define RID_IHNM_ARROW_SPRITES 13 +#define RID_IHNM_SAVEREMINDER_SPRITES 14 +#define RID_IHNM_OPTION_PANEL 15 +#define RID_IHNM_WARNING_PANEL 17 +#define RID_IHNM_BOSS_SCREEN 19 +#define RID_IHNM_PROFILE_BG 20 +#define RID_IHNM_MAIN_STRINGS 21 + +// 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 + +// ITE Scene resource numbers +#define RID_ITE_OVERMAP_SCENE 226 +#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 + +#define RID_ITE_INTRO_ANIM_STARTFRAME 1529 + +#define RID_ITE_INTRO_ANIM_1 1530 +#define RID_ITE_INTRO_ANIM_2 1531 +#define RID_ITE_INTRO_ANIM_3 1532 +#define RID_ITE_INTRO_ANIM_4 1533 +#define RID_ITE_INTRO_ANIM_5 1534 +#define RID_ITE_INTRO_ANIM_6 1535 +#define RID_ITE_INTRO_ANIM_7 1536 + +#define RID_ITE_CAVE_IMG_1 1540 +#define RID_ITE_CAVE_IMG_2 1543 +#define RID_ITE_CAVE_IMG_3 1546 +#define RID_ITE_CAVE_IMG_4 1549 + +#define RID_ITE_INTRO_IMG_1 1552 +#define RID_ITE_INTRO_IMG_2 1557 +#define RID_ITE_INTRO_IMG_3 1561 +#define RID_ITE_INTRO_IMG_4 1565 + +// ITE_VOICES +#define RID_CAVE_VOICE_0 0 +#define RID_CAVE_VOICE_1 1 +#define RID_CAVE_VOICE_2 2 +#define RID_CAVE_VOICE_3 3 +#define RID_CAVE_VOICE_4 4 +#define RID_CAVE_VOICE_5 5 +#define RID_CAVE_VOICE_6 6 +#define RID_CAVE_VOICE_7 7 +#define RID_CAVE_VOICE_8 8 +#define RID_CAVE_VOICE_9 9 +#define RID_CAVE_VOICE_10 10 +#define RID_CAVE_VOICE_11 11 +#define RID_CAVE_VOICE_12 12 +#define RID_CAVE_VOICE_13 13 + +#define RID_SCENE1_VOICE_009 57 +//TODO: fill it +#define RID_SCENE1_VOICE_138 186 + +#define RID_BOAR_VOICE_000 239 +#define RID_BOAR_VOICE_002 241 +#define RID_BOAR_VOICE_005 244 +#define RID_BOAR_VOICE_006 245 +#define RID_BOAR_VOICE_007 246 + +// MUSIC +#define MUSIC_1 9 +#define MUSIC_2 10 + +// TODO: If the sound effects are numbered sequentially, we don't really need +// these constants. But for now they might be useful for debugging. + +// SOUND EFFECTS + +#define FX_DOOR_OPEN 14 +#define FX_DOOR_CLOSE 15 +#define FX_RUSH_WATER 16 +#define FX_CRICKET 17 +#define FX_PORTICULLIS 18 +#define FX_CLOCK_1 19 +#define FX_CLOCK_2 20 +#define FX_DAM_MACHINE 21 +#define FX_HUM1 22 +#define FX_HUM2 23 +#define FX_HUM3 24 +#define FX_HUM4 25 +#define FX_STREAM 26 +#define FX_SURF 27 +#define FX_FIRELOOP 28 +#define FX_SCRAPING 29 +#define FX_BEE_SWARM 30 +#define FX_SQUEAKBOARD 31 +#define FX_KNOCK 32 +#define FX_COINS 33 +#define FX_STORM 34 +#define FX_DOOR_CLOSE_2 35 +#define FX_ARCWELD 36 +#define FX_RETRACT_ORB 37 +#define FX_DRAGON 38 +#define FX_SNORES 39 +#define FX_SPLASH 40 +#define FX_LOBBY_DOOR 41 +#define FX_CHIRP_LOOP 42 +#define FX_DOOR_CREAK 43 +#define FX_SPOON_DIG 44 +#define FX_CROW 45 +#define FX_COLDWIND 46 +#define FX_TOOL_SND_1 47 +#define FX_TOOL_SND_2 48 +#define FX_TOOL_SND_3 49 +#define FX_DOOR_METAL 50 +#define FX_WATER_LOOP_S 51 +#define FX_WATER_LOOP_L 52 +#define FX_DOOR_OPEN_2 53 +#define FX_JAIL_DOOR 54 +#define FX_KILN_FIRE 55 +#define FX_DUMMY 56 + +// These are only in the CD version + +#define FX_CROWD_01 57 +#define FX_CROWD_02 58 +#define FX_CROWD_03 59 +#define FX_CROWD_04 60 +#define FX_CROWD_05 61 +#define FX_CROWD_06 62 +#define FX_CROWD_07 63 +#define FX_CROWD_08 64 +#define FX_CROWD_09 65 +#define FX_CROWD_10 66 +#define FX_CROWD_11 67 +#define FX_CROWD_12 68 +#define FX_CROWD_13 69 +#define FX_CROWD_14 70 +#define FX_CROWD_15 71 +#define FX_CROWD_16 72 +#define FX_CROWD_17 73 + +} // End of namespace Saga + +#endif diff --git a/engines/saga/rscfile.cpp b/engines/saga/rscfile.cpp new file mode 100644 index 0000000000..24d1262d56 --- /dev/null +++ b/engines/saga/rscfile.cpp @@ -0,0 +1,615 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// 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/rscfile.h" +#include "saga/scene.h" +#include "saga/sndres.h" +#include "saga/stream.h" + +namespace Saga { + +struct MacResMap { + int16 resAttr; + int16 typeOffset; + int16 nameOffset; + int16 numTypes; +}; + +struct MacResource { + int16 id; + int16 nameOffset; + byte attr; + int32 dataOffset; + byte name[255]; +}; + +struct MacResType { + uint32 id; + int16 items; + int16 maxItemId; + int16 offset; + MacResource *resources; +}; + + +#define ID_MIDI MKID_BE('Midi') + +Resource::Resource(SagaEngine *vm): _vm(vm) { + _contexts = NULL; + _contextsCount = 0; +} + +Resource::~Resource() { + clearContexts(); +} + +bool Resource::loadSagaContext(ResourceContext *context, uint32 contextOffset, uint32 contextSize) { + size_t i; + bool result; + byte tableInfo[RSC_TABLEINFO_SIZE]; + byte *tableBuffer; + size_t tableSize; + uint32 resourceTableOffset; + ResourceData *resourceData; + + if (contextSize < RSC_MIN_FILESIZE) { + return false; + } + + context->file->seek(contextOffset + contextSize - RSC_TABLEINFO_SIZE); + + if (context->file->read(tableInfo, RSC_TABLEINFO_SIZE) != RSC_TABLEINFO_SIZE) { + return false; + } + + MemoryReadStreamEndian readS(tableInfo, RSC_TABLEINFO_SIZE, context->isBigEndian); + + resourceTableOffset = readS.readUint32(); + context->count = readS.readUint32(); + + // Check for sane table offset + if (resourceTableOffset != contextSize - RSC_TABLEINFO_SIZE - RSC_TABLEENTRY_SIZE * context->count) { + return false; + } + + // Load resource table + tableSize = RSC_TABLEENTRY_SIZE * context->count; + + tableBuffer = (byte *)malloc(tableSize); + + context->file->seek(resourceTableOffset + contextOffset, SEEK_SET); + + result = (context->file->read(tableBuffer, tableSize) == tableSize); + if (result) { + context->table = (ResourceData *)calloc(context->count, sizeof(*context->table)); + + MemoryReadStreamEndian readS1(tableBuffer, tableSize, context->isBigEndian); + + for (i = 0; i < context->count; i++) { + resourceData = &context->table[i]; + resourceData->offset = contextOffset + readS1.readUint32(); + resourceData->size = readS1.readUint32(); + //sanity check + if ((resourceData->offset > context->file->size()) || (resourceData->size > contextSize)) { + result = false; + break; + } + } + } + + free(tableBuffer); + return result; +} + +bool Resource::loadMacContext(ResourceContext *context) { + int32 macDataSize, macDataSizePad; + int32 macResSize, macResSizePad; + int32 macResOffset; + + uint32 macMapLength; + uint32 macDataLength; + uint32 macMapOffset; + uint32 macDataOffset; + + MacResMap macResMap; + MacResType *macResTypes; + + MacResType *macResType; + MacResource *macResource; + int i, j; + byte macNameLen; + bool notSagaContext = false; + + if (context->file->size() < RSC_MIN_FILESIZE + MAC_BINARY_HEADER_SIZE) { + return false; + } + + if (context->file->readByte() != 0) { + return false; + } + context->file->readByte(); //MAX Name Len + context->file->seek(74); + if (context->file->readByte() != 0) { + return false; + } + context->file->seek(82); + if (context->file->readByte() != 0) { + return false; + } + + macDataSize = context->file->readSint32BE(); + macResSize = context->file->readSint32BE(); + macDataSizePad = (((macDataSize + 127) >> 7) << 7); + macResSizePad = (((macResSize + 127) >> 7) << 7); + + macResOffset = MAC_BINARY_HEADER_SIZE + macDataSizePad; + context->file->seek(macResOffset); + + macDataOffset = context->file->readUint32BE() + macResOffset; + macMapOffset = context->file->readUint32BE() + macResOffset; + macDataLength = context->file->readUint32BE(); + macMapLength = context->file->readUint32BE(); + + if (macDataOffset >= context->file->size() || macMapOffset >= context->file->size() || + macDataLength + macMapLength > context->file->size()) { + return false; + } + + context->file->seek(macMapOffset + 22); + + macResMap.resAttr = context->file->readUint16BE(); + macResMap.typeOffset = context->file->readUint16BE(); + macResMap.nameOffset = context->file->readUint16BE(); + macResMap.numTypes = context->file->readUint16BE(); + macResMap.numTypes++; + + context->file->seek(macMapOffset + macResMap.typeOffset + 2); + + macResTypes = (MacResType *)calloc(macResMap.numTypes, sizeof(*macResTypes)); + + for (i = macResMap.numTypes, macResType = macResTypes; i > 0; i--, macResType++) { + macResType->id = context->file->readUint32BE(); + macResType->items = context->file->readUint16BE(); + macResType->offset = context->file->readUint16BE(); + macResType->items++; + macResType->resources = (MacResource*)calloc(macResType->items, sizeof(*macResType->resources)); + } + + for (i = macResMap.numTypes, macResType = macResTypes; i > 0; i--, macResType++) { + context->file->seek(macResType->offset + macMapOffset + macResMap.typeOffset); + + for (j = macResType->items, macResource = macResType->resources; j > 0; j--, macResource++) { + macResource->id = context->file->readUint16BE(); + macResource->nameOffset = context->file->readUint16BE(); + macResource->dataOffset = context->file->readUint32BE(); + macResSize = context->file->readUint32BE(); + + macResource->attr = macResource->dataOffset >> 24; + macResource->dataOffset &= 0xFFFFFF; + if (macResource->id > macResType->maxItemId) { + macResType->maxItemId = macResource->id; + } + } + + for (j = macResType->items, macResource = macResType->resources; j > 0; j--, macResource++) { + if (macResource->nameOffset != -1) { + context->file->seek(macResource->nameOffset + macMapOffset + macResMap.nameOffset); + macNameLen = context->file->readByte(); + context->file->read(macResource->name, macNameLen); + } + } + } + +// + for (i = macResMap.numTypes, macResType = macResTypes; i > 0; i--, macResType++) { + //getting offsets & sizes of midi + if (((context->fileType & GAME_MUSICFILE_GM) > 0) && (macResType->id == ID_MIDI)) { + + context->count = macResType->maxItemId + 1; + context->table = (ResourceData *)calloc(context->count, sizeof(*context->table)); + for (j = macResType->items, macResource = macResType->resources; j > 0; j--, macResource++) { + context->file->seek(macDataOffset + macResource->dataOffset); + context->table[macResource->id].size = context->file->readUint32BE(); + context->table[macResource->id].offset = context->file->pos(); + } + notSagaContext = true; + break; + } + } + +//free + for (i = 0; i < macResMap.numTypes; i++) { + free(macResTypes[i].resources); + } + free(macResTypes); + + if ((!notSagaContext) && (!loadSagaContext(context, MAC_BINARY_HEADER_SIZE, macDataSize))) { + return false; + } + + return true; +} + +bool Resource::loadContext(ResourceContext *context) { + size_t i; + int j; + GamePatchDescription *patchDescription; + ResourceData *resourceData; + uint16 subjectResourceType; + ResourceContext *subjectContext; + uint32 subjectResourceId; + uint32 patchResourceId; + ResourceData *subjectResourceData; + byte *tableBuffer; + size_t tableSize; + bool isMacBinary; + + if (!context->file->open(context->fileName)) { + return false; + } + + context->isBigEndian = _vm->isBigEndian(); + + if (context->fileType & GAME_SWAPENDIAN) + context->isBigEndian = !context->isBigEndian; + + isMacBinary = (context->fileType & GAME_MACBINARY) > 0; + context->fileType &= ~GAME_MACBINARY; + + if (isMacBinary) { + if (!loadMacContext(context)) { + return false; + } + } else { + if (!loadSagaContext(context, 0, context->file->size())) { + return false; + } + } + + //process internal patch files + if (GAME_PATCHFILE & context->fileType) { + subjectResourceType = ~GAME_PATCHFILE & context->fileType; + subjectContext = getContext((GameFileTypes)subjectResourceType); + if (subjectContext == NULL) { + error("Resource::loadContext() Subject context not found"); + } + loadResource(context, context->count - 1, tableBuffer, tableSize); + + MemoryReadStreamEndian readS2(tableBuffer, tableSize, context->isBigEndian); + for (i = 0; i < tableSize / 8; i++) { + subjectResourceId = readS2.readUint32(); + patchResourceId = readS2.readUint32(); + subjectResourceData = getResourceData(subjectContext, subjectResourceId); + resourceData = getResourceData(context, patchResourceId); + subjectResourceData->patchData = new PatchData(context->file); + subjectResourceData->offset = resourceData->offset; + subjectResourceData->size = resourceData->size; + } + + } + + //process external patch files + for (j = 0; j < _vm->getGameDescription()->patchesCount; j++) { + patchDescription = &_vm->getGameDescription()->patchDescriptions[j]; + if ((patchDescription->fileType & context->fileType) != 0) { + if (patchDescription->resourceId < context->count) { + resourceData = &context->table[patchDescription->resourceId]; + resourceData->patchData = new PatchData(patchDescription); + if (resourceData->patchData->_patchFile->open(patchDescription->fileName)) { + resourceData->offset = 0; + resourceData->size = resourceData->patchData->_patchFile->size(); + } else { + delete resourceData->patchData; + resourceData->patchData = NULL; + } + } + } + } + + return true; +} + +bool Resource::createContexts() { + int i; + ResourceContext *context; + _contextsCount = _vm->getGameDescription()->filesCount; + _contexts = (ResourceContext*)calloc(_contextsCount, sizeof(*_contexts)); + + for (i = 0; i < _contextsCount; i++) { + context = &_contexts[i]; + context->file = new Common::File(); + context->fileName = _vm->getGameDescription()->filesDescriptions[i].fileName; + context->fileType = _vm->getGameDescription()->filesDescriptions[i].fileType; + context->serial = 0; + + // IHNM has serveral different voice files, so we need to allow + // multiple resource contexts of the same type. We tell them + // apart by assigning each of the duplicates an unique serial + // number. The default behaviour when requesting a context will + // be to look for serial number 0. + + for (int j = i - 1; j >= 0; j--) { + if (_contexts[j].fileType & context->fileType) { + context->serial = _contexts[j].serial + 1; + break; + } + } + + if (!loadContext(context)) { + return false; + } + } + return true; +} + +void Resource::clearContexts() { + int i; + size_t j; + ResourceContext *context; + if (_contexts == NULL) { + return; + } + for(i = 0; i < _contextsCount; i++) { + context = &_contexts[i]; + delete context->file; + if (context->table != NULL) { + for(j = 0; j < context->count; j++) { + delete context->table[j].patchData; + } + } + free(context->table); + } + free(_contexts); + _contexts = NULL; +} + +uint32 Resource::convertResourceId(uint32 resourceId) { + + if (_vm->getGameType() == GType_ITE && _vm->isMacResources()) { + if (resourceId > 1537) { + return resourceId - 2; + } else { + if (resourceId == 1535 || resourceId == 1536) { + error ("Wrong resource number %d for Mac ITE", resourceId); + } + } + } + + return resourceId; +} + +void Resource::loadResource(ResourceContext *context, uint32 resourceId, byte*&resourceBuffer, size_t &resourceSize) { + Common::File *file; + uint32 resourceOffset; + ResourceData *resourceData; + + debug(8, "loadResource %d", resourceId); + + resourceData = getResourceData(context, resourceId); + + file = context->getFile(resourceData); + + resourceOffset = resourceData->offset; + resourceSize = resourceData->size; + + resourceBuffer = (byte*)malloc(resourceSize); + + file->seek((long)resourceOffset, SEEK_SET); + + if (file->read(resourceBuffer, resourceSize) != resourceSize) { + error("Resource::loadResource() failed to read"); + } +} + +static int metaResourceTable[] = { 0, 326, 517, 677, 805, 968, 1165, 0, 1271 }; + +void Resource::loadGlobalResources(int chapter, int actorsEntrance) { + if (chapter < 0) + chapter = 8; + + // TODO + //if (module.voiceLUT) + // free module.voiceLUT; + + // TODO: close chapter context, or rather reassign it in our case + + ResourceContext *resourceContext; + ResourceContext *soundContext; + int 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"); + } + + byte *resourcePointer; + size_t resourceLength; + + _vm->_resource->loadResource(resourceContext, metaResourceTable[chapter], + resourcePointer, resourceLength); + + if (resourceLength == 0) { + error("Resource::loadGlobalResources wrong metaResource"); + } + + MemoryReadStream metaS(resourcePointer, resourceLength); + + _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(); + + free(resourcePointer); + + _vm->_actor->loadActorList(actorsEntrance, _metaResource.actorCount, + _metaResource.actorsResourceID, _metaResource.protagStatesCount, + _metaResource.protagStatesResourceID); + + _vm->_actor->_protagonist->_sceneNumber = _metaResource.sceneIndex; + + _vm->_actor->_objectsStrings.freeMem(); + + _vm->_resource->loadResource(resourceContext, _metaResource.objectsStringsResourceID, resourcePointer, resourceLength); + _vm->loadStrings(_vm->_actor->_objectsStrings, resourcePointer, resourceLength); + free(resourcePointer); + + if (chapter >= _vm->_sndRes->_fxTableIDsLen) { + 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], + resourcePointer, resourceLength); + + if (resourceLength == 0) { + error("Resource::loadGlobalResources Can't load sound effects for current track"); + } + + free(_vm->_sndRes->_fxTable); + + _vm->_sndRes->_fxTableLen = resourceLength / 4; + _vm->_sndRes->_fxTable = (FxTable *)malloc(sizeof(FxTable) * _vm->_sndRes->_fxTableLen); + + MemoryReadStream fxS(resourcePointer, resourceLength); + + for (i = 0; i < _vm->_sndRes->_fxTableLen; i++) { + _vm->_sndRes->_fxTable[i].res = fxS.readSint16LE(); + _vm->_sndRes->_fxTable[i].vol = fxS.readSint16LE(); + } + free(resourcePointer); + + _vm->_interface->_defPortraits.freeMem(); + _vm->_sprite->loadList(_metaResource.protagFaceSpritesID, _vm->_interface->_defPortraits); + + _vm->_actor->_actorsStrings.freeMem(); + + _vm->_resource->loadResource(resourceContext, _metaResource.actorsStringsResourceID, resourcePointer, resourceLength); + _vm->loadStrings(_vm->_actor->_actorsStrings, resourcePointer, resourceLength); + free(resourcePointer); + + _vm->_sprite->_inventorySprites.freeMem(); + _vm->_sprite->loadList(_metaResource.inventorySpritesID, _vm->_sprite->_inventorySprites); + + _vm->_sprite->_mainSprites.freeMem(); + _vm->_sprite->loadList(_metaResource.mainSpritesID, _vm->_sprite->_mainSprites); + + _vm->_actor->loadObjList(_metaResource.objectCount, _metaResource.objectsResourceID); + + _vm->_resource->loadResource(resourceContext, _metaResource.cutawayListResourceID, resourcePointer, resourceLength); + + if (resourceLength == 0) { + error("Resource::loadGlobalResources Can't load cutaway list"); + } + + _vm->_anim->loadCutawayList(resourcePointer, resourceLength); + + _vm->_resource->loadResource(resourceContext, _metaResource.songTableID, resourcePointer, resourceLength); + + if (resourceLength == 0) { + error("Resource::loadGlobalResources Can't load songs list for current track"); + } + + free(_vm->_music->_songTable); + + _vm->_music->_songTableLen = resourceLength / 4; + _vm->_music->_songTable = (int32 *)malloc(sizeof(int32) * _vm->_music->_songTableLen); + + MemoryReadStream songS(resourcePointer, resourceLength); + + for (i = 0; i < _vm->_music->_songTableLen; i++) + _vm->_music->_songTable[i] = songS.readSint32LE(); + free(resourcePointer); + + int voiceLUTResourceID = 0; + + _vm->_script->_globalVoiceLUT.freeMem(); + + switch (chapter) { + case 1: + _vm->_sndRes->setVoiceBank(1); + voiceLUTResourceID = 23; + break; + case 2: + _vm->_sndRes->setVoiceBank(2); + voiceLUTResourceID = 24; + break; + case 3: + _vm->_sndRes->setVoiceBank(3); + voiceLUTResourceID = 25; + break; + case 4: + _vm->_sndRes->setVoiceBank(4); + voiceLUTResourceID = 26; + break; + case 5: + _vm->_sndRes->setVoiceBank(5); + voiceLUTResourceID = 27; + break; + case 6: + _vm->_sndRes->setVoiceBank(6); + voiceLUTResourceID = 28; + break; + case 7: + break; + case 8: + _vm->_sndRes->setVoiceBank(0); + voiceLUTResourceID = 22; + break; + } + + if (voiceLUTResourceID) { + _vm->_resource->loadResource(resourceContext, voiceLUTResourceID, resourcePointer, resourceLength); + _vm->_script->loadVoiceLUT(_vm->_script->_globalVoiceLUT, resourcePointer, resourceLength); + free(resourcePointer); + } + + _vm->_spiritualBarometer = 0; + _vm->_scene->setChapterNumber(chapter); +} + +} // End of namespace Saga diff --git a/engines/saga/rscfile.h b/engines/saga/rscfile.h new file mode 100644 index 0000000000..434a6d9a96 --- /dev/null +++ b/engines/saga/rscfile.h @@ -0,0 +1,173 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// RSC Resource file management header file + +#ifndef SAGA_RSCFILE_H__ +#define SAGA_RSCFILE_H__ + +#include "common/file.h" + +namespace Saga { + +#define MAC_BINARY_HEADER_SIZE 128 +#define RSC_TABLEINFO_SIZE 8 +#define RSC_TABLEENTRY_SIZE 8 + +#define RSC_MIN_FILESIZE (RSC_TABLEINFO_SIZE + RSC_TABLEENTRY_SIZE + 1) + +struct PatchData { + bool _deletePatchFile; + Common::File *_patchFile; + GamePatchDescription *_patchDescription; + + PatchData(GamePatchDescription *patchDescription): _patchDescription(patchDescription), _deletePatchFile(true) { + _patchFile = new Common::File(); + } + PatchData(Common::File *patchFile): _patchDescription(NULL), _patchFile(patchFile), _deletePatchFile(false) { + } + + ~PatchData() { + if (_deletePatchFile) { + delete _patchFile; + } + } +}; + +struct ResourceData { + size_t offset; + size_t size; + PatchData *patchData; + void fillSoundPatch(const GameSoundInfo *&soundInfo) { + if (patchData != NULL) { + if (patchData->_patchDescription != NULL) { + if (patchData->_patchDescription->soundInfo != NULL) { + soundInfo = patchData->_patchDescription->soundInfo; + } + } + } + } +}; + +struct ResourceContext { + const char *fileName; + uint16 fileType; + Common::File *file; + int serial; + + bool isBigEndian; + ResourceData *table; + size_t count; + + Common::File *getFile(ResourceData *resourceData) const { + if (resourceData->patchData != NULL) { + return resourceData->patchData->_patchFile; + } else { + return file; + } + } +}; + +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); + ~Resource(); + bool createContexts(); + void clearContexts(); + void loadResource(ResourceContext *context, uint32 resourceId, byte*&resourceBuffer, size_t &resourceSize); + size_t getResourceSize(ResourceContext *context, uint32 resourceId); + uint32 convertResourceId(uint32 resourceId); + + void loadGlobalResources(int chapter, int actorsEntrance); + + ResourceContext *getContext(uint16 fileType, int serial = 0) { + int i; + for (i = 0; i < _contextsCount; i++) { + if ((_contexts[i].fileType & fileType) && _contexts[i].serial == serial) { + return &_contexts[i]; + } + } + return NULL; + } + + bool validResourceId(ResourceContext *context, uint32 resourceId) const { + return (resourceId < context->count); + } + + size_t getResourceSize(ResourceContext *context, uint32 resourceId) const { + return getResourceData(context, resourceId)->size; + } + + size_t getResourceOffset(ResourceContext *context, uint32 resourceId) const { + return getResourceData(context, resourceId)->offset; + } + + ResourceData *getResourceData(ResourceContext *context, uint32 resourceId) const { + if (!validResourceId(context, resourceId)) { + warning("Resource::getResourceData() wrong resourceId %d", resourceId); + assert(0); + } + return &context->table[resourceId]; + } + +private: + SagaEngine *_vm; + ResourceContext *_contexts; + int _contextsCount; + + bool loadContext(ResourceContext *context); + bool loadMacContext(ResourceContext *context); + bool loadSagaContext(ResourceContext *context, uint32 contextOffset, uint32 contextSize); + + +public: + MetaResource _metaResource; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp new file mode 100644 index 0000000000..4adda480c6 --- /dev/null +++ b/engines/saga/saga.cpp @@ -0,0 +1,505 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ +#include "common/stdafx.h" + +#include "base/gameDetector.h" +#include "base/plugins.h" +#include "backends/fs/fs.h" + +#include "common/file.h" +#include "common/config-manager.h" +#include "common/system.h" + +#include "sound/mixer.h" + +#include "saga/saga.h" + +#include "saga/rscfile.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" +#include "saga/resnames.h" + +static const GameSettings saga_games[] = { + {"ite", "Inherit the Earth", 0}, + {"ihnm", "I Have No Mouth and I Must Scream", GF_DEFAULT_TO_1X_SCALER }, + {0, 0, 0} +}; + +GameList Engine_SAGA_gameList() { + GameList games; + const GameSettings *g = saga_games; + + while (g->gameid) { + games.push_back(*g); + g++; + } + + return games; +} + +DetectedGameList Engine_SAGA_detectGames(const FSList &fslist) { + return Saga::GAME_ProbeGame(fslist); +} + +Engine *Engine_SAGA_create(GameDetector *detector, OSystem *syst) { + return new Saga::SagaEngine(detector, syst); +} + +REGISTER_PLUGIN(SAGA, "SAGA Engine") + +namespace Saga { + +#define MAX_TIME_DELTA 100 + +SagaEngine::SagaEngine(GameDetector *detector, OSystem *syst) + : Engine(syst), + _targetName(detector->_targetName) { + + _leftMouseButtonPressed = _rightMouseButtonPressed = false; + + _console = NULL; + _quit = false; + + _resource = NULL; + _sndRes = NULL; + _events = NULL; + _font = NULL; + _sprite = NULL; + _anim = NULL; + _script = NULL; + _interface = NULL; + _actor = NULL; + _palanim = NULL; + _scene = NULL; + _isoMap = NULL; + _gfx = NULL; + _console = NULL; + _render = NULL; + _music = NULL; + _sound = NULL; + _puzzle = NULL; + + _frameCount = 0; + _globalFlags = 0; + memset(_ethicsPoints, 0, sizeof(_ethicsPoints)); + + // The Linux version of Inherit the Earth puts all data files in an + // 'itedata' sub-directory, except for voices.rsc + Common::File::addDefaultDirectory(_gameDataPath + "itedata/"); + + // The Windows version of Inherit the Earth puts various data files in + // other subdirectories. + Common::File::addDefaultDirectory(_gameDataPath + "graphics/"); + Common::File::addDefaultDirectory(_gameDataPath + "music/"); + Common::File::addDefaultDirectory(_gameDataPath + "sound/"); + + // 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 + Common::File::addDefaultDirectory(_gameDataPath + "game/itedata/"); + + // Mac CD Wyrmkeep + Common::File::addDefaultDirectory(_gameDataPath + "patch/"); + + // Setup mixer + if (!_mixer->isReady()) { + warning("Sound initialization failed."); + } + + _displayClip.left = _displayClip.top = 0; +} + +SagaEngine::~SagaEngine() { + if (_scene != NULL) { + if (_scene->isSceneLoaded()) { + _scene->endScene(); + } + } + + delete _puzzle; + delete _sndRes; + delete _events; + delete _font; + delete _sprite; + delete _anim; + delete _script; + delete _interface; + delete _actor; + delete _palanim; + delete _scene; + delete _isoMap; + delete _render; + delete _music; + delete _sound; + delete _gfx; + delete _console; + + delete _resource; +} + +void SagaEngine::errorString(const char *buf1, char *buf2) { + strcpy(buf2, buf1); +} + +int SagaEngine::init(GameDetector &detector) { + _soundVolume = ConfMan.getInt("sfx_volume") / 25; + _musicVolume = ConfMan.getInt("music_volume") / 25; + _subtitlesEnabled = ConfMan.getBool("subtitles"); + _readingSpeed = ConfMan.getInt("talkspeed"); + _copyProtection = ConfMan.getBool("copy_protection"); + + if (_readingSpeed > 3) + _readingSpeed = 0; + + _resource = new Resource(this); + + // Add some default directories + // Win32 demo & full game + Common::File::addDefaultDirectory("graphics"); + Common::File::addDefaultDirectory("music"); + Common::File::addDefaultDirectory("sound"); + + // Linux demo + Common::File::addDefaultDirectory("itedata"); + + // Mac demos & full game + Common::File::addDefaultDirectory("patch"); + + // Process command line + + // Detect game and open resource files + if (!initGame()) { + return FAILURE; + } + + // Initialize engine modules + _sndRes = new SndRes(this); + _events = new Events(this); + _font = new Font(this); + _sprite = new Sprite(this); + _anim = new Anim(this); + _script = new Script(this); + _interface = new Interface(this); // requires script module + _scene = new Scene(this); + _actor = new Actor(this); + _palanim = new PalAnim(this); + _isoMap = new IsoMap(this); + _puzzle = new Puzzle(this); + + // System initialization + + _previousTicks = _system->getMillis(); + + // Initialize graphics + _gfx = new Gfx(this, _system, getDisplayWidth(), getDisplayHeight(), detector); + + // Graphics driver should be initialized before console + _console = new Console(this); + + // Graphics should be initialized before music + int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); + bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); + bool adlib = (midiDriver == MD_ADLIB); + + MidiDriver *driver = MidiDriver::createMidi(midiDriver); + if (native_mt32) + driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + + _music = new Music(this, _mixer, driver, _musicVolume); + _music->setNativeMT32(native_mt32); + _music->setAdlib(adlib); + + if (!_musicVolume) { + debug(1, "Music disabled."); + } + + _render = new Render(this, _system); + if (!_render->initialized()) { + return FAILURE; + } + + // Initialize system specific sound + _sound = new Sound(this, _mixer, _soundVolume); + if (!_soundVolume) { + debug(1, "Sound disabled."); + } + + _interface->converseInit(); + _script->setVerb(_script->getVerbType(kVerbWalkTo)); + + _music->setVolume(-1, 1); + + _gfx->initPalette(); + + // FIXME: This is the ugly way of reducing redraw overhead. It works + // well for 320x200 but it's unclear how well it will work for + // 640x480. + + if (getGameType() == GType_ITE) + _system->setFeatureState(OSystem::kFeatureAutoComputeDirtyRects, true); + + return SUCCESS; +} + +int SagaEngine::go() { + 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 (getGameType() == GType_ITE) + _interface->addToInventory(_actor->objIndexToId(ITE_OBJ_MAGIC_HAT)); + _scene->changeScene(ConfMan.getInt("boot_param"), 0, kTransitionNoFade); + } else if (ConfMan.hasKey("save_slot")) { + // First scene sets up palette + _scene->changeScene(getStartSceneNumber(), 0, kTransitionNoFade); + _events->handleEvents(0); // Process immediate events + + char *fileName; + fileName = calcSaveFileName(ConfMan.getInt("save_slot")); + load(fileName); + _interface->setMode(kPanelMain); + } else { + _framesEsc = 0; + _scene->startScene(); + } + + uint32 currentTicks; + + while (!_quit) { + if (_console->isAttached()) + _console->onFrame(); + + 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 is actorless, we do it here + if (_puzzle->isActive()) { + _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); + } + + return 0; +} + +void SagaEngine::loadStrings(StringsTable &stringsTable, const byte *stringsPointer, size_t stringsLength) { + uint16 stringsCount; + size_t offset; + int i; + + if (stringsLength == 0) { + error("SagaEngine::loadStrings() Error loading strings list resource"); + } + + stringsTable.stringsPointer = (byte*)malloc(stringsLength); + memcpy(stringsTable.stringsPointer, stringsPointer, stringsLength); + + + MemoryReadStreamEndian scriptS(stringsTable.stringsPointer, stringsLength, isBigEndian()); //TODO: get endianess from context + + offset = scriptS.readUint16(); + stringsCount = offset / 2; + stringsTable.strings = (const char **)malloc(stringsCount * sizeof(*stringsTable.strings)); + i = 0; + scriptS.seek(0); + while (i < stringsCount) { + offset = scriptS.readUint16(); + if (offset == stringsLength) { + stringsCount = i; + stringsTable.strings = (const char **)realloc(stringsTable.strings, stringsCount * sizeof(*stringsTable.strings)); + break; + } + if (offset > stringsLength) { + error("SagaEngine::loadStrings wrong strings table"); + } + stringsTable.strings[i] = (const char *)stringsTable.stringsPointer + offset; + debug(9, "string[%i]=%s", i, stringsTable.strings[i]); + i++; + } + stringsTable.stringsCount = stringsCount; +} + +const char *SagaEngine::getObjectName(uint16 objectId) { + ActorData *actor; + ObjectData *obj; + const HitZone *hitZone; + switch (objectTypeId(objectId)) { + case kGameObjectObject: + obj = _actor->getObj(objectId); + if (getGameType() == GType_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)); + return _scene->_sceneStrings.getString(hitZone->getNameIndex()); + } + warning("SagaEngine::getObjectName name not found for 0x%X", objectId); + return NULL; +} + +const char *SagaEngine::getTextString(int textStringId) { + const char *string; + int lang = (getLanguage() == Common::DE_DEU) ? 1 : 0; + + 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(kVerbPickUp)) { + textString = getTextString(kTextICantPickup); + soundResourceId = RID_BOAR_VOICE_007; + } else + if (verb == _script->getVerbType(kVerbLookAt)) { + textString = getTextString(kTextNothingSpecial); + soundResourceId = RID_BOAR_VOICE_006; + } + if (verb == _script->getVerbType(kVerbOpen)) { + textString = getTextString(kTextNoPlaceToOpen); + soundResourceId = RID_BOAR_VOICE_000; + } + if (verb == _script->getVerbType(kVerbClose)) { + textString = getTextString(kTextNoOpening); + soundResourceId = RID_BOAR_VOICE_002; + } + if (verb == _script->getVerbType(kVerbUse)) { + textString = getTextString(kTextDontKnow); + soundResourceId = RID_BOAR_VOICE_005; + } +} + +ColorId SagaEngine::KnownColor2ColorId(KnownColor knownColor) { + ColorId colorId = kITEColorTransBlack; + + if (getGameType() == GType_ITE) { + switch (knownColor) { + case(kKnownColorTransparent): + colorId = kITEColorTransBlack; + break; + + case (kKnownColorBrightWhite): + colorId = kITEColorBrightWhite; + break; + case (kKnownColorBlack): + colorId = kITEColorBlack; + break; + + case (kKnownColorSubtitleTextColor): + colorId = (ColorId)255; + break; + case (kKnownColorVerbText): + colorId = kITEColorBlue; + break; + case (kKnownColorVerbTextShadow): + colorId = kITEColorBlack; + break; + case (kKnownColorVerbTextActive): + colorId = (ColorId)96; + break; + + default: + error("SagaEngine::KnownColor2ColorId unknown color %i", knownColor); + } + } else if (getGameType() == GType_IHNM) { + switch (knownColor) + { + case(kKnownColorTransparent): + colorId = kITEColorTransBlack; + break; + + case (kKnownColorBlack): + colorId = kIHNMColorBlack; + break; + + case (kKnownColorVerbText): + colorId = (ColorId)253; + break; + case (kKnownColorVerbTextShadow): + colorId = (ColorId)15; + break; + case (kKnownColorVerbTextActive): + colorId = (ColorId)252; + break; + + default: + error("SagaEngine::KnownColor2ColorId unknown color %i", knownColor); + } + } + return colorId; +} + + +} // End of namespace Saga diff --git a/engines/saga/saga.h b/engines/saga/saga.h new file mode 100644 index 0000000000..f2111265bd --- /dev/null +++ b/engines/saga/saga.h @@ -0,0 +1,741 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SAGA_H +#define SAGA_H + +#include "base/engine.h" +#include "base/plugins.h" + +#include "common/stream.h" + +#include "saga/gfx.h" +#include "saga/list.h" + +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; + +struct ResourceContext; +struct StringList; + +//#define MIN_IMG_RLECODE 3 +//#define MODEX_SCANLINE_LIMIT 200 //TODO: remove + +#define SAGA_IMAGE_DATA_OFFSET 776 +#define SAGA_IMAGE_HEADER_LEN 8 + +#define MAXPATH 512 //TODO: remove + +#define SAVE_TITLE_SIZE 28 +#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 OBJ_SPRITE_BASE 9 + +#define memoryError(Place) error("%s Memory allocation error.", Place) + +enum ERRORCODE { + MEM = -2,//todo: remove + FAILURE = -1, + SUCCESS = 0 +}; + +enum SAGAGameType { + GType_ITE, + GType_IHNM +}; + +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) +}; + + +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 { + kTextWalkTo, + kTextLookAt, + kTextPickUp, + kTextTalkTo, + kTextOpen, + kTextClose, + kTextUse, + kTextGive, + 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 +}; + +struct ImageHeader { + int width; + int height; +}; + +struct StringsTable { + byte *stringsPointer; + int stringsCount; + const char **strings; + + const char *getString(int index) const { + if ((stringsCount <= index) || (index < 0)) { + error("StringList::getString wrong index 0x%X (%d)", index, stringsCount); + } + return strings[index]; + } + + void freeMem() { + free(strings); + free(stringsPointer); + memset(this, 0, sizeof(*this)); + } + + StringsTable() { + memset(this, 0, sizeof(*this)); + } + ~StringsTable() { + freeMem(); + } +}; + +enum GameIds { + // Dreamers Guild + GID_ITE_DEMO_G = 0, + GID_ITE_DISK_G, + GID_ITE_DISK_G2, + GID_ITE_CD_G, + GID_ITE_CD_G2, + GID_ITE_MACCD_G, + + // Wyrmkeep + GID_ITE_CD, // data for Win rerelease is same as in old DOS + GID_ITE_WINCD, // but it has a bunch of patch files + GID_ITE_MACCD, + GID_ITE_LINCD, + GID_ITE_MULTICD, // Wyrmkeep combined Windows/Mac/Linux version + GID_ITE_WINDEMO1, // older Wyrmkeep windows demo + GID_ITE_MACDEMO1, // older Wyrmkeep mac demo + GID_ITE_LINDEMO, + GID_ITE_WINDEMO2, + GID_ITE_MACDEMO2, + + // German + GID_ITE_DISK_DE, + GID_ITE_DISK_DE2, + GID_ITE_AMIGACD_DE, // TODO + GID_ITE_OLDMAC_DE, // TODO + GID_ITE_AMIGA_FL_DE,// TODO + GID_ITE_CD_DE, // reported by mld. Bestsellergamers cover disk + GID_ITE_CD_DE2, + GID_ITE_AMIGA_AGA_DEMO, // TODO + GID_ITE_AMIGA_ECS_DEMO, // TODO + + GID_IHNM_DEMO, + GID_IHNM_CD, + GID_IHNM_CD_DE, // reported by mld. German retail + GID_IHNM_CD_ES, + GID_IHNM_CD_RU, + GID_IHNM_CD_FR +}; + +enum GameFileTypes { + GAME_RESOURCEFILE = 1 << 0, + GAME_SCRIPTFILE = 1 << 1, + GAME_SOUNDFILE = 1 << 2, + GAME_VOICEFILE = 1 << 3, + GAME_DEMOFILE = 1 << 4, + GAME_MUSICFILE = 1 << 5, + GAME_MUSICFILE_GM = 1 << 6, + GAME_MUSICFILE_FM = 1 << 7, + GAME_PATCHFILE = 1 << 8, + GAME_MACBINARY = 1 << 9, + GAME_SWAPENDIAN = 1 << 10 +}; + +enum GameSoundTypes { + kSoundPCM = 0, + kSoundVOX = 1, + kSoundVOC = 2, + kSoundWAV = 3, + kSoundMacPCM = 4 +}; + +enum GameFeatures { + GF_BIG_ENDIAN_DATA = 1 << 0, + GF_WYRMKEEP = 1 << 1, + GF_CD_FX = 1 << 2, + GF_SCENE_SUBSTITUTES = 1 << 3 +}; + +enum ColorId { + kITEColorTransBlack = 0x00, + kITEColorBrightWhite = 0x01, + kITEColorWhite = 0x02, + kITEColorLightGrey = 0x04, + kITEColorGrey = 0x0a, + kITEColorDarkGrey = 0x0b, + kITEColorDarkGrey0C = 0x0C, + kITEColorBlack = 0x0f, + kITEColorRed = 0x65, + kITEColorDarkBlue8a = 0x8a, + kITEColorBlue89 = 0x89, + kITEColorLightBlue92 = 0x92, + kITEColorBlue = 0x93, + kITEColorLightBlue94 = 0x94, + kITEColorLightBlue96 = 0x96, + kITEColorGreen = 0xba, + + kIHNMColorBlack = 0xfa, + kIHNMColorPortrait = 0xfe +}; + +enum KnownColor { + kKnownColorTransparent, + kKnownColorBrightWhite, + kKnownColorBlack, + + kKnownColorSubtitleTextColor, + kKnownColorVerbText, + kKnownColorVerbTextShadow, + kKnownColorVerbTextActive +}; + +struct GameSoundInfo { + GameSoundTypes resourceType; + long frequency; + int sampleBits; + bool stereo; + bool isBigEndian; + bool isSigned; +}; + +struct GameFontDescription { + uint32 fontResourceId; +}; + +struct GameResourceDescription { + uint32 sceneLUTResourceId; + uint32 moduleLUTResourceId; + uint32 mainPanelResourceId; + uint32 conversePanelResourceId; + uint32 optionPanelResourceId; + uint32 mainSpritesResourceId; + uint32 mainPanelSpritesResourceId; + uint32 defaultPortraitsResourceId; + uint32 mainStringsResourceId; + uint32 actorsStringsResourceId; +}; + +struct GameFileDescription { + const char *fileName; + uint16 fileType; +}; + +struct GamePatchDescription { + const char *fileName; + uint16 fileType; + uint32 resourceId; + GameSoundInfo *soundInfo; +}; + +struct PanelButton { + PanelButtonType type; + int xOffset; + int yOffset; + int width; + int height; + int id; + uint16 ascii; + int state; + int upSpriteNumber; + int downSpriteNumber; + int overSpriteNumber; +}; + +struct GameDisplayInfo { + int logicalWidth; + int logicalHeight; + + 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 saveReminderSecondSpriteNumber; + + 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; + uint 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; +}; + + +struct GameDescription { + const char *name; + SAGAGameType gameType; + GameIds gameId; + const char *title; + GameDisplayInfo *gameDisplayInfo; + int startSceneNumber; + GameResourceDescription *resourceDescription; + int filesCount; + GameFileDescription *filesDescriptions; + int fontsCount; + GameFontDescription *fontDescriptions; + GameSoundInfo *voiceInfo; + GameSoundInfo *sfxInfo; + GameSoundInfo *musicInfo; + int patchesCount; + GamePatchDescription *patchDescriptions; + uint32 features; + Common::Language language; + Common::Platform platform; + + GameSettings toGameSettings() const { + GameSettings dummy = { name, title, features }; + return dummy; + } +}; + +struct SaveFileData { + char name[SAVE_TITLE_SIZE]; + uint slotNumber; +}; + +struct SaveGameHeader { + uint32 type; + uint32 size; + uint32 version; + char name[SAVE_TITLE_SIZE]; +}; + +inline int ticksToMSec(int tick) { + return tick * 1000 / kScriptTimeTicksPerSecond; +} + +inline int clamp(int minValue, int value, int maxValue) { + if (value <= minValue) { + return minValue; + } else { + if (value >= maxValue) { + return maxValue; + } else { + return value; + } + } +} + +inline int integerCompare(int i1, int i2) { + return ((i1) > (i2) ? 1 : ((i1) < (i2) ? -1 : 0)); +} + +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); +} + + +DetectedGameList GAME_ProbeGame(const FSList &fslist, int **matches = NULL); + +class SagaEngine : public Engine { + friend class Scene; + + void errorString(const char *buf_input, char *buf_output); + +protected: + int go(); + int init(GameDetector &detector); +public: + SagaEngine(GameDetector * detector, OSystem * syst); + virtual ~SagaEngine(); + void shutDown() { _quit = true; } + + void save(const char *fileName, const char *saveName); + void load(const char *fileName); + uint32 getCurrentLoadVersion() { + return _saveHeader.version; + } + void fillSaveList(); + char *calcSaveFileName(uint slotNumber); + + SaveFileData *getSaveFile(uint idx); + uint getSaveSlotNumber(uint idx); + uint getNewSaveSlotNumber(); + bool locateSaveFile(char *saveName, uint &titleNumber); + bool isSaveListFull() const { + return _saveFilesMaxCount == _saveFilesCount; + } + uint getSaveFilesCount() const { + return isSaveListFull() ? _saveFilesCount : _saveFilesCount + 1; + } + + int16 _framesEsc; + + uint32 _globalFlags; + int16 _ethicsPoints[8]; + int _spiritualBarometer; + + int _soundVolume; + int _musicVolume; + bool _subtitlesEnabled; + int _readingSpeed; + + bool _copyProtection; + + 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: + int decodeBGImageRLE(const byte *inbuf, size_t inbuf_len, byte *outbuf, size_t outbuf_len); + int flipImage(byte *img_buf, int columns, int scanlines); + int unbankBGImage(byte *dest_buf, const byte *src_buf, int columns, int scanlines); + uint32 _previousTicks; + +public: + int decodeBGImage(const byte *image_data, size_t image_size, + byte **output_buf, size_t *output_buf_len, int *w, int *h, bool flip = false); + const byte *getImagePal(const byte *image_data, size_t image_size); + void loadStrings(StringsTable &stringsTable, const byte *stringsPointer, size_t stringsLength); + + const char *getObjectName(uint16 objectId); +public: + int processInput(void); + const Point &mousePos() const { + return _mousePos; + } + + const bool leftMouseButtonPressed() const { + return _leftMouseButtonPressed; + } + + const bool rightMouseButtonPressed() const { + return _rightMouseButtonPressed; + } + + const bool mouseButtonPressed() const { + return _leftMouseButtonPressed || _rightMouseButtonPressed; + } + + private: + Common::String _targetName; + + uint _saveFilesMaxCount; + uint _saveFilesCount; + SaveFileData _saveFiles[MAX_SAVES]; + bool _saveMarks[MAX_SAVES]; + SaveGameHeader _saveHeader; + + Point _mousePos; + bool _leftMouseButtonPressed; + bool _rightMouseButtonPressed; + + bool _quit; + +//current game description + int _gameNumber; + GameDescription *_gameDescription; + Common::Rect _displayClip; + +protected: + GameDisplayInfo _gameDisplayInfo; + +public: + int32 _frameCount; + +public: + bool initGame(void); +public: + const GameDescription *getGameDescription() const { return _gameDescription; } + const bool isBigEndian() const { return (_gameDescription->features & GF_BIG_ENDIAN_DATA) != 0; } + const bool isMacResources() const { return (getPlatform() == Common::kPlatformMacintosh); } + const GameResourceDescription *getResourceDescription() { return _gameDescription->resourceDescription; } + const GameSoundInfo *getVoiceInfo() const { return _gameDescription->voiceInfo; } + const GameSoundInfo *getSfxInfo() const { return _gameDescription->sfxInfo; } + const GameSoundInfo *getMusicInfo() const { return _gameDescription->musicInfo; } + + const GameFontDescription *getFontDescription(int index) { + assert(index < _gameDescription->fontsCount); + return &_gameDescription->fontDescriptions[index]; + } + int getFontsCount() const { return _gameDescription->fontsCount; } + + int getGameId() const { return _gameDescription->gameId; } + int getGameType() const { return _gameDescription->gameType; } + uint32 getFeatures() const { return _gameDescription->features; } + Common::Language getLanguage() const { return _gameDescription->language; } + Common::Platform getPlatform() const { return _gameDescription->platform; } + int getGameNumber() const { return _gameNumber; } + int getStartSceneNumber() const { return _gameDescription->startSceneNumber; } + + + const Common::Rect &getDisplayClip() const { return _displayClip;} + int getDisplayWidth() const { return _gameDisplayInfo.logicalWidth; } + int getDisplayHeight() const { return _gameDisplayInfo.logicalHeight;} + const GameDisplayInfo & getDisplayInfo() { return _gameDisplayInfo; } + + const char *getTextString(int textStringId); + void getExcuseInfo(int verb, const char *&textString, int &soundResourceId); + +private: + +public: + ColorId KnownColor2ColorId(KnownColor knownColor); +}; + + +} // End of namespace Saga + +#endif diff --git a/engines/saga/saveload.cpp b/engines/saga/saveload.cpp new file mode 100644 index 0000000000..761ae49521 --- /dev/null +++ b/engines/saga/saveload.cpp @@ -0,0 +1,299 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/stdafx.h" + +#include "common/config-manager.h" +#include "common/savefile.h" +#include "common/system.h" +#include "common/file.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/resnames.h" +#include "saga/scene.h" +#include "saga/script.h" + +#define CURRENT_SAGA_VER 5 + +namespace Saga { + +static SaveFileData emptySlot = { + "", 0 +}; + +//TODO: +// - delete savegame + +char* SagaEngine::calcSaveFileName(uint slotNumber) { + static char name[MAX_FILE_NAME]; + sprintf(name, "%s.s%02d", _targetName.c_str(), slotNumber); + return name; +} + +SaveFileData *SagaEngine::getSaveFile(uint idx) { + if (idx >= _saveFilesMaxCount) { + error("getSaveFileName wrong idx"); + } + if (isSaveListFull()) { + return &_saveFiles[_saveFilesCount - idx - 1]; + } else { + if (!emptySlot.name[0]) + strcpy(emptySlot.name, getTextString(kTextNewSave)); + + 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() { + uint i, j; + bool found; + if (isSaveListFull()) { + error("getNewSaveSlotNumber save list is full"); + } + for (i = 0; i < MAX_SAVES; i++) { + if (_saveMarks[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"); +} + +void SagaEngine::fillSaveList() { + int i; + Common::InSaveFile *in; + char *name; + + name = calcSaveFileName(MAX_SAVES); + name[strlen(name) - 2] = 0; + _saveFileMan->listSavefiles(name, _saveMarks, MAX_SAVES); + + _saveFilesMaxCount = 0; + for (i = 0; i < MAX_SAVES; i++) { + if (_saveMarks[i]) { + _saveFilesMaxCount++; + } + _saveFiles[i].name[0] = 0; + _saveFiles[i].slotNumber = (uint)-1; + } + + _saveFilesCount = 0; + + i = 0; + while (i < MAX_SAVES) { + if (_saveMarks[i]) { + name = calcSaveFileName(i); + if ((in = _saveFileMan->openForLoading(name)) != NULL) { + in->read(&_saveHeader, sizeof(_saveHeader)); + + if (_saveHeader.type != MKID('SAGA')) { + error("SagaEngine::load wrong format"); + } + strcpy(_saveFiles[_saveFilesCount].name, _saveHeader.name); + _saveFiles[_saveFilesCount].slotNumber = i; + delete in; + _saveFilesCount++; + } + } + i++; + } +/* 4debug + for (i = 0; i < 14; i++) { + sprintf(_saveFiles[i].name,"test%i", i); + _saveFiles[i].slotNumber = i; + } + _saveFilesCount = 14; + _saveFilesMaxCount = 14; + */ +} + + +#define TITLESIZE 80 +void SagaEngine::save(const char *fileName, const char *saveName) { + Common::OutSaveFile *out; + char title[TITLESIZE]; + + if (!(out = _saveFileMan->openForSaving(fileName))) { + return; + } + + _saveHeader.type = MKID('SAGA'); + _saveHeader.size = 0; + _saveHeader.version = TO_LE_32(CURRENT_SAGA_VER); + strncpy(_saveHeader.name, saveName, SAVE_TITLE_SIZE); + + out->write(&_saveHeader, sizeof(_saveHeader)); + + // Original game title + memset(title, 0, TITLESIZE); + strncpy(title, getGameDescription()->title, TITLESIZE); + out->write(title, TITLESIZE); + + // Surrounding scene + out->writeSint32LE(_scene->getOutsetSceneNumber()); + + // Inset scene + out->writeSint32LE(_scene->currentSceneNumber()); + + if (getGameType() != GType_ITE) { + out->writeUint32LE(_globalFlags); + for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++) + out->writeSint16LE(_ethicsPoints[i]); + } + + _interface->saveState(out); + + _actor->saveState(out); + + out->writeSint16LE(_script->_commonBufferSize); + + out->write(_script->_commonBuffer, _script->_commonBufferSize); + + out->writeSint16LE(_isoMap->getMapPosition().x); + out->writeSint16LE(_isoMap->getMapPosition().y); + + out->flush(); + + // TODO: Check out->ioFailed() + + delete out; +} + +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; + } + + in->read(&_saveHeader, sizeof(_saveHeader)); + + _saveHeader.size = FROM_LE_32(_saveHeader.size); + _saveHeader.version = FROM_LE_32(_saveHeader.version); + + // This save was written in native endianness (fix that, so warning will show up) + if (_saveHeader.version > CURRENT_SAGA_VER) { +#ifdef SCUMM_LITTLE_ENDIAN + _saveHeader.version = TO_BE_32(_saveHeader.version); +#else + _saveHeader.version = TO_LE_32(_saveHeader.version); +#endif + } + + debug(2, "Save version: %x", _saveHeader.version); + + if (_saveHeader.version < 4) + warning("This savegame is not endian-safe. There may be problems"); + + if (_saveHeader.type != MKID('SAGA')) { + error("SagaEngine::load wrong format"); + } + + if (_saveHeader.version > 4) { + in->read(title, TITLESIZE); + debug(0, "Save is for: %s", title); + } + + // Surrounding scene + sceneNumber = in->readSint32LE(); + + // Inset scene + insetSceneNumber = in->readSint32LE(); + + if (getGameType() != GType_ITE) { + _globalFlags = in->readUint32LE(); + for (int i = 0; i < ARRAYSIZE(_ethicsPoints); i++) + _ethicsPoints[i] = in->readSint16LE(); + } + + _interface->loadState(in); + + _actor->loadState(in); + + commonBufferSize = in->readSint16LE(); + in->read(_script->_commonBuffer, commonBufferSize); + + mapx = in->readSint16LE(); + mapy = in->readSint16LE(); + + delete in; + + // Mute volume to prevent outScene music play + int volume = _music->getVolume(); + _music->setVolume(0); + + _isoMap->setMapPosition(mapx, mapy); + + _scene->clearSceneQueue(); + _scene->changeScene(sceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade); + + _events->handleEvents(0); //dissolve backgrounds + + if (insetSceneNumber != sceneNumber) { + _render->setFlag(RF_DISABLE_ACTORS); + _render->drawScene(); + _render->clearFlag(RF_DISABLE_ACTORS); + _scene->changeScene(insetSceneNumber, ACTOR_NO_ENTRANCE, kTransitionNoFade); + } + + _music->setVolume(volume); + + _interface->draw(); +} + +} // End of namespace Saga diff --git a/engines/saga/scene.cpp b/engines/saga/scene.cpp new file mode 100644 index 0000000000..708800dc41 --- /dev/null +++ b/engines/saga/scene.cpp @@ -0,0 +1,1283 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Scene management module +#include "saga/saga.h" + +#include "saga/gfx.h" +#include "saga/animation.h" +#include "saga/console.h" +#include "saga/interface.h" +#include "saga/events.h" +#include "saga/isomap.h" +#include "saga/objectmap.h" +#include "saga/palanim.h" +#include "saga/puzzle.h" +#include "saga/render.h" +#include "saga/script.h" +#include "saga/sound.h" +#include "saga/music.h" + +#include "saga/scene.h" +#include "saga/stream.h" +#include "saga/actor.h" +#include "saga/rscfile.h" +#include "saga/resnames.h" + +#include "graphics/ilbm.h" +#include "common/util.h" + +namespace Saga { + +static int initSceneDoors[SCENE_DOORS_MAX] = { + 0, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +static SAGAResourceTypes ITESceneResourceTypes[26] = { + SAGA_ACTOR, + SAGA_OBJECT, + SAGA_BG_IMAGE, + SAGA_BG_MASK, +SAGA_UNKNOWN, + SAGA_STRINGS, + SAGA_OBJECT_MAP, + SAGA_ACTION_MAP, + SAGA_ISO_IMAGES, + SAGA_ISO_MAP, + SAGA_ISO_PLATFORMS, + SAGA_ISO_METATILES, + SAGA_ENTRY, +SAGA_UNKNOWN, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ISO_MULTI, + SAGA_PAL_ANIM, + SAGA_FACES, + SAGA_PALETTE +}; + +static SAGAResourceTypes IHNMSceneResourceTypes[28] = { + SAGA_ACTOR, +SAGA_UNKNOWN, + SAGA_BG_IMAGE, + SAGA_BG_MASK, +SAGA_UNKNOWN, + SAGA_STRINGS, + SAGA_OBJECT_MAP, + SAGA_ACTION_MAP, + SAGA_ISO_IMAGES, + SAGA_ISO_MAP, + SAGA_ISO_PLATFORMS, + SAGA_ISO_METATILES, + SAGA_ENTRY, +SAGA_UNKNOWN, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ANIM, + SAGA_ISO_MULTI, + SAGA_PAL_ANIM, + SAGA_FACES, + SAGA_PALETTE +}; + +const char *SAGAResourceTypesString[] = { + "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" +}; + +Scene::Scene(SagaEngine *vm) : _vm(vm) { + byte *sceneLUTPointer; + size_t sceneLUTLength; + uint32 resourceId; + int i; + + // Load scene module resource context + _sceneContext = _vm->_resource->getContext(GAME_RESOURCEFILE); + if (_sceneContext == NULL) { + error("Scene::Scene() scene context not found"); + } + + // Load scene lookup table + resourceId = _vm->_resource->convertResourceId(_vm->getResourceDescription()->sceneLUTResourceId); + debug(3, "Loading scene LUT from resource %i", resourceId); + _vm->_resource->loadResource(_sceneContext, resourceId, sceneLUTPointer, sceneLUTLength); + if (sceneLUTLength == 0) { + error("Scene::Scene() sceneLUTLength == 0"); + } + _sceneCount = sceneLUTLength / 2; + _sceneLUT = (int *)malloc(_sceneCount * sizeof(*_sceneLUT)); + if (_sceneLUT == NULL) { + memoryError("Scene::Scene()"); + } + + MemoryReadStreamEndian readS(sceneLUTPointer, sceneLUTLength, _sceneContext->isBigEndian); + + for (i = 0; i < _sceneCount; i++) { + _sceneLUT[i] = readS.readUint16(); + debug(8, "sceneNumber %i has resourceId %i", i, _sceneLUT[i]); + } + + free(sceneLUTPointer); + +#define DUMP_SCENES_LEVEL 10 + + if (DUMP_SCENES_LEVEL <= gDebugLevel) { + uint j; + int backUpDebugLevel = gDebugLevel; + SAGAResourceTypes *types; + int typesCount; + SAGAResourceTypes resType; + + getResourceTypes(types, typesCount); + + for (i = 0; i < _sceneCount; i++) { + gDebugLevel = -1; + loadSceneDescriptor(_sceneLUT[i]); + loadSceneResourceList(_sceneDescription.resourceListResourceId); + gDebugLevel = backUpDebugLevel; + debug(DUMP_SCENES_LEVEL, "Dump Scene: number %i, descriptor resourceId %i, resourceList resourceId %i", i, _sceneLUT[i], _sceneDescription.resourceListResourceId); + debug(DUMP_SCENES_LEVEL, "\tresourceListCount %i", _resourceListCount); + for (j = 0; j < _resourceListCount; j++) { + if (_resourceList[j].resourceType >= typesCount) { + error("wrong resource type %i", _resourceList[j].resourceType); + } + resType = types[_resourceList[j].resourceType]; + + debug(DUMP_SCENES_LEVEL, "\t%s resourceId %i", SAGAResourceTypesString[resType], _resourceList[j].resourceId); + } + free(_resourceList); + } + } + + + debug(3, "LUT has %d entries.", _sceneCount); + + _sceneLoaded = false; + _sceneNumber = 0; + _sceneResourceId = 0; + _inGame = false; + _loadDescription = false; + memset(&_sceneDescription, 0, sizeof(_sceneDescription)); + _resourceListCount = 0; + _resourceList = NULL; + _sceneProc = NULL; + _objectMap = new ObjectMap(_vm); + _actionMap = new ObjectMap(_vm); + memset(&_bg, 0, sizeof(_bg)); + memset(&_bgMask, 0, sizeof(_bgMask)); +} + +Scene::~Scene() { + delete _actionMap; + delete _objectMap; + free(_sceneLUT); +} + +void Scene::getResourceTypes(SAGAResourceTypes *&types, int &typesCount) { + if (_vm->getGameType() == GType_IHNM) { + typesCount = ARRAYSIZE(IHNMSceneResourceTypes); + types = IHNMSceneResourceTypes; + } else { + typesCount = ARRAYSIZE(ITESceneResourceTypes); + types = ITESceneResourceTypes; + } +} + +void Scene::drawTextList(Surface *ds) { + TextListEntry *entry; + + for (TextList::iterator textIterator = _textList.begin(); textIterator != _textList.end(); ++textIterator) { + entry = (TextListEntry *)textIterator.operator->(); + if (entry->display) { + + if (entry->useRect) { + _vm->_font->textDrawRect(entry->font, ds, entry->text, entry->rect, _vm->KnownColor2ColorId(entry->knownColor), _vm->KnownColor2ColorId(entry->effectKnownColor), entry->flags); + } else { + _vm->_font->textDraw(entry->font, ds, entry->text, entry->point, _vm->KnownColor2ColorId(entry->knownColor), _vm->KnownColor2ColorId(entry->effectKnownColor), entry->flags); + } + } + } +} + +void Scene::startScene() { + SceneQueueList::iterator queueIterator; + LoadSceneParams *sceneQueue; + Event event; + + if (_sceneLoaded) { + error("Scene::start(): Error: Can't start game...scene already loaded"); + } + + if (_inGame) { + error("Scene::start(): Error: Can't start game...game already started"); + } + + // Hide cursor during intro + event.type = kEvTOneshot; + event.code = kCursorEvent; + event.op = kEventHide; + _vm->_events->queue(&event); + + switch (_vm->getGameType()) { + case GType_ITE: + ITEStartProc(); + break; + case GType_IHNM: + IHNMStartProc(); + break; + default: + error("Scene::start(): Error: Can't start game... gametype not supported"); + break; + } + + // Load the head in scene queue + queueIterator = _sceneQueue.begin(); + if (queueIterator == _sceneQueue.end()) { + return; + } + + sceneQueue = queueIterator.operator->(); + + loadScene(sceneQueue); +} + +void Scene::nextScene() { + SceneQueueList::iterator queueIterator; + LoadSceneParams *sceneQueue; + + if (!_sceneLoaded) { + error("Scene::next(): Error: Can't advance scene...no scene loaded"); + } + + if (_inGame) { + error("Scene::next(): Error: Can't advance scene...game already started"); + } + + endScene(); + + // Delete the current head in scene queue + queueIterator = _sceneQueue.begin(); + if (queueIterator == _sceneQueue.end()) { + return; + } + + queueIterator = _sceneQueue.erase(queueIterator); + + if (queueIterator == _sceneQueue.end()) { + return; + } + + // Load the head in scene queue + sceneQueue = queueIterator.operator->(); + + loadScene(sceneQueue); +} + +void Scene::skipScene() { + SceneQueueList::iterator queueIterator; + + LoadSceneParams *sceneQueue = NULL; + LoadSceneParams *skipQueue = NULL; + + if (!_sceneLoaded) { + error("Scene::skip(): Error: Can't skip scene...no scene loaded"); + } + + if (_inGame) { + error("Scene::skip(): Error: Can't skip scene...game already started"); + } + + // Walk down scene queue and try to find a skip target + queueIterator = _sceneQueue.begin(); + if (queueIterator == _sceneQueue.end()) { + error("Scene::skip(): Error: Can't skip scene...no scenes in queue"); + } + + ++queueIterator; + while (queueIterator != _sceneQueue.end()) { + sceneQueue = queueIterator.operator->(); + assert(sceneQueue != NULL); + + if (sceneQueue->sceneSkipTarget) { + skipQueue = sceneQueue; + break; + } + ++queueIterator; + } + + // If skip target found, remove preceding scenes and load + if (skipQueue != NULL) { + _sceneQueue.erase(_sceneQueue.begin(), queueIterator); + + endScene(); + loadScene(skipQueue); + } +} + +static struct SceneSubstitutes { + int sceneId; + const char *message; + const char *title; + const char *image; +} sceneSubstitutes[] = { + { + 7, + "Tycho says he knows much about the northern lands. Can Rif convince " + "the Dog to share this knowledge?", + "The Home of Tycho Northpaw", + "tycho.bbm" + }, + + { + 27, + "The scene of the crime may hold many clues, but will the servants of " + "the Sanctuary trust Rif?", + "The Sanctuary of the Orb", + "sanctuar.bbm" + }, + + { + 5, + "The Rats hold many secrets that could guide Rif on his quest -- assuming " + "he can get past the doorkeeper.", + "The Rat Complex", + "ratdoor.bbm" + }, + + { + 2, + "The Ferrets enjoy making things and have the materials to do so. How can " + "that help Rif?", + "The Ferret Village", + "ferrets.bbm" + }, + + { + 67, + "What aid can the noble King of the Elks provide to Rif and his companions?", + "The Realm of the Forest King", + "elkenter.bbm" + }, + + { + 3, + "The King holds Rif's sweetheart hostage. Will the Boar provide any " + "assistance to Rif?", + "The Great Hall of the Boar King", + "boarhall.bbm" + } +}; + +void Scene::changeScene(int16 sceneNumber, int actorsEntrance, SceneTransitionType transitionType, int chapter) { + + debug(5, "Scene::changeScene(%d, %d, %d, %d)", sceneNumber, actorsEntrance, transitionType, chapter); + + // This is used for latter ITE demos where all places on world map except + // Tent Faire are substituted with LBM picture and short description + if (_vm->getFeatures() & GF_SCENE_SUBSTITUTES) { + for (int i = 0; i < ARRAYSIZE(sceneSubstitutes); i++) { + if (sceneSubstitutes[i].sceneId == sceneNumber) { + Surface *backBuffer = _vm->_gfx->getBackBuffer(); + Surface bbmBuffer; + byte *pal, *colors; + Common::File file; + Rect rect; + PalEntry cPal[PAL_ENTRIES]; + + _vm->_interface->setMode(kPanelSceneSubstitute); + + if (file.open(sceneSubstitutes[i].image)) { + Graphics::decodeILBM(file, bbmBuffer, pal); + colors = pal; + rect.setWidth(bbmBuffer.w); + rect.setHeight(bbmBuffer.h); + backBuffer->blit(rect, (const byte*)bbmBuffer.pixels); + for (int j = 0; j < PAL_ENTRIES; j++) { + cPal[j].red = *pal++; + cPal[j].green = *pal++; + cPal[j].blue = *pal++; + } + free(colors); + _vm->_gfx->setPalette(cPal); + + } + + _vm->_interface->setStatusText("Click or Press Return to continue. Press Q to quit.", 96); + _vm->_font->textDrawRect(kKnownFontMedium, backBuffer, sceneSubstitutes[i].title, + Common::Rect(0, 7, _vm->getDisplayWidth(), 27), _vm->KnownColor2ColorId(kKnownColorBrightWhite), _vm->KnownColor2ColorId(kKnownColorBlack), kFontOutline); + _vm->_font->textDrawRect(kKnownFontMedium, backBuffer, sceneSubstitutes[i].message, + Common::Rect(24, getHeight() - 33, _vm->getDisplayWidth() - 11, + getHeight()), _vm->KnownColor2ColorId(kKnownColorBrightWhite), _vm->KnownColor2ColorId(kKnownColorBlack), kFontOutline); + return; + } + } + } + + LoadSceneParams sceneParams; + + sceneParams.actorsEntrance = actorsEntrance; + sceneParams.loadFlag = kLoadBySceneNumber; + sceneParams.sceneDescriptor = sceneNumber; + sceneParams.transitionType = transitionType; + sceneParams.sceneProc = NULL; + sceneParams.sceneSkipTarget = false; + sceneParams.chapter = chapter; + + if (sceneNumber != -2) { + endScene(); + } + loadScene(&sceneParams); +} + +void Scene::getSlopes(int &beginSlope, int &endSlope) { + beginSlope = getHeight() - _sceneDescription.beginSlope; + endSlope = getHeight() - _sceneDescription.endSlope; +} + +void Scene::getBGInfo(BGInfo &bgInfo) { + bgInfo.buffer = _bg.buf; + bgInfo.bufferLength = _bg.buf_len; + bgInfo.bounds.left = 0; + bgInfo.bounds.top = 0; + + if (_bg.w < _vm->getDisplayWidth()) { + bgInfo.bounds.left = (_vm->getDisplayWidth() - _bg.w) / 2; + } + + if (_bg.h < getHeight()) { + bgInfo.bounds.top = (getHeight() - _bg.h) / 2; + } + + bgInfo.bounds.setWidth(_bg.w); + bgInfo.bounds.setHeight(_bg.h); +} + +int Scene::getBGMaskType(const Point &testPoint) { + uint offset; + if (!_bgMask.loaded) { + return 0; + } + offset = testPoint.x + testPoint.y * _bgMask.w; + if (offset >= _bgMask.buf_len) { + error("Scene::getBGMaskType offset 0x%X exceed bufferLength 0x%X", offset, _bgMask.buf_len); + } + + return (_bgMask.buf[offset] >> 4) & 0x0f; +} + +bool Scene::validBGMaskPoint(const Point &testPoint) { + if (!_bgMask.loaded) { + error("Scene::validBGMaskPoint _bgMask not loaded"); + } + + return !((testPoint.x < 0) || (testPoint.x >= _bgMask.w) || + (testPoint.y < 0) || (testPoint.y >= _bgMask.h)); +} + +bool Scene::canWalk(const Point &testPoint) { + int maskType; + + if (!_bgMask.loaded) { + return true; + } + if (!validBGMaskPoint(testPoint)) { + return true; + } + + maskType = getBGMaskType(testPoint); + return getDoorState(maskType) == 0; +} + +bool Scene::offscreenPath(Point &testPoint) { + Point point; + + if (!_bgMask.loaded) { + return false; + } + + point.x = clamp( 0, testPoint.x, _bgMask.w - 1 ); + point.y = clamp( 0, testPoint.y, _bgMask.h - 1 ); + if (point == testPoint) { + return false; + } + + if (point.y >= _bgMask.h - 1) { + point.y = _bgMask.h - 2; + } + testPoint = point; + + return true; +} + + +void Scene::getBGMaskInfo(int &width, int &height, byte *&buffer, size_t &bufferLength) { + if (!_bgMask.loaded) { + error("Scene::getBGMaskInfo _bgMask not loaded"); + } + + width = _bgMask.w; + height = _bgMask.h; + buffer = _bgMask.buf; + bufferLength = _bgMask.buf_len; +} + +void Scene::setDoorState(int doorNumber, int doorState) { + if ((doorNumber < 0) || (doorNumber >= SCENE_DOORS_MAX)) + error("Scene::setDoorState wrong doorNumber"); + + _sceneDoors[doorNumber] = doorState; +} + +int Scene::getDoorState(int doorNumber) { + if ((doorNumber < 0) || (doorNumber >= SCENE_DOORS_MAX)) + error("Scene::getDoorState wrong doorNumber"); + + return _sceneDoors[doorNumber]; +} + +void Scene::initDoorsState() { + memcpy(_sceneDoors, initSceneDoors, sizeof (_sceneDoors) ); +} + +void Scene::loadScene(LoadSceneParams *loadSceneParams) { + size_t i; + Event event; + Event *q_event; + static PalEntry current_pal[PAL_ENTRIES]; + + if ((_vm->getGameType() == GType_IHNM) && (loadSceneParams->chapter != NO_CHAPTER_CHANGE)) { + if (loadSceneParams->loadFlag != kLoadBySceneNumber) { + error("loadScene wrong usage"); + } + + if (loadSceneParams->chapter == 6) + _vm->_interface->setLeftPortrait(0); + + _vm->_anim->freeCutawayList(); + _vm->_script->freeModules(); + // deleteAllScenes(); + + // installSomeAlarm() + + _vm->_interface->clearInventory(); + _vm->_resource->loadGlobalResources(loadSceneParams->chapter, loadSceneParams->actorsEntrance); + _vm->_interface->addToInventory(IHNM_OBJ_PROFILE); + _vm->_interface->activate(); + + if (loadSceneParams->chapter == 8 || loadSceneParams->chapter == -1) + _vm->_interface->setMode(kPanelChapterSelection); + else + _vm->_interface->setMode(kPanelMain); + + _inGame = true; + + _vm->_script->setVerb(_vm->_script->getVerbType(kVerbWalkTo)); + + if (loadSceneParams->sceneDescriptor == -2) { + return; + } + } + + if (_sceneLoaded) { + error("Scene::loadScene(): Error, a scene is already loaded"); + } + + _loadDescription = true; + + if (_vm->getGameType() == GType_IHNM) { + if (loadSceneParams->loadFlag == kLoadBySceneNumber) // When will we get rid of it? + if (loadSceneParams->sceneDescriptor <= 0) + loadSceneParams->sceneDescriptor = _vm->_resource->_metaResource.sceneIndex; + } + + switch (loadSceneParams->loadFlag) { + case kLoadByResourceId: + _sceneNumber = 0; // original assign zero for loaded by resource id + _sceneResourceId = loadSceneParams->sceneDescriptor; + break; + case kLoadBySceneNumber: + _sceneNumber = loadSceneParams->sceneDescriptor; + _sceneResourceId = getSceneResourceId(_sceneNumber); + break; + case kLoadByDescription: + _sceneNumber = -1; + _sceneResourceId = -1; + assert(loadSceneParams->sceneDescription != NULL); + assert(loadSceneParams->sceneDescription->resourceList != NULL); + _loadDescription = false; + _sceneDescription = *loadSceneParams->sceneDescription; + _resourceList = loadSceneParams->sceneDescription->resourceList; + _resourceListCount = loadSceneParams->sceneDescription->resourceListCount; + break; + } + + debug(3, "Loading scene number %d:", _sceneNumber); + + // Load scene descriptor and resource list resources + if (_loadDescription) { + debug(3, "Loading scene resource %i", _sceneResourceId); + + loadSceneDescriptor(_sceneResourceId); + + loadSceneResourceList(_sceneDescription.resourceListResourceId); + } else { + debug(3, "Loading memory scene resource"); + } + + // Load resources from scene resource list + for (i = 0; i < _resourceListCount; i++) { + if (!_resourceList[i].invalid) { + _vm->_resource->loadResource(_sceneContext, _resourceList[i].resourceId, + _resourceList[i].buffer, _resourceList[i].size); + + + if (_resourceList[i].size >= 6) { + if (!memcmp(_resourceList[i].buffer, "DUMMY!", 6)) { + _resourceList[i].invalid = true; + warning("DUMMY resource %i", _resourceList[i].resourceId); + } + } + } + } + + // Process resources from scene resource list + processSceneResources(); + + if (_sceneDescription.flags & kSceneFlagISO) { + _outsetSceneNumber = _sceneNumber; + + _sceneClip.left = 0; + _sceneClip.top = 0; + _sceneClip.right = _vm->getDisplayWidth(); + _sceneClip.bottom = getHeight(); + } else { + BGInfo backGroundInfo; + getBGInfo(backGroundInfo); + _sceneClip = backGroundInfo.bounds; + if (!(_bg.w < _vm->getDisplayWidth() || _bg.h < getHeight())) + _outsetSceneNumber = _sceneNumber; + } + + _sceneLoaded = true; + + q_event = NULL; + + //fix placard bug + //i guess we should remove RF_PLACARD flag - and use _interface->getMode() + event.type = kEvTOneshot; + event.code = kGraphicsEvent; + event.op = kEventClearFlag; + event.param = RF_PLACARD; + + q_event = _vm->_events->chain(q_event, &event); + + if (loadSceneParams->transitionType == kTransitionFade) { + + _vm->_interface->setFadeMode(kFadeOut); + + // Fade to black out + _vm->_gfx->getCurrentPal(current_pal); + event.type = kEvTImmediate; + event.code = kPalEvent; + event.op = kEventPalToBlack; + event.time = 0; + event.duration = kNormalFadeDuration; + event.data = current_pal; + q_event = _vm->_events->queue(&event); + + // set fade mode + event.type = kEvTImmediate; + event.code = kInterfaceEvent; + event.op = kEventSetFadeMode; + event.param = kNoFade; + event.time = 0; + event.duration = 0; + q_event = _vm->_events->chain(q_event, &event); + + // Display scene background, but stay with black palette + event.type = kEvTImmediate; + event.code = kBgEvent; + event.op = kEventDisplay; + event.param = kEvPNoSetPalette; + event.time = 0; + event.duration = 0; + q_event = _vm->_events->chain(q_event, &event); + + } + + // Start the scene pre script, but stay with black palette + if (_sceneDescription.startScriptEntrypointNumber > 0) { + event.type = kEvTOneshot; + event.code = kScriptEvent; + event.op = kEventExecBlocking; + event.time = 0; + event.param = _sceneDescription.scriptModuleNumber; + event.param2 = _sceneDescription.startScriptEntrypointNumber; + event.param3 = 0; // Action + event.param4 = _sceneNumber; // Object + event.param5 = loadSceneParams->actorsEntrance; // With Object + event.param6 = 0; // Actor + + q_event = _vm->_events->chain(q_event, &event); + } + + if (loadSceneParams->transitionType == kTransitionFade) { + + // set fade mode + event.type = kEvTImmediate; + event.code = kInterfaceEvent; + event.op = kEventSetFadeMode; + event.param = kFadeIn; + event.time = 0; + event.duration = 0; + q_event = _vm->_events->chain(q_event, &event); + + // Fade in from black to the scene background palette + event.type = kEvTImmediate; + event.code = kPalEvent; + event.op = kEventBlackToPal; + event.time = 0; + event.duration = kNormalFadeDuration; + event.data = _bg.pal; + + q_event = _vm->_events->chain(q_event, &event); + + // set fade mode + event.type = kEvTImmediate; + event.code = kInterfaceEvent; + event.op = kEventSetFadeMode; + event.param = kNoFade; + event.time = 0; + event.duration = 0; + q_event = _vm->_events->chain(q_event, &event); + } + + if (loadSceneParams->sceneProc == NULL) { + if (!_inGame && _vm->getGameType() == GType_ITE) { + _inGame = true; + _vm->_interface->setMode(kPanelMain); + } + + _vm->_sound->stopAll(); + + // FIXME: Does IHNM use scene background music, or is all the + // music scripted? At the very least, it shouldn't try + // to start song 0 at the beginning of the game, since + // it's the end credits music. + + if (_vm->getGameType() == GType_ITE) { + if (_sceneDescription.musicResourceId >= 0) { + event.type = kEvTOneshot; + event.code = kMusicEvent; + event.param = _sceneDescription.musicResourceId; + event.param2 = MUSIC_DEFAULT; + event.op = kEventPlay; + event.time = 0; + + _vm->_events->queue(&event); + } else { + event.type = kEvTOneshot; + event.code = kMusicEvent; + event.op = kEventStop; + event.time = 0; + + _vm->_events->queue(&event); + } + } + + // Set scene background + event.type = kEvTOneshot; + event.code = kBgEvent; + event.op = kEventDisplay; + event.param = kEvPSetPalette; + event.time = 0; + + _vm->_events->queue(&event); + + // Begin palette cycle animation if present + event.type = kEvTOneshot; + event.code = kPalAnimEvent; + event.op = kEventCycleStart; + event.time = 0; + + q_event = _vm->_events->queue(&event); + + // Start the scene main script + if (_sceneDescription.sceneScriptEntrypointNumber > 0) { + event.type = kEvTOneshot; + event.code = kScriptEvent; + event.op = kEventExecNonBlocking; + event.time = 0; + event.param = _sceneDescription.scriptModuleNumber; + event.param2 = _sceneDescription.sceneScriptEntrypointNumber; + event.param3 = _vm->_script->getVerbType(kVerbEnter); // Action + event.param4 = _sceneNumber; // Object + event.param5 = loadSceneParams->actorsEntrance; // With Object + event.param6 = 0; // Actor + + _vm->_events->queue(&event); + } + + debug(3, "Scene started"); + + } else { + loadSceneParams->sceneProc(SCENE_BEGIN, this); + } + + + + // We probably don't want "followers" to go into scene -1 , 0. At the very + // least we don't want garbage to be drawn that early in the ITE intro. + if (_sceneNumber > 0 && _sceneNumber != ITE_SCENE_PUZZLE) + _vm->_actor->updateActorsScene(loadSceneParams->actorsEntrance); + + if (_sceneNumber == ITE_SCENE_PUZZLE) + _vm->_puzzle->execute(); + + if (getFlags() & kSceneFlagShowCursor) { + // Activate user interface + event.type = kEvTOneshot; + event.code = kInterfaceEvent; + event.op = kEventActivate; + event.time = 0; + _vm->_events->queue(&event); + } +} + +void Scene::loadSceneDescriptor(uint32 resourceId) { + byte *sceneDescriptorData; + size_t sceneDescriptorDataLength; + + memset(&_sceneDescription, 0, sizeof(_sceneDescription)); + + if (resourceId == 0) { + return; + } + + _vm->_resource->loadResource(_sceneContext, resourceId, sceneDescriptorData, sceneDescriptorDataLength); + + if (sceneDescriptorDataLength == 16) { + MemoryReadStreamEndian readS(sceneDescriptorData, sceneDescriptorDataLength, _sceneContext->isBigEndian); + + _sceneDescription.flags = readS.readSint16(); + _sceneDescription.resourceListResourceId = readS.readSint16(); + _sceneDescription.endSlope = readS.readSint16(); + _sceneDescription.beginSlope = readS.readSint16(); + _sceneDescription.scriptModuleNumber = readS.readUint16(); + _sceneDescription.sceneScriptEntrypointNumber = readS.readUint16(); + _sceneDescription.startScriptEntrypointNumber = readS.readUint16(); + _sceneDescription.musicResourceId = readS.readSint16(); + } + + free(sceneDescriptorData); +} + +void Scene::loadSceneResourceList(uint32 resourceId) { + byte *resourceListData; + size_t resourceListDataLength; + size_t i; + + _resourceListCount = 0; + _resourceList = NULL; + + if (resourceId == 0) { + return; + } + + // Load the scene resource table + _vm->_resource->loadResource(_sceneContext, resourceId, resourceListData, resourceListDataLength); + + if ((resourceListDataLength % SAGA_RESLIST_ENTRY_LEN) == 0) { + MemoryReadStreamEndian readS(resourceListData, resourceListDataLength, _sceneContext->isBigEndian); + + // Allocate memory for scene resource list + _resourceListCount = resourceListDataLength / SAGA_RESLIST_ENTRY_LEN; + debug(3, "Scene resource list contains %i entries", _resourceListCount); + _resourceList = (SceneResourceData *)calloc(_resourceListCount, sizeof(*_resourceList)); + + // Load scene resource list from raw scene + // resource table + debug(3, "Loading scene resource list"); + + for (i = 0; i < _resourceListCount; i++) { + _resourceList[i].resourceId = readS.readUint16(); + _resourceList[i].resourceType = readS.readUint16(); + // demo version may contain invalid resourceId + _resourceList[i].invalid = !_vm->_resource->validResourceId(_sceneContext, _resourceList[i].resourceId); + } + + } + free(resourceListData); +} + +void Scene::processSceneResources() { + byte *resourceData; + size_t resourceDataLength; + const byte *palPointer; + size_t i; + SAGAResourceTypes *types; + int typesCount; + SAGAResourceTypes resType; + + getResourceTypes(types, typesCount); + + // Process the scene resource list + for (i = 0; i < _resourceListCount; i++) { + if (_resourceList[i].invalid) { + continue; + } + resourceData = _resourceList[i].buffer; + resourceDataLength = _resourceList[i].size; + + if (_resourceList[i].resourceType >= typesCount) { + error("Scene::processSceneResources() wrong resource type %i", _resourceList[i].resourceType); + } + + resType = types[_resourceList[i].resourceType]; + + switch (resType) { + case SAGA_UNKNOWN: + warning("UNKNOWN resourceType %i", _resourceList[i].resourceType); + break; + case SAGA_ACTOR: + //for (a = actorsInScene; a; a = a->nextInScene) + // if (a->obj.figID == glist->file_id) + // if (_vm->getGameType() == GType_ITE || + // ((a->obj.flags & ACTORF_FINAL_FACE) & 0xff)) + // a->sprites = (xSpriteSet *)glist->offset; + warning("STUB: unimplemeted handler of SAGA_ACTOR resource"); + break; + case SAGA_OBJECT: + break; + case SAGA_BG_IMAGE: // Scene background resource + if (_bg.loaded) { + error("Scene::processSceneResources() Multiple background resources encountered"); + } + + debug(3, "Loading background resource."); + _bg.res_buf = resourceData; + _bg.res_len = resourceDataLength; + _bg.loaded = 1; + + if (_vm->decodeBGImage(_bg.res_buf, + _bg.res_len, + &_bg.buf, + &_bg.buf_len, + &_bg.w, + &_bg.h) != SUCCESS) { + error("Scene::processSceneResources() Error loading background resource %i", _resourceList[i].resourceId); + } + + palPointer = _vm->getImagePal(_bg.res_buf, _bg.res_len); + memcpy(_bg.pal, palPointer, sizeof(_bg.pal)); + break; + case SAGA_BG_MASK: // Scene background mask resource + if (_bgMask.loaded) { + error("Scene::ProcessSceneResources(): Duplicate background mask resource encountered"); + } + debug(3, "Loading BACKGROUND MASK resource."); + _bgMask.res_buf = resourceData; + _bgMask.res_len = resourceDataLength; + _bgMask.loaded = 1; + _vm->decodeBGImage(_bgMask.res_buf, _bgMask.res_len, &_bgMask.buf, + &_bgMask.buf_len, &_bgMask.w, &_bgMask.h, true); + + // At least in ITE the mask needs to be clipped. + + _bgMask.w = MIN(_bgMask.w, _vm->getDisplayWidth()); + _bgMask.h = MIN(_bgMask.h, getHeight()); + + debug(4, "BACKGROUND MASK width=%d height=%d length=%d", _bgMask.w, _bgMask.h, _bgMask.buf_len); + break; + case SAGA_STRINGS: + debug(3, "Loading scene strings resource..."); + _vm->loadStrings(_sceneStrings, resourceData, resourceDataLength); + break; + case SAGA_OBJECT_MAP: + debug(3, "Loading object map resource..."); + _objectMap->load(resourceData, resourceDataLength); + break; + case SAGA_ACTION_MAP: + debug(3, "Loading action map resource..."); + _actionMap->load(resourceData, resourceDataLength); + break; + case SAGA_ISO_IMAGES: + if (!(_sceneDescription.flags & kSceneFlagISO)) { + error("Scene::ProcessSceneResources(): not Iso mode"); + } + + debug(3, "Loading isometric images resource."); + + _vm->_isoMap->loadImages(resourceData, resourceDataLength); + break; + case SAGA_ISO_MAP: + if (!(_sceneDescription.flags & kSceneFlagISO)) { + error("Scene::ProcessSceneResources(): not Iso mode"); + } + + debug(3, "Loading isometric map resource."); + + _vm->_isoMap->loadMap(resourceData, resourceDataLength); + break; + case SAGA_ISO_PLATFORMS: + if (!(_sceneDescription.flags & kSceneFlagISO)) { + error("Scene::ProcessSceneResources(): not Iso mode"); + } + + debug(3, "Loading isometric platforms resource."); + + _vm->_isoMap->loadPlatforms(resourceData, resourceDataLength); + break; + case SAGA_ISO_METATILES: + if (!(_sceneDescription.flags & kSceneFlagISO)) { + error("Scene::ProcessSceneResources(): not Iso mode"); + } + + debug(3, "Loading isometric metatiles resource."); + + _vm->_isoMap->loadMetaTiles(resourceData, resourceDataLength); + break; + case SAGA_ANIM: + { + uint16 animId = _resourceList[i].resourceType - 14; + + debug(3, "Loading animation resource animId=%i", animId); + + _vm->_anim->load(animId, resourceData, resourceDataLength); + } + break; + case SAGA_ENTRY: + debug(3, "Loading entry list resource..."); + loadSceneEntryList(resourceData, resourceDataLength); + break; + case SAGA_ISO_MULTI: + if (!(_sceneDescription.flags & kSceneFlagISO)) { + error("Scene::ProcessSceneResources(): not Iso mode"); + } + + debug(3, "Loading isometric multi resource."); + + _vm->_isoMap->loadMulti(resourceData, resourceDataLength); + break; + case SAGA_PAL_ANIM: + debug(3, "Loading palette animation resource."); + _vm->_palanim->loadPalAnim(resourceData, resourceDataLength); + break; + case SAGA_FACES: + if (_vm->getGameType() == GType_ITE) + _vm->_interface->loadScenePortraits(_resourceList[i].resourceId); + break; + case SAGA_PALETTE: + { + PalEntry pal[PAL_ENTRIES]; + byte *palPtr = resourceData; + + if (resourceDataLength < 3 * PAL_ENTRIES) + error("Too small scene palette %i", resourceDataLength); + + for (uint16 c = 0; c < PAL_ENTRIES; c++) { + pal[c].red = *palPtr++; + pal[c].green = *palPtr++; + pal[c].blue = *palPtr++; + } + _vm->_gfx->setPalette(pal); + } + break; + default: + error("Scene::ProcessSceneResources() Encountered unknown resource type %i", _resourceList[i].resourceType); + break; + } + } +} + +void Scene::draw() { + Surface *backBuffer; + Surface *backGroundSurface; + Rect rect; + + backBuffer = _vm->_gfx->getBackBuffer(); + + backGroundSurface = _vm->_render->getBackGroundSurface(); + + if (_sceneDescription.flags & kSceneFlagISO) { + _vm->_isoMap->adjustScroll(false); + _vm->_isoMap->draw(backBuffer); + } else { + backGroundSurface->getRect(rect); + if (_sceneClip.bottom < rect.bottom) { + rect.bottom = getHeight(); + } + backBuffer->blit(rect, (const byte *)backGroundSurface->pixels); + } +} + +void Scene::endScene() { + Surface *backBuffer; + Surface *backGroundSurface; + Rect rect; + size_t i; + + if (!_sceneLoaded) + return; + + debug(3, "Ending scene..."); + + if (_sceneProc != NULL) { + _sceneProc(SCENE_END, this); + } + + // + _vm->_script->abortAllThreads(); + _vm->_script->_skipSpeeches = false; + + // Copy current screen to render buffer so inset rooms will get proper background + backGroundSurface = _vm->_render->getBackGroundSurface(); + if (!(_sceneDescription.flags & kSceneFlagISO) && !_vm->_scene->isInIntro()) { + BGInfo bgInfo; + + _vm->_scene->getBGInfo(bgInfo); + backGroundSurface->blit(bgInfo.bounds, bgInfo.buffer); + } else { + backBuffer = _vm->_gfx->getBackBuffer(); + backBuffer->getRect(rect); + backGroundSurface->blit(rect, (const byte *)backBuffer->pixels); + } + + // Free scene background + if (_bg.loaded) { + free(_bg.buf); + _bg.loaded = 0; + } + + // Free scene background mask + if (_bgMask.loaded) { + free(_bgMask.buf); + _bgMask.loaded = 0; + } + + // Free scene resource list + for (i = 0; i < _resourceListCount; i++) { + free(_resourceList[i].buffer); + } + + if (_loadDescription) { + free(_resourceList); + } + + // Free animation info list + _vm->_anim->reset(); + + _vm->_palanim->freePalAnim(); + + _objectMap->freeMem(); + _actionMap->freeMem(); + _entryList.freeMem(); + _sceneStrings.freeMem(); + _vm->_isoMap->freeMem(); + + _vm->_events->clearList(); + _textList.clear(); + + _sceneLoaded = false; + +} + +void Scene::cmdSceneChange(int argc, const char **argv) { + int scene_num = 0; + + scene_num = atoi(argv[1]); + + if ((scene_num < 1) || (scene_num >= _sceneCount)) { + _vm->_console->DebugPrintf("Invalid scene number.\n"); + return; + } + + clearSceneQueue(); + + changeScene(scene_num, 0, kTransitionNoFade); +} + +void Scene::cmdActionMapInfo() { + _actionMap->cmdInfo(); +} + +void Scene::cmdObjectMapInfo() { + _objectMap->cmdInfo(); +} + + +void Scene::loadSceneEntryList(const byte* resourcePointer, size_t resourceLength) { + int i; + + _entryList.entryListCount = resourceLength / 8; + + MemoryReadStreamEndian readS(resourcePointer, resourceLength, _sceneContext->isBigEndian); + + + if (_entryList.entryList) + error("Scene::loadSceneEntryList entryList != NULL"); + + _entryList.entryList = (SceneEntry *) malloc(_entryList.entryListCount * sizeof(*_entryList.entryList)); + if (_entryList.entryList == NULL) { + memoryError("Scene::loadSceneEntryList"); + } + + for (i = 0; i < _entryList.entryListCount; i++) { + _entryList.entryList[i].location.x = readS.readSint16(); + _entryList.entryList[i].location.y = readS.readSint16(); + _entryList.entryList[i].location.z = readS.readSint16(); + _entryList.entryList[i].facing = readS.readUint16(); + } +} + +} // End of namespace Saga diff --git a/engines/saga/scene.h b/engines/saga/scene.h new file mode 100644 index 0000000000..7f99140d10 --- /dev/null +++ b/engines/saga/scene.h @@ -0,0 +1,370 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Scene management module private header file + +#ifndef SAGA_SCENE_H +#define SAGA_SCENE_H + +#include "saga/font.h" +#include "saga/list.h" +#include "saga/actor.h" +#include "saga/interface.h" + +namespace Saga { + +#define SCENE_DOORS_MAX 16 + +#define NO_CHAPTER_CHANGE -2 + +class ObjectMap; + +struct Event; + +enum SceneFlags { + kSceneFlagISO = 1, + kSceneFlagShowCursor = 2 +}; + +struct BGInfo { + Rect bounds; + byte *buffer; + size_t bufferLength; +}; + +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; + byte *buffer; + size_t size; + bool invalid; +}; + +#define SAGA_SCENE_DESC_LEN 16 + +struct SceneDescription { + int16 flags; + int16 resourceListResourceId; + int16 endSlope; + int16 beginSlope; + uint16 scriptModuleNumber; + uint16 sceneScriptEntrypointNumber; + uint16 startScriptEntrypointNumber; + int16 musicResourceId; + SceneResourceData *resourceList; + size_t resourceListCount; +}; + +struct SceneEntry { + Location location; + int facing; +}; + +struct SceneEntryList { + SceneEntry *entryList; + int entryListCount; + + const SceneEntry * getEntry(int index) { + if ((index < 0) || (index >= entryListCount)) { + error("SceneEntryList::getEntry wrong index (%d)", index); + } + return &entryList[index]; + } + void freeMem() { + free(entryList); + memset(this, 0, sizeof(*this)); + } + SceneEntryList() { + memset(this, 0, sizeof(*this)); + } + ~SceneEntryList() { + freeMem(); + } +}; + +struct SceneImage { + int loaded; + int w; + int h; + int p; + byte *buf; + size_t buf_len; + byte *res_buf; + size_t res_len; + PalEntry pal[256]; +}; + + +enum SceneTransitionType { + kTransitionNoFade, + kTransitionFade +}; + +enum SceneLoadFlags { + kLoadByResourceId, + kLoadBySceneNumber, + kLoadByDescription +}; + +struct LoadSceneParams { + int32 sceneDescriptor; + SceneLoadFlags loadFlag; + SceneDescription* sceneDescription; + 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 + +///// ITE-specific stuff +#define ITE_INTRO_FRAMETIME 90 + +#define INTRO_CAPTION_Y 170 +#define INTRO_DE_CAPTION_Y 160 +#define VOICE_PAD 50 +#define VOICE_LETTERLEN 90 + +#define PALETTE_FADE_DURATION 1000 +#define DISSOLVE_DURATION 3000 +#define LOGO_DISSOLVE_DURATION 1000 + +#define CREDIT_DURATION1 4000 + +struct IntroDialogue { + uint32 i_voice_rn; + const char *i_str; +}; + +struct IntroCredit { + Common::Language lang; + int game; + int type; + const char *string; +}; + + +class Scene { + public: + Scene(SagaEngine *vm); + ~Scene(); + +// Console functions + void cmdActionMapInfo(); + void cmdObjectMapInfo(); + + void cmdSceneChange(int argc, const char **argv); + + void startScene(); + void nextScene(); + void skipScene(); + void endScene(); + void queueScene(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, size_t &bufferLength); + int isBGMaskPresent() { return _bgMask.loaded; } + int getBGMaskType(const Point &testPoint); + bool validBGMaskPoint(const Point &testPoint); + bool canWalk(const Point &testPoint); + bool offscreenPath(Point &testPoint); + + void setDoorState(int doorNumber, int doorState); + int getDoorState(int doorNumber); + void initDoorsState(); + + void getBGInfo(BGInfo &bgInfo); + void getBGPal(PalEntry *&pal) { + pal = (PalEntry *)_bg.pal; + } + + void getSlopes(int &beginSlope, int &endSlope); + + void clearSceneQueue(void) { + _sceneQueue.clear(); + } + void changeScene(int16 sceneNumber, int actorsEntrance, SceneTransitionType transitionType, int chapter = NO_CHAPTER_CHANGE); + + bool isSceneLoaded() const { return _sceneLoaded; } + + int getSceneResourceId(int sceneNumber) { + if ((sceneNumber < 0) || (sceneNumber >= _sceneCount)) { + error("getSceneResourceId: wrong sceneNumber %i", sceneNumber); + } + 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; } + void cutawaySkip() { + if (_vm->_scene->isInIntro()) + _vm->_framesEsc = 2; + else + _vm->_framesEsc = 1; + } + + void drawTextList(Surface *ds); + + int getHeight() const { + if (_vm->_interface->getMode() == kPanelChapterSelection) + return _vm->_gameDisplayInfo.logicalHeight; + else + return _vm->_gameDisplayInfo.sceneHeight; + } + + private: + void loadScene(LoadSceneParams *loadSceneParams); + void loadSceneDescriptor(uint32 resourceId); + void loadSceneResourceList(uint32 resourceId); + void loadSceneEntryList(const byte* resourcePointer, size_t resourceLength); + void processSceneResources(); + void getResourceTypes(SAGAResourceTypes *&types, int &typesCount); + + + SagaEngine *_vm; + + ResourceContext *_sceneContext; + int *_sceneLUT; + int _sceneCount; + SceneQueueList _sceneQueue; + bool _sceneLoaded; + int _sceneNumber; + int _chapterNumber; + int _outsetSceneNumber; + int _sceneResourceId; + bool _inGame; + bool _loadDescription; + SceneDescription _sceneDescription; + size_t _resourceListCount; + SceneResourceData *_resourceList; + 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 IHNMStartProc(); + int ITEStartProc(); + + public: + static int SC_IHNMIntroMovieProc1(int param, void *refCon); + static int SC_IHNMIntroMovieProc2(int param, void *refCon); + static int SC_IHNMIntroMovieProc3(int param, void *refCon); + static int SC_IHNMHateProc(int param, void *refCon); + + private: + int IHNMIntroMovieProc1(int param); + int IHNMIntroMovieProc2(int param); + int IHNMIntroMovieProc3(int param); + int IHNMHateProc(int param); + + 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); + + private: + Event *ITEQueueDialogue(Event *q_event, int n_dialogues, const IntroDialogue dialogue[]); + Event *ITEQueueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]); + int ITEIntroAnimProc(int param); + int ITEIntroCave1Proc(int param); + int ITEIntroCave2Proc(int param); + int ITEIntroCave3Proc(int param); + int ITEIntroCave4Proc(int param); + int ITEIntroValleyProc(int param); + int ITEIntroTreeHouseProc(int param); + int ITEIntroFairePathProc(int param); + int ITEIntroFaireTentProc(int param); + +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp new file mode 100644 index 0000000000..3d3a626e9f --- /dev/null +++ b/engines/saga/script.cpp @@ -0,0 +1,806 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Scripting module: Script resource handling functions +#include "saga/saga.h" + +#include "saga/gfx.h" +#include "saga/console.h" + +#include "saga/script.h" +#include "saga/stream.h" +#include "saga/interface.h" +#include "saga/itedata.h" +#include "saga/scene.h" +#include "saga/events.h" +#include "saga/actor.h" +#include "saga/objectmap.h" +#include "saga/isomap.h" +#include "saga/rscfile.h" + +namespace Saga { + + + +// Initializes the scripting module. +// Loads script resource look-up table, initializes script data system +Script::Script(SagaEngine *vm) : _vm(vm) { + ResourceContext *resourceContext; + byte *resourcePointer; + size_t resourceLength; + int prevTell; + int i, j; + byte *stringsPointer; + size_t stringsLength; + + //initialize member variables + _abortEnabled = true; + _skipSpeeches = false; + _conversingThread = NULL; + + _firstObjectSet = false; + _secondObjectNeeded = false; + _pendingVerb = getVerbType(kVerbNone); + _currentVerb = getVerbType(kVerbNone); + _stickyVerb = getVerbType(kVerbWalkTo); + _leftButtonVerb = getVerbType(kVerbNone); + _rightButtonVerb = getVerbType(kVerbNone); + _pointerObject = ID_NOTHING; + + _staticSize = 0; + _commonBufferSize = COMMON_BUFFER_SIZE; + _commonBuffer = (byte*)malloc(_commonBufferSize); + memset(_commonBuffer, 0, _commonBufferSize); + + debug(8, "Initializing scripting subsystem"); + // Load script resource file context + _scriptContext = _vm->_resource->getContext(GAME_SCRIPTFILE); + if (_scriptContext == NULL) { + error("Script::Script() script context not found"); + } + + resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE); + if (resourceContext == NULL) { + error("Script::Script() resource context not found"); + } + + debug(3, "Loading module LUT from resource %i", _vm->getResourceDescription()->moduleLUTResourceId); + _vm->_resource->loadResource(resourceContext, _vm->getResourceDescription()->moduleLUTResourceId, resourcePointer, resourceLength); + + + // Create logical script LUT from resource + if (resourceLength % S_LUT_ENTRYLEN_ITECD == 0) { + _modulesLUTEntryLen = S_LUT_ENTRYLEN_ITECD; + } else if (resourceLength % S_LUT_ENTRYLEN_ITEDISK == 0) { + _modulesLUTEntryLen = S_LUT_ENTRYLEN_ITEDISK; + } else { + error("Script::Script() Invalid script lookup table length (%i)", resourceLength); + } + + // Calculate number of entries + _modulesCount = resourceLength / _modulesLUTEntryLen; + + debug(3, "LUT has %i entries", _modulesCount); + + // Allocate space for logical LUT + _modules = (ModuleData *)malloc(_modulesCount * sizeof(*_modules)); + if (_modules == NULL) { + memoryError("Script::Script()"); + } + + // Convert LUT resource to logical LUT + MemoryReadStreamEndian scriptS(resourcePointer, resourceLength, resourceContext->isBigEndian); + for (i = 0; i < _modulesCount; i++) { + memset(&_modules[i], 0, sizeof(ModuleData)); + + prevTell = scriptS.pos(); + _modules[i].scriptResourceId = scriptS.readUint16(); + _modules[i].stringsResourceId = scriptS.readUint16(); + _modules[i].voicesResourceId = scriptS.readUint16(); + + // Skip the unused portion of the structure + for (j = scriptS.pos(); j < prevTell + _modulesLUTEntryLen; j++) { + if (scriptS.readByte() != 0) + warning("Unused scriptLUT part isn't really unused for LUT %d (pos: %d)", i, j); + } + } + + free(resourcePointer); + + // TODO + // + // In ITE, the "main strings" resource contains both the verb strings + // and the object names. + // + // In IHNM, the "main strings" contains the verb strings, but not the + // object names. At least, I think that's the case. + + _vm->_resource->loadResource(resourceContext, _vm->getResourceDescription()->mainStringsResourceId, stringsPointer, stringsLength); + + _vm->loadStrings(_mainStrings, stringsPointer, stringsLength); + free(stringsPointer); + + setupScriptFuncList(); +} + +// Shut down script module gracefully; free all allocated module resources +Script::~Script() { + + debug(8, "Shutting down scripting subsystem."); + + _mainStrings.freeMem(); + + freeModules(); + free(_modules); + + free(_commonBuffer); +} + +void Script::loadModule(int scriptModuleNumber) { + byte *resourcePointer; + size_t resourceLength; + + // Validate script number + if ((scriptModuleNumber < 0) || (scriptModuleNumber >= _modulesCount)) { + error("Script::loadScript() Invalid script module number"); + } + + if (_modules[scriptModuleNumber].loaded) { + return; + } + + // Initialize script data structure + debug(3, "Loading script module #%d", scriptModuleNumber); + + _vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].scriptResourceId, resourcePointer, resourceLength); + + loadModuleBase(_modules[scriptModuleNumber], resourcePointer, resourceLength); + free(resourcePointer); + + _vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].stringsResourceId, resourcePointer, resourceLength); + + _vm->loadStrings(_modules[scriptModuleNumber].strings, resourcePointer, resourceLength); + free(resourcePointer); + + if (_modules[scriptModuleNumber].voicesResourceId > 0) { + _vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].voicesResourceId, resourcePointer, resourceLength); + + loadVoiceLUT(_modules[scriptModuleNumber].voiceLUT, resourcePointer, resourceLength); + free(resourcePointer); + } + + _modules[scriptModuleNumber].staticOffset = _staticSize; + _staticSize += _modules[scriptModuleNumber].staticSize; + if (_staticSize > _commonBufferSize) { + error("Script::loadModule() _staticSize > _commonBufferSize"); + } + _modules[scriptModuleNumber].loaded = true; +} + +void Script::freeModules() { + int i; + for (i = 0; i < _modulesCount; i++) { + if (_modules[i].loaded) { + _modules[i].freeMem(); + } + } + _staticSize = 0; +} + +void Script::loadModuleBase(ModuleData &module, const byte *resourcePointer, size_t resourceLength) { + int i; + + debug(3, "Loading module base..."); + + module.moduleBase = (byte*)malloc(resourceLength); + module.moduleBaseSize = resourceLength; + + memcpy(module.moduleBase, resourcePointer, resourceLength); + + MemoryReadStreamEndian scriptS(module.moduleBase, module.moduleBaseSize, _scriptContext->isBigEndian); + + module.entryPointsCount = scriptS.readUint16(); + scriptS.readUint16(); //skip + module.entryPointsTableOffset = scriptS.readUint16(); + scriptS.readUint16(); //skip + + if ((module.moduleBaseSize - module.entryPointsTableOffset) < (module.entryPointsCount * SCRIPT_TBLENTRY_LEN)) { + error("Script::loadModuleBase() Invalid table offset"); + } + + if (module.entryPointsCount > SCRIPT_MAX) { + error("Script::loadModuleBase()Script limit exceeded"); + } + + module.entryPoints = (EntryPoint *)malloc(module.entryPointsCount * sizeof(*module.entryPoints)); + if (module.entryPoints == NULL) { + memoryError("Script::loadModuleBase"); + } + + // Read in the entrypoint table + + module.staticSize = scriptS.readUint16(); + while (scriptS.pos() < module.entryPointsTableOffset) + scriptS.readByte(); + + for (i = 0; i < module.entryPointsCount; i++) { + // First uint16 is the offset of the entrypoint name from the start + // of the bytecode resource, second uint16 is the offset of the + // bytecode itself for said entrypoint + module.entryPoints[i].nameOffset = scriptS.readUint16(); + module.entryPoints[i].offset = scriptS.readUint16(); + + // Perform a simple range check on offset values + if ((module.entryPoints[i].nameOffset >= module.moduleBaseSize) || (module.entryPoints[i].offset >= module.moduleBaseSize)) { + error("Script::loadModuleBase() Invalid offset encountered in script entrypoint table"); + } + } +} + +void Script::loadVoiceLUT(VoiceLUT &voiceLUT, const byte *resourcePointer, size_t resourceLength) { + uint16 i; + + voiceLUT.voicesCount = resourceLength / 2; + + voiceLUT.voices = (uint16 *)malloc(voiceLUT.voicesCount * sizeof(*voiceLUT.voices)); + if (voiceLUT.voices == NULL) { + error("Script::loadVoiceLUT() not enough memory"); + } + + MemoryReadStreamEndian scriptS(resourcePointer, resourceLength, _scriptContext->isBigEndian); + + for (i = 0; i < voiceLUT.voicesCount; i++) { + voiceLUT.voices[i] = scriptS.readUint16(); + } +} + +// verb +void Script::showVerb(int statusColor) { + const char *verbName; + const char *object1Name; + const char *object2Name; + char statusString[STATUS_TEXT_LEN]; + + if (_leftButtonVerb == getVerbType(kVerbNone)) { + _vm->_interface->setStatusText(""); + return; + } + + verbName = _mainStrings.getString(_leftButtonVerb - 1); + + if (objectTypeId(_currentObject[0]) == kGameObjectNone) { + _vm->_interface->setStatusText(verbName, statusColor); + return; + } + + object1Name = _vm->getObjectName(_currentObject[0]); + + if (!_secondObjectNeeded) { + snprintf(statusString, STATUS_TEXT_LEN, "%s %s", verbName, object1Name); + _vm->_interface->setStatusText(statusString, statusColor); + return; + } + + + if (objectTypeId(_currentObject[1]) != kGameObjectNone) { + object2Name = _vm->getObjectName(_currentObject[1]); + } else { + object2Name = ""; + } + + if (_leftButtonVerb == getVerbType(kVerbGive)) { + snprintf(statusString, STATUS_TEXT_LEN, _vm->getTextString(kTextGiveTo), object1Name, object2Name); + _vm->_interface->setStatusText(statusString, statusColor); + } else { + if (_leftButtonVerb == getVerbType(kVerbUse)) { + snprintf(statusString, STATUS_TEXT_LEN, _vm->getTextString(kTextUseWidth), object1Name, object2Name); + _vm->_interface->setStatusText(statusString, statusColor); + } else { + snprintf(statusString, STATUS_TEXT_LEN, "%s %s", verbName, object1Name); + _vm->_interface->setStatusText(statusString, statusColor); + } + } +} + +int Script::getVerbType(VerbTypes verbType) { + if (_vm->getGameType() == GType_ITE) { + switch (verbType) { + case kVerbNone: + return kVerbITENone; + case kVerbWalkTo: + return kVerbITEWalkTo; + case kVerbGive: + return kVerbITEGive; + case kVerbUse: + return kVerbITEUse; + case kVerbEnter: + return kVerbITEEnter; + case kVerbLookAt: + return kVerbITELookAt; + case kVerbPickUp: + return kVerbITEPickUp; + case kVerbOpen: + return kVerbITEOpen; + case kVerbClose: + return kVerbITEClose; + case kVerbTalkTo: + return kVerbITETalkTo; + case kVerbWalkOnly: + return kVerbITEWalkOnly; + case kVerbLookOnly: + return kVerbITELookOnly; + case kVerbOptions: + return kVerbITEOptions; + } + } + else { + switch (verbType) { + case kVerbNone: + return kVerbIHNMNone; + case kVerbWalkTo: + return kVerbIHNMWalk; + case kVerbGive: + return kVerbIHNMGive; + case kVerbUse: + return kVerbIHNMUse; + case kVerbEnter: + return kVerbIHNMEnter; + case kVerbLookAt: + return kVerbIHNMLookAt; + case kVerbPickUp: + return kVerbIHNMTake; + case kVerbOpen: + return -2; + case kVerbClose: + return -2; + case kVerbTalkTo: + return kVerbIHNMTalkTo; + case kVerbWalkOnly: + return kVerbIHNMWalkOnly; + case kVerbLookOnly: + return kVerbIHNMLookOnly; + case kVerbOptions: + return kVerbIHNMOptions; + } + } + error("Script::getVerbType() unknown verb type %d", verbType); +} + +void Script::setVerb(int verb) { + _pendingObject[0] = ID_NOTHING; + _currentObject[0] = ID_NOTHING; + _pendingObject[1] = ID_NOTHING; + _currentObject[1] = ID_NOTHING; + _firstObjectSet = false; + _secondObjectNeeded = false; + + // The pointer object will be updated again immediately. This way the + // new verb will be applied to it. It's not exactly how the original + // engine did it, but it appears to work. + _pointerObject = ID_NOTHING; + + setLeftButtonVerb( verb ); + showVerb(); +} + +void Script::setLeftButtonVerb(int verb) { + int oldVerb = _currentVerb; + + _currentVerb = _leftButtonVerb = verb; + + if ((_currentVerb != oldVerb) && (_vm->_interface->getMode() == kPanelMain)){ + if (oldVerb > getVerbType(kVerbNone)) + _vm->_interface->setVerbState(oldVerb, 2); + + if (_currentVerb > getVerbType(kVerbNone)) + _vm->_interface->setVerbState(_currentVerb, 2); + } +} + +void Script::setRightButtonVerb(int verb) { + int oldVerb = _rightButtonVerb; + + _rightButtonVerb = verb; + + if ((_rightButtonVerb != oldVerb) && (_vm->_interface->getMode() == kPanelMain)){ + if (oldVerb > getVerbType(kVerbNone)) + _vm->_interface->setVerbState(oldVerb, 2); + + if (_rightButtonVerb > getVerbType(kVerbNone)) + _vm->_interface->setVerbState(_rightButtonVerb, 2); + } +} + +void Script::doVerb() { + int scriptEntrypointNumber = 0; + int scriptModuleNumber = 0; + int objectType; + Event event; + const char *excuseText; + int excuseSampleResourceId; + const HitZone *hitZone; + + objectType = objectTypeId(_pendingObject[0]); + + if (_pendingVerb == getVerbType(kVerbGive)) { + scriptEntrypointNumber = _vm->_actor->getObjectScriptEntrypointNumber(_pendingObject[1]); + if (_vm->_actor->getObjectFlags(_pendingObject[1]) & (kFollower|kProtagonist|kExtended)) { + scriptModuleNumber = 0; + } else { + scriptModuleNumber = _vm->_scene->getScriptModuleNumber(); + } + } else { + if (_pendingVerb == getVerbType(kVerbUse)) { + if ((objectTypeId(_pendingObject[1]) > kGameObjectNone) && (objectType < objectTypeId(_pendingObject[1]))) { + SWAP(_pendingObject[0], _pendingObject[1]); + objectType = objectTypeId(_pendingObject[0]); + } + } + + if (objectType == kGameObjectHitZone) { + scriptModuleNumber = _vm->_scene->getScriptModuleNumber(); + hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[0])); + if ((hitZone->getFlags() & kHitZoneExit) == 0) { + scriptEntrypointNumber = hitZone->getScriptNumber(); + } + } else { + if (objectType & (kGameObjectActor | kGameObjectObject)) { + scriptEntrypointNumber = _vm->_actor->getObjectScriptEntrypointNumber(_pendingObject[0]); + + if ((objectType == kGameObjectActor) && !(_vm->_actor->getObjectFlags(_pendingObject[0]) & (kFollower|kProtagonist|kExtended))) { + scriptModuleNumber = _vm->_scene->getScriptModuleNumber(); + } else { + scriptModuleNumber = 0; + } + } + } + } + + if (scriptEntrypointNumber > 0) { + + event.type = kEvTOneshot; + event.code = kScriptEvent; + event.op = kEventExecNonBlocking; + event.time = 0; + event.param = scriptModuleNumber; + event.param2 = scriptEntrypointNumber; + event.param3 = _pendingVerb; // Action + event.param4 = _pendingObject[0]; // Object + event.param5 = _pendingObject[1]; // With Object + event.param6 = (objectType == kGameObjectActor) ? _pendingObject[0] : ID_PROTAG; // Actor + + _vm->_events->queue(&event); + + } else { + _vm->getExcuseInfo(_pendingVerb, excuseText, excuseSampleResourceId); + if (excuseText) { + // In Floppy versions we don't have excuse texts + if (!(_vm->getFeatures() & GF_CD_FX)) + excuseSampleResourceId = -1; + + _vm->_actor->actorSpeech(ID_PROTAG, &excuseText, 1, excuseSampleResourceId, 0); + } + } + + if ((_currentVerb == getVerbType(kVerbWalkTo)) || (_currentVerb == getVerbType(kVerbLookAt))) { + _stickyVerb = _currentVerb; + } + + _pendingVerb = getVerbType(kVerbNone); + _currentObject[0] = _currentObject[1] = ID_NOTHING; + setLeftButtonVerb(_stickyVerb); + + setPointerVerb(); +} + +void Script::setPointerVerb() { + if (_vm->_interface->isActive()) { + _pointerObject = ID_PROTAG; + whichObject(_vm->mousePos()); + } +} + +void Script::hitObject(bool leftButton) { + int verb; + verb = leftButton ? _leftButtonVerb : _rightButtonVerb; + + if (verb > getVerbType(kVerbNone)) { + if (_firstObjectSet) { + if (_secondObjectNeeded) { + _pendingObject[0] = _currentObject[0]; + _pendingObject[1] = _currentObject[1]; + _pendingVerb = verb; + + _leftButtonVerb = verb; + if (_pendingVerb > getVerbType(kVerbNone)) + showVerb(kITEColorBrightWhite); + else + showVerb(); + + _secondObjectNeeded = false; + _firstObjectSet = false; + return; + } + } else { + if (verb == getVerbType(kVerbGive)) { + _secondObjectNeeded = true; + } else { + if (verb == getVerbType(kVerbUse)) { + + if (_currentObjectFlags[0] & kObjUseWith) { + _secondObjectNeeded = true; + } + } + } + + if (!_secondObjectNeeded) { + _pendingObject[0] = _currentObject[0]; + _pendingObject[1] = ID_NOTHING; + _pendingVerb = verb; + + _secondObjectNeeded = false; + _firstObjectSet = false; + } else { + _firstObjectSet = true; + } + } + + _leftButtonVerb = verb; + if (_pendingVerb > getVerbType(kVerbNone)) + showVerb(kITEColorBrightWhite); + else + showVerb(); + } + +} + +void Script::playfieldClick(const Point& mousePoint, bool leftButton) { + Location pickLocation; + const HitZone *hitZone; + Point specialPoint; + + _vm->_actor->abortSpeech(); + + if ((_vm->_actor->_protagonist->_currentAction != kActionWait) && + (_vm->_actor->_protagonist->_currentAction != kActionFreeze) && + (_vm->_actor->_protagonist->_currentAction != kActionWalkToLink) && + (_vm->_actor->_protagonist->_currentAction != kActionWalkToPoint)) { + return; + } + if (_pendingVerb > getVerbType(kVerbNone)) { + setLeftButtonVerb(getVerbType(kVerbWalkTo)); + } + + if (_pointerObject != ID_NOTHING) { + hitObject( leftButton ); + } else { + _pendingObject[0] = ID_NOTHING; + _pendingObject[1] = ID_NOTHING; + _pendingVerb = getVerbType(kVerbWalkTo); + } + + + // tiled stuff + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _vm->_isoMap->screenPointToTileCoords(mousePoint, pickLocation); + } else { + pickLocation.fromScreenPoint(mousePoint); + } + + + hitZone = NULL; + + if (objectTypeId(_pendingObject[0]) == kGameObjectHitZone) { + hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[0])); + } else { + if ((_pendingVerb == getVerbType(kVerbUse)) && (objectTypeId(_pendingObject[1]) == kGameObjectHitZone)) { + hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[1])); + } + } + + if (hitZone != NULL) { + if (hitZone->getFlags() & kHitZoneNoWalk) { + _vm->_actor->actorFaceTowardsPoint(ID_PROTAG, pickLocation); + doVerb(); + return; + } + + if (hitZone->getFlags() & kHitZoneProject) { + if (!hitZone->getSpecialPoint(specialPoint)) { + // Original behaved this way and this prevents from crash + // at ruins. See bug #1257459 + specialPoint.x = specialPoint.y = 0; + } + + // tiled stuff + if (_vm->_scene->getFlags() & kSceneFlagISO) { + pickLocation.u() = specialPoint.x; + pickLocation.v() = specialPoint.y; + pickLocation.z = _vm->_actor->_protagonist->_location.z; + } else { + pickLocation.fromScreenPoint(specialPoint); + } + } + } + + if ((_pendingVerb == getVerbType(kVerbWalkTo)) || + (_pendingVerb == getVerbType(kVerbPickUp)) || + (_pendingVerb == getVerbType(kVerbOpen)) || + (_pendingVerb == getVerbType(kVerbClose)) || + (_pendingVerb == getVerbType(kVerbUse))) { + _vm->_actor->actorWalkTo(ID_PROTAG, pickLocation); + } else { + if (_pendingVerb == getVerbType(kVerbLookAt)) { + if (objectTypeId(_pendingObject[0]) != kGameObjectActor ) { + _vm->_actor->actorWalkTo(ID_PROTAG, pickLocation); + } else { + doVerb(); + } + } else { + if ((_pendingVerb == getVerbType(kVerbTalkTo)) || + (_pendingVerb == getVerbType(kVerbGive))) { + doVerb(); + } + } + } +} + +void Script::whichObject(const Point& mousePoint) { + uint16 objectId; + int16 objectFlags; + int newRightButtonVerb; + uint16 newObjectId; + ActorData *actor; + ObjectData *obj; + Point pickPoint; + Location pickLocation; + int hitZoneIndex; + const HitZone * hitZone; + PanelButton * panelButton; + + objectId = ID_NOTHING; + objectFlags = 0; + _leftButtonVerb = _currentVerb; + newRightButtonVerb = getVerbType(kVerbNone); + + if (_vm->_actor->_protagonist->_currentAction != kActionWalkDir) { + if (_vm->_scene->getHeight() >= mousePoint.y) { + newObjectId = _vm->_actor->hitTest(mousePoint, true); + + if (newObjectId != ID_NOTHING) { + if (objectTypeId(newObjectId) == kGameObjectObject) { + objectId = newObjectId; + objectFlags = 0; + newRightButtonVerb = getVerbType(kVerbLookAt); + + if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) { + objectId = ID_NOTHING; + newObjectId = ID_NOTHING; + } + } else { + actor = _vm->_actor->getActor(newObjectId); + objectId = newObjectId; + objectFlags = kObjUseWith; + newRightButtonVerb = getVerbType(kVerbTalkTo); + + if ((_currentVerb == getVerbType(kVerbPickUp)) || + (_currentVerb == getVerbType(kVerbOpen)) || + (_currentVerb == getVerbType(kVerbClose)) || + ((_currentVerb == getVerbType(kVerbGive)) && !_firstObjectSet) || + ((_currentVerb == getVerbType(kVerbUse)) && !(actor->_flags & kFollower))) { + objectId = ID_NOTHING; + newObjectId = ID_NOTHING; + } + } + } + + if (newObjectId == ID_NOTHING) { + + pickPoint = mousePoint; + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + pickPoint.y -= _vm->_actor->_protagonist->_location.z; + _vm->_isoMap->screenPointToTileCoords(pickPoint, pickLocation); + pickLocation.toScreenPointUV(pickPoint); + } + + hitZoneIndex = _vm->_scene->_objectMap->hitTest(pickPoint); + + if ((hitZoneIndex != -1)) { + hitZone = _vm->_scene->_objectMap->getHitZone(hitZoneIndex); + objectId = hitZone->getHitZoneId(); + objectFlags = 0; + newRightButtonVerb = hitZone->getRightButtonVerb() & 0x7f; + + if (_vm->getGameType() == GType_ITE) { + + if (newRightButtonVerb == getVerbType(kVerbWalkOnly)) { + if (_firstObjectSet) { + objectId = ID_NOTHING; + } else { + newRightButtonVerb = _leftButtonVerb = getVerbType(kVerbWalkTo); + } + } else { + if (newRightButtonVerb == getVerbType(kVerbLookOnly)) { + if (_firstObjectSet) { + objectId = ID_NOTHING; + } else { + newRightButtonVerb = _leftButtonVerb = getVerbType(kVerbLookAt); + } + } + } + + if (newRightButtonVerb >= getVerbType(kVerbOptions)) { + newRightButtonVerb = getVerbType(kVerbNone); + } + } + + if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) { + objectId = ID_NOTHING; + newObjectId = ID_NOTHING; + } + + if ((_leftButtonVerb == getVerbType(kVerbUse)) && (hitZone->getRightButtonVerb() & 0x80)) { + objectFlags = kObjUseWith; + } + } + } + } else { + if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) { + // no way + } else { + panelButton = _vm->_interface->inventoryHitTest(mousePoint); + if (panelButton) { + objectId = _vm->_interface->getInventoryContentByPanelButton(panelButton); + if (objectId != 0) { + obj = _vm->_actor->getObj(objectId); + newRightButtonVerb = getVerbType(kVerbLookAt); + if (obj->_interactBits & kObjUseWith) { + objectFlags = kObjUseWith; + } + } + } + } + + if ((_currentVerb == getVerbType(kVerbPickUp)) || (_currentVerb == getVerbType(kVerbTalkTo)) || (_currentVerb == getVerbType(kVerbWalkTo))) { + _leftButtonVerb = getVerbType(kVerbLookAt); + } + } + } + + if (objectId != _pointerObject) { + _pointerObject = objectId; + _currentObject[_firstObjectSet ? 1 : 0] = objectId; + _currentObjectFlags[_firstObjectSet ? 1 : 0] = objectFlags; + if (_pendingVerb == getVerbType(kVerbNone)) { + showVerb(); + } + } + + if (newRightButtonVerb != _rightButtonVerb) { + setRightButtonVerb(newRightButtonVerb); + } +} + +} // End of namespace Saga diff --git a/engines/saga/script.h b/engines/saga/script.h new file mode 100644 index 0000000000..bc11013b3f --- /dev/null +++ b/engines/saga/script.h @@ -0,0 +1,637 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Scripting module private header + +#ifndef SAGA_SCRIPT_H +#define SAGA_SCRIPT_H + +#include "saga/font.h" +#include "saga/list.h" + +namespace Saga { + +#define COMMON_BUFFER_SIZE 1024 // Why 1024? + +#define S_LUT_ENTRYLEN_ITECD 22 +#define S_LUT_ENTRYLEN_ITEDISK 16 + +#define SCRIPT_TBLENTRY_LEN 4 + +#define SCRIPT_MAX 5000 + +#define ITE_SCRIPT_FUNCTION_MAX 78 +#define IHNM_SCRIPT_FUNCTION_MAX 105 +#define DEFAULT_THREAD_STACK_SIZE 256 + +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 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 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 +}; + +enum OpCodes { + opNextBlock = 0x01, + opDup = 0x02, + opDrop = 0x03, + opZero = 0x04, + opOne = 0x05, + opConstint = 0x06, +//... + opStrlit = 0x08, +//... + opGetFlag = 0x0B, + opGetInt = 0x0C, +//... + opPutFlag = 0x0F, + opPutInt = 0x10, + //... + opPutFlagV = 0x13, + opPutIntV = 0x14, +//... + opCall = 0x17, + opCcall = 0x18, + opCcallV = 0x19, + opEnter = 0x1A, + opReturn = 0x1B, + opReturnV = 0x1C, + opJmp = 0x1D, + opJmpTrueV = 0x1E, + opJmpFalseV = 0x1F, + opJmpTrue = 0x20, + opJmpFalse = 0x21, + opJmpSwitch = 0x22, +//... + opJmpRandom = 0x24, + opNegate = 0x25, + opNot = 0x26, + opCompl = 0x27, + opIncV = 0x28, + opDecV = 0x29, + opPostInc = 0x2A, + opPostDec = 0x2B, + opAdd = 0x2C, + opSub = 0x2D, + opMul = 0x2E, + opDiv = 0x2F, + opMod = 0x30, +//... + opEq = 0x33, + opNe = 0x34, + opGt = 0x35, + opLt = 0x36, + opGe = 0x37, + opLe = 0x38, +//... + opRsh = 0x3F, + opLsh = 0x40, + opAnd = 0x41, + opOr = 0x42, + opXor = 0x43, + opLAnd = 0x44, + opLOr = 0x45, + opLXor = 0x46, +//... + opSpeak = 0x53, + opDialogBegin = 0x54, + opDialogEnd = 0x55, + opReply = 0x56, + opAnimate = 0x57 +}; + +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; +}; + +struct VoiceLUT { + uint16 voicesCount; + uint16 *voices; + void freeMem() { + voicesCount = 0; + free(voices); + } + VoiceLUT() { + memset(this, 0, sizeof(*this)); + } +}; + +struct ModuleData { + bool loaded; // is it loaded or not? + int scriptResourceId; + int stringsResourceId; + int voicesResourceId; + + byte *moduleBase; // all base module + uint16 moduleBaseSize; // base module size + uint16 staticSize; // size of static data + uint staticOffset; // offset of static data begining in _commonBuffer + + uint16 entryPointsTableOffset; // offset of entrypoint table in moduleBase + uint16 entryPointsCount; + EntryPoint *entryPoints; + + StringsTable strings; + VoiceLUT voiceLUT; + void freeMem() { + strings.freeMem(); + voiceLUT.freeMem(); + free(moduleBase); + free(entryPoints); + memset(this, 0x0, sizeof(*this)); + } +}; + +class ScriptThread { +public: + uint16 *_stackBuf; + uint16 _stackSize; // stack size in uint16 + + uint16 _stackTopIndex; + uint16 _frameIndex; + + uint16 _threadVars[kThreadVarMax]; + + byte *_moduleBase; // + uint16 _moduleBaseSize; + + byte *_commonBase; // + byte *_staticBase; // + VoiceLUT *_voiceLUT; // + StringsTable *_strings; // + + int _flags; // ThreadFlags + int _waitType; // ThreadWaitTypes + uint _sleepTime; + void *_threadObj; // which object we're handling + + int16 _returnValue; + + uint16 _instructionOffset; // Instruction offset + + int32 _frameWait; + +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 (int16)_stackBuf[_stackTopIndex]; + } + + uint pushedSize() { + return _stackSize - _stackTopIndex - 2; + } + + void push(int16 value) { + if (_stackTopIndex <= 0) { + error("ScriptThread::push() stack overflow"); + } + _stackBuf[--_stackTopIndex] = (uint16)value; + } + + int16 pop() { + if (_stackTopIndex >= _stackSize) { + error("ScriptThread::pop() stack underflow"); + } + return (int16)_stackBuf[_stackTopIndex++]; + } + + +// wait stuff + void wait(int waitType) { + _waitType = waitType; + _flags |= kTFlagWaiting; + } + + void waitWalk(void *threadObj) { + wait(kWaitTypeWalk); + _threadObj = threadObj; + } + + void waitDelay(int sleepTime) { + wait(kWaitTypeDelay); + _sleepTime = sleepTime; + } + + ScriptThread() { + memset(this, 0xFE, sizeof(*this)); + _stackBuf = NULL; + } + ~ScriptThread() { + free(_stackBuf); + } +}; + +typedef SortedList<ScriptThread> ScriptThreadList; + + +#define SCRIPTFUNC_PARAMS ScriptThread *thread, int nArgs, bool &disContinue + +class Script { +public: + StringsTable _mainStrings; + + Script(SagaEngine *vm); + ~Script(); + + void loadModule(int scriptModuleNumber); + void freeModules(); + + 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[0] = ID_NOTHING; + setPointerVerb(); + } + int getVerbType(VerbTypes verbType); + +private: + // 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; + + uint16 _modulesLUTEntryLen; + ModuleData *_modules; + int _modulesCount; + TextListEntry *_placardTextEntry; + +protected: + friend class SagaEngine; + byte *_commonBuffer; + uint _commonBufferSize; + +private: + 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; + +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); + void abortAllThreads(void); + + void wakeUpActorThread(int waitType, void *threadObj); + void wakeUpThreads(int waitType); + void wakeUpThreadsDelayed(int waitType, int sleepTime); + + void loadVoiceLUT(VoiceLUT &voiceLUT, const byte *resourcePointer, size_t resourceLength); + +private: + void loadModuleBase(ModuleData &module, const byte *resourcePointer, size_t resourceLength); + + // runThread returns true if we should break running of other threads + bool runThread(ScriptThread *thread, uint instructionLimit); + void setThreadEntrypoint(ScriptThread *thread, int entrypointNumber); + +public: + void finishDialog(int replyID, int flags, int bitOffset); + +private: + + typedef void (Script::*ScriptFunctionType)(SCRIPTFUNC_PARAMS); + + struct ScriptFunctionDescription { + ScriptFunctionType scriptFunction; + const char *scriptFunctionName; + }; + const ScriptFunctionDescription *_scriptFunctionsList; + + void setupScriptFuncList(void); + + 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 SF_cycleColors(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 sf87(SCRIPTFUNC_PARAMS); + void sf88(SCRIPTFUNC_PARAMS); + void sf89(SCRIPTFUNC_PARAMS); + void sfGetPoints(SCRIPTFUNC_PARAMS); + void sfSetGlobalFlag(SCRIPTFUNC_PARAMS); + void sfClearGlobalFlag(SCRIPTFUNC_PARAMS); + void sfTestGlobalFlag(SCRIPTFUNC_PARAMS); + void sfSetPoints(SCRIPTFUNC_PARAMS); + void sf103(SCRIPTFUNC_PARAMS); + void sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS); + + void SF_stub(const char *name, ScriptThread *thread, int nArgs); +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/sfuncs.cpp b/engines/saga/sfuncs.cpp new file mode 100644 index 0000000000..9caa15150b --- /dev/null +++ b/engines/saga/sfuncs.cpp @@ -0,0 +1,2038 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// 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/script.h" +#include "saga/objectmap.h" + +#include "saga/scene.h" +#include "saga/isomap.h" +#include "saga/resnames.h" + +#include "common/config-manager.h" + +namespace Saga { + +#define OPCODE(x) {&Script::x, #x} + +void Script::setupScriptFuncList(void) { + static const ScriptFunctionDescription ITEscriptFunctionsList[ITE_SCRIPT_FUNCTION_MAX] = { + OPCODE(sfPutString), + 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(SF_cycleColors), + 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(sfPlacard), + OPCODE(sfPlacardOff), + 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(sfPickClimbOutPos), + OPCODE(sfTossRif), + OPCODE(sfShowControls), + OPCODE(sfShowMap), + OPCODE(sfPuzzleWon), + OPCODE(sfEnableEscape), + OPCODE(sfPlaySound), + OPCODE(sfPlayLoopedSound), + OPCODE(sfGetDeltaFrame), + OPCODE(sfShowProtect), + OPCODE(sfProtectResult), + OPCODE(sfRand), + OPCODE(sfFadeMusic), + OPCODE(sfPlayVoice) + }; + +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(sfNull), + 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(SF_cycleColors), + 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(sf87), + OPCODE(sf88), + OPCODE(sf89), + OPCODE(sfVstopFX), + OPCODE(sfVstopLoopedFX), + OPCODE(sfNull), + OPCODE(sfDemoIsInteractive), + OPCODE(sfVsetTrack), + OPCODE(sfGetPoints), + OPCODE(sfSetGlobalFlag), + OPCODE(sfClearGlobalFlag), + OPCODE(sfTestGlobalFlag), + OPCODE(sfSetPoints), + OPCODE(sfSetSpeechBox), + OPCODE(sfDebugShowData), + OPCODE(sfWaitFramesEsc), + OPCODE(sf103), + OPCODE(sfDisableAbortSpeeches) + }; + if (_vm->getGameType() == GType_IHNM) + _scriptFunctionsList = IHNMscriptFunctionsList; + else + _scriptFunctionsList = ITEscriptFunctionsList; +} + +// Script function #0 (0x00) +// Print a debugging message +void Script::sfPutString(SCRIPTFUNC_PARAMS) { + const char *str; + str = thread->_strings->getString(thread->pop()); + + _vm->_console->DebugPrintf("sfPutString: %s\n",str); + debug(0, "sfPutString: %s", str); +} + +// Script function #1 (0x01) blocking +// Param1: time in ticks +void Script::sfWait(SCRIPTFUNC_PARAMS) { + int16 time; + time = thread->pop(); + + if (!_skipSpeeches) { + thread->waitDelay(ticksToMSec(time)); // put thread to sleep + } +} + +// Script function #2 (0x02) +void Script::sfTakeObject(SCRIPTFUNC_PARAMS) { + uint16 objectId = thread->pop(); + ObjectData *obj; + obj = _vm->_actor->getObj(objectId); + if (obj->_sceneNumber != ITE_SCENE_INV) { + obj->_sceneNumber = ITE_SCENE_INV; + //4debug for (int j=0;j<17;j++) + _vm->_interface->addToInventory(objectId); + } +} + +// Script function #3 (0x03) +// Check if an object is carried. +void Script::sfIsCarried(SCRIPTFUNC_PARAMS) { + uint16 objectId = thread->pop(); + CommonObjectData *object; + if (_vm->_actor->validObjId(objectId)) { + object = _vm->_actor->getObj(objectId); + thread->_returnValue = (object->_sceneNumber == ITE_SCENE_INV) ? 1 : 0; + } else { + thread->_returnValue = 0; + } + + +} + +// Script function #4 (0x04) nonblocking +// Set the command display to the specified text string +// Param1: dialogue index of string +void Script::sfStatusBar(SCRIPTFUNC_PARAMS) { + int16 stringIndex = thread->pop(); + + _vm->_interface->setStatusText(thread->_strings->getString(stringIndex)); +} + +// Script function #5 (0x05) +void Script::sfMainMode(SCRIPTFUNC_PARAMS) { + _vm->_actor->_centerActor = _vm->_actor->_protagonist; + + showVerb(); + _vm->_interface->activate(); + _vm->_interface->setMode(kPanelMain); + + if (_vm->getGameType() == GType_ITE) + setPointerVerb(); +} + +// Script function #6 (0x06) blocking +// Param1: actor id +// Param2: actor x +// Param3: actor y +void Script::sfScriptWalkTo(SCRIPTFUNC_PARAMS) { + uint16 actorId; + Location actorLocation; + ActorData *actor; + + actorId = thread->pop(); + actorLocation.x = thread->pop(); + actorLocation.y = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + actorLocation.z = actor->_location.z; + + actor->_flags &= ~kFollower; + + if (_vm->_actor->actorWalkTo(actorId, actorLocation)) { + thread->waitWalk(actor); + } +} + +// Script function #7 (0x07) +// Param1: actor id +// Param2: action +// Param3: theObject +// Param4: withObject +void Script::sfScriptDoAction(SCRIPTFUNC_PARAMS) { + uint16 objectId; + uint16 action; + uint16 theObject; + uint16 withObject; + int16 scriptEntryPointNumber; + int16 moduleNumber; + ActorData *actor; + ObjectData *obj; + const HitZone *hitZone; + Event event; + + objectId = thread->pop(); + action = thread->pop(); + theObject = thread->pop(); + withObject = thread->pop(); + + switch (objectTypeId(objectId)) { + case kGameObjectObject: + obj = _vm->_actor->getObj(objectId); + scriptEntryPointNumber = obj->_scriptEntrypointNumber; + if (scriptEntryPointNumber <= 0) { + return; + } + moduleNumber = 0; + break; + case kGameObjectActor: + actor = _vm->_actor->getActor(objectId); + scriptEntryPointNumber = actor->_scriptEntrypointNumber; + if (scriptEntryPointNumber <= 0) { + return; + } + if (actor->_flags & (kProtagonist | kFollower)) { + moduleNumber = 0; + } else { + moduleNumber = _vm->_scene->getScriptModuleNumber(); + } + break; + case kGameObjectHitZone: + case kGameObjectStepZone: + if (objectTypeId(objectId) == kGameObjectHitZone) { + hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId)); + } else { + hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId)); + } + scriptEntryPointNumber = hitZone->getScriptNumber(); + moduleNumber = _vm->_scene->getScriptModuleNumber(); + break; + default: + error("Script::sfScriptDoAction wrong object type 0x%X", objectId); + } + + event.type = kEvTOneshot; + event.code = kScriptEvent; + event.op = kEventExecNonBlocking; + event.time = 0; + event.param = moduleNumber; + event.param2 = scriptEntryPointNumber; + event.param3 = action; // Action + event.param4 = theObject; // Object + event.param5 = withObject; // With Object + event.param6 = objectId; + + _vm->_events->queue(&event); +} + +// Script function #8 (0x08) nonblocking +// Param1: actor id +// Param2: actor orientation +void Script::sfSetActorFacing(SCRIPTFUNC_PARAMS) { + int16 actorId; + int actorDirection; + ActorData *actor; + + actorId = thread->pop(); + actorDirection = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + actor->_facingDirection = actor->_actionDirection = actorDirection; + actor->_targetObject = ID_NOTHING; +} + +// Script function #9 (0x09) +void Script::sfStartBgdAnim(SCRIPTFUNC_PARAMS) { + int16 animId = thread->pop(); + int16 cycles = thread->pop(); + + _vm->_anim->setCycles(animId, cycles); + _vm->_anim->setFrameTime(animId, ticksToMSec(kRepeatSpeedTicks)); + _vm->_anim->play(animId, 0); + + debug(1, "sfStartBgdAnim(%d, %d)", animId, cycles); +} + +// Script function #10 (0x0A) +void Script::sfStopBgdAnim(SCRIPTFUNC_PARAMS) { + int16 animId = thread->pop(); + + _vm->_anim->stop(animId); + + debug(1, "sfStopBgdAnim(%d)", animId); +} + +// Script function #11 (0x0B) nonblocking +// If the parameter is true, the user interface is disabled while script +// continues to run. If the parameter is false, the user interface is +// reenabled. +// Param1: boolean +void Script::sfLockUser(SCRIPTFUNC_PARAMS) { + int16 lock; + + lock = thread->pop(); + + if (lock) { + _vm->_interface->deactivate(); + } else { + _vm->_interface->activate(); + } + +} + +// Script function #12 (0x0C) +// Disables mouse input, etc. +void Script::sfPreDialog(SCRIPTFUNC_PARAMS) { + _vm->_interface->deactivate(); + _vm->_interface->converseClear(); + if (_vm->_interface->isInMainMode()) + _vm->_interface->setMode(kPanelConverse); + else + _vm->_interface->converseDisplayText(); + + _vm->_interface->setMode(kPanelNull); +} + +// Script function #13 (0x0D) +void Script::sfKillActorThreads(SCRIPTFUNC_PARAMS) { + ScriptThread *anotherThread; + ScriptThreadList::iterator threadIterator; + int16 actorId; + + actorId = thread->pop(); + + + for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) { + anotherThread = threadIterator.operator->(); + if ((anotherThread != thread) && (anotherThread->_threadVars[kThreadVarActor] == actorId)) { + anotherThread->_flags &= ~kTFlagWaiting; + anotherThread->_flags |= kTFlagAborted; + } + } +} + +// Script function #14 (0x0E) +// Param1: actor id +// Param2: object id +void Script::sfFaceTowards(SCRIPTFUNC_PARAMS) { + int16 actorId; + int16 targetObject; + ActorData *actor; + + actorId = thread->pop(); + targetObject = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + actor->_targetObject = targetObject; +} + +// Script function #15 (0x0F) +// Param1: actor id +// Param2: target object +void Script::sfSetFollower(SCRIPTFUNC_PARAMS) { + int16 actorId; + int16 targetObject; + + ActorData *actor; + + actorId = thread->pop(); + targetObject = thread->pop(); + + debug(1, "sfSetFollower(%d, %d) [%d]", actorId, targetObject, _vm->_actor->actorIdToIndex(actorId)); + + actor = _vm->_actor->getActor(actorId); + actor->_targetObject = targetObject; + if (targetObject != ID_NOTHING) { + actor->_flags |= kFollower; + actor->_actorFlags &= ~kActorNoFollow; + } else { + actor->_flags &= ~kFollower; + } +} + +// Script function #16 (0x10) +void Script::sfScriptGotoScene(SCRIPTFUNC_PARAMS) { + int16 sceneNumber; + int16 entrance; + + sceneNumber = thread->pop(); + entrance = thread->pop(); + if (sceneNumber < 0) { + _vm->shutDown(); + return; + } + + if (_vm->getGameType() == GType_IHNM) { + warning("FIXME: implement sfScriptGotoScene differences for IHNM"); + + // Since it doesn't look like the IHNM scripts remove the + // cutaway after the intro, this is probably the best place to + // to it. + _vm->_anim->clearCutaway(); + } + + // It is possible to leave scene when converse panel is on, + // particulalrly it may happen at Moneychanger tent. This + // prevent this from happening. + if (_vm->_interface->getMode() == kPanelConverse) { + _vm->_interface->setMode(kPanelMain); + } + + _vm->_scene->changeScene(sceneNumber, entrance, (sceneNumber == ITE_SCENE_ENDCREDIT1) ? kTransitionFade : kTransitionNoFade); + + //TODO: placard stuff + _pendingVerb = _vm->_script->getVerbType(kVerbNone); + _currentObject[0] = _currentObject[1] = ID_NOTHING; + showVerb(); +} + +// Script function #17 (0x11) +// Param1: object id +// Param2: sprite index +void Script::sfSetObjImage(SCRIPTFUNC_PARAMS) { + uint16 objectId; + uint16 spriteId; + ObjectData *obj; + + objectId = thread->pop(); + spriteId = thread->pop(); + + obj = _vm->_actor->getObj(objectId); + obj->_spriteListResourceId = OBJ_SPRITE_BASE + spriteId; + _vm->_interface->refreshInventory(); +} + +// Script function #18 (0x12) +// Param1: object id +// Param2: name index +void Script::sfSetObjName(SCRIPTFUNC_PARAMS) { + uint16 objectId; + uint16 nameIdx; + ObjectData *obj; + + objectId = thread->pop(); + nameIdx = thread->pop(); + + obj = _vm->_actor->getObj(objectId); + obj->_nameIndex = nameIdx; +} + +// Script function #19 (0x13) +// Param1: object id +void Script::sfGetObjImage(SCRIPTFUNC_PARAMS) { + uint16 objectId; + ObjectData *obj; + + objectId = thread->pop(); + + obj = _vm->_actor->getObj(objectId); + + if (_vm->getGameType() == GType_IHNM) + thread->_returnValue = obj->_spriteListResourceId; + else + thread->_returnValue = obj->_spriteListResourceId - OBJ_SPRITE_BASE; +} + +// Script function #20 (0x14) +void Script::sfGetNumber(SCRIPTFUNC_PARAMS) { + if (_vm->_interface->_statusTextInputState == kStatusTextInputFirstRun) { + _vm->_interface->enterStatusString(); + thread->wait(kWaitTypeStatusTextInput); + disContinue = true; + } else { + if (_vm->_interface->_statusTextInputState == kStatusTextInputAborted) { + thread->_returnValue = -1; + } else { + thread->_returnValue = atoi(_vm->_interface->_statusTextInputString); + } + + _vm->_interface->_statusTextInputState = kStatusTextInputFirstRun; + } +} + +// Script function #21 (0x15) +// Param1: door # +void Script::sfScriptOpenDoor(SCRIPTFUNC_PARAMS) { + int16 doorNumber; + doorNumber = thread->pop(); + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _vm->_isoMap->setTileDoorState(doorNumber, 1); + } else { + _vm->_scene->setDoorState(doorNumber, 0); + } +} + +// Script function #22 (0x16) +// Param1: door # +void Script::sfScriptCloseDoor(SCRIPTFUNC_PARAMS) { + int16 doorNumber; + doorNumber = thread->pop(); + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _vm->_isoMap->setTileDoorState(doorNumber, 0); + } else { + _vm->_scene->setDoorState(doorNumber, 0xff); + } +} + +// Script function #23 (0x17) +void Script::sfSetBgdAnimSpeed(SCRIPTFUNC_PARAMS) { + int16 animId = thread->pop(); + int16 speed = thread->pop(); + + _vm->_anim->setFrameTime(animId, ticksToMSec(speed)); + debug(1, "sfSetBgdAnimSpeed(%d, %d)", animId, speed); +} + +// Script function #24 (0x18) +void Script::SF_cycleColors(SCRIPTFUNC_PARAMS) { + SF_stub("SF_cycleColors", thread, nArgs); + + error("Please, report this to sev"); +} + +// Script function #25 (0x19) +// Param1: actor id +void Script::sfDoCenterActor(SCRIPTFUNC_PARAMS) { + int16 actorId; + actorId = thread->pop(); + + _vm->_actor->_centerActor = _vm->_actor->getActor(actorId); +} + +// Script function #26 (0x1A) nonblocking +// Starts the specified animation +void Script::sfStartBgdAnimSpeed(SCRIPTFUNC_PARAMS) { + int16 animId = thread->pop(); + int16 cycles = thread->pop(); + int16 speed = thread->pop(); + + _vm->_anim->setCycles(animId, cycles); + _vm->_anim->setFrameTime(animId, ticksToMSec(speed)); + _vm->_anim->play(animId, 0); + + debug(1, "sfStartBgdAnimSpeed(%d, %d, %d)", animId, cycles, speed); +} + +// Script function #27 (0x1B) nonblocking +// Param1: actor id +// Param2: actor x +// Param3: actor y +void Script::sfScriptWalkToAsync(SCRIPTFUNC_PARAMS) { + int16 actorId; + Location actorLocation; + ActorData *actor; + + actorId = thread->pop(); + actorLocation.x = thread->pop(); + actorLocation.y = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + actorLocation.z = actor->_location.z; + + actor->_flags &= ~kFollower; + + _vm->_actor->actorWalkTo(actorId, actorLocation); +} + +// Script function #28 (0x1C) +void Script::sfEnableZone(SCRIPTFUNC_PARAMS) { + uint16 objectId = thread->pop(); + int16 flag = thread->pop(); + HitZone *hitZone; + + if (objectTypeId(objectId) == kGameObjectHitZone) { + hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(objectId)); + } else { + hitZone = _vm->_scene->_actionMap->getHitZone(objectIdToIndex(objectId)); + } + + if (flag) { + hitZone->setFlag(kHitZoneEnabled); + } else { + hitZone->clearFlag(kHitZoneEnabled); + _vm->_actor->_protagonist->_lastZone = NULL; + } +} + +// Script function #29 (0x1D) +// Param1: actor id +// Param2: current action +void Script::sfSetActorState(SCRIPTFUNC_PARAMS) { + int16 actorId; + int currentAction; + ActorData *actor; + + actorId = thread->pop(); + currentAction = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + + if ((currentAction >= kActionWalkToPoint) && (currentAction <= kActionWalkToPoint)) { + wakeUpActorThread(kWaitTypeWalk, actor); + } + actor->_currentAction = currentAction; + actor->_actorFlags &= ~kActorBackwards; +} + +// Script function #30 (0x1E) nonblocking +// Param1: actor id +// Param2: actor pos x +// Param3: actor pos y +void Script::sfScriptMoveTo(SCRIPTFUNC_PARAMS) { + int16 objectId; + Location location; + ActorData *actor; + ObjectData *obj; + + objectId = thread->pop(); + location.x = thread->pop(); + location.y = thread->pop(); + + if (_vm->_actor->validActorId(objectId)) { + actor = _vm->_actor->getActor(objectId); + + actor->_location.x = location.x; + actor->_location.y = location.y; + } else { + if (_vm->_actor->validObjId(objectId)) { + obj = _vm->_actor->getObj(objectId); + obj->_location.x = location.x; + obj->_location.y = location.y; + } + } +} + +// Script function #31 (0x21) +// Param1: sceneNumber +void Script::sfSceneEq(SCRIPTFUNC_PARAMS) { + int16 sceneNumber = thread->pop(); + + if (_vm->_scene->getSceneResourceId(sceneNumber) == _vm->_scene->currentSceneResourceId()) + thread->_returnValue = 1; + else + thread->_returnValue = 0; +} + +// Script function #32 (0x20) +void Script::sfDropObject(SCRIPTFUNC_PARAMS) { + uint16 objectId; + uint16 spriteId; + int16 x; + int16 y; + ObjectData *obj; + + objectId = thread->pop(); + spriteId = thread->pop(); + x = thread->pop(); + y = thread->pop(); + + obj = _vm->_actor->getObj(objectId); + + if (obj->_sceneNumber == ITE_SCENE_INV) { + _vm->_interface->removeFromInventory(objectId); + } + + obj->_sceneNumber = _vm->_scene->currentSceneNumber(); + + if (_vm->getGameType() == GType_IHNM) + obj->_spriteListResourceId = spriteId; + else + obj->_spriteListResourceId = OBJ_SPRITE_BASE + spriteId; + + obj->_location.x = x; + obj->_location.y = y; +} + +// Script function #33 (0x21) +void Script::sfFinishBgdAnim(SCRIPTFUNC_PARAMS) { + int16 animId = thread->pop(); + + _vm->_anim->finish(animId); + + debug(1, "sfFinishBgdAnim(%d)", animId); +} + +// Script function #34 (0x22) +// Param1: actor id 1 +// Param2: actor id 2 +void Script::sfSwapActors(SCRIPTFUNC_PARAMS) { + int16 actorId1; + int16 actorId2; + ActorData *actor1; + ActorData *actor2; + + actorId1 = thread->pop(); + actorId2 = thread->pop(); + + actor1 = _vm->_actor->getActor(actorId1); + actor2 = _vm->_actor->getActor(actorId2); + + SWAP(actor1->_location, actor2->_location); + + if (actor1->_flags & kProtagonist) { + actor1->_flags &= ~kProtagonist; + actor2->_flags |= kProtagonist; + _vm->_actor->_protagonist = _vm->_actor->_centerActor = actor2; + } else if (actor2->_flags & kProtagonist) { + actor2->_flags &= ~kProtagonist; + actor1->_flags |= kProtagonist; + _vm->_actor->_protagonist = _vm->_actor->_centerActor = actor1; + } + + // Here non-protagonist ID gets saved in variable + if (_vm->getGameType() == GType_IHNM) + warning("sfSwapActors: incomplete implementation"); +} + +// Script function #35 (0x23) +// Param1: string rid +// Param2: actorscount +// Param3: actor id1 +///.... +// Param3: actor idN +void Script::sfSimulSpeech(SCRIPTFUNC_PARAMS) { + int16 stringId; + int16 actorsCount; + int i; + uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX]; + const char *string; + int16 sampleResourceId = -1; + + stringId = thread->pop(); + actorsCount = thread->pop(); + + if (actorsCount > ACTOR_SPEECH_ACTORS_MAX) + error("sfSimulSpeech actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount); + + for (i = 0; i < actorsCount; i++) + actorsIds[i] = thread->pop(); + + string = thread->_strings->getString(stringId); + + if (thread->_voiceLUT->voices) { + if (_vm->getGameType() == GType_IHNM && stringId >= 338) { + sampleResourceId = -1; + } else { + sampleResourceId = thread->_voiceLUT->voices[stringId]; + if (sampleResourceId <= 0 || sampleResourceId > 4000) + sampleResourceId = -1; + } + } + + _vm->_actor->simulSpeech(string, actorsIds, actorsCount, 0, sampleResourceId); + thread->wait(kWaitTypeSpeech); +} + +// Script function #36 (0x24) ? +// Param1: actor id +// Param2: actor x +// Param3: actor y +// Param4: actor walk flag +void Script::sfScriptWalk(SCRIPTFUNC_PARAMS) { + int16 actorId; + Location actorLocation; + ActorData *actor; + uint16 walkFlags; + + actorId = thread->pop(); + actorLocation.x = thread->pop(); + actorLocation.y = thread->pop(); + walkFlags = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + actorLocation.z = actor->_location.z; + + _vm->_actor->realLocation(actorLocation, ID_NOTHING, walkFlags); + + actor->_flags &= ~kFollower; + + if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) { + thread->waitWalk(actor); + } + + if (walkFlags & kWalkBackPedal) { + actor->_actorFlags |= kActorBackwards; + } + + actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask); +} + +// Script function #37 (0x25) nonblocking +// Param1: actor id +// Param2: flags telling how to cycle the frames +// Param3: cycle frame number +// Param4: cycle delay +void Script::sfCycleFrames(SCRIPTFUNC_PARAMS) { + int16 actorId; + int16 flags; + int cycleFrameSequence; + int cycleDelay; + ActorData *actor; + + actorId = thread->pop(); + flags = thread->pop(); + cycleFrameSequence = thread->pop(); + cycleDelay = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + + if (flags & kCyclePong) { + actor->_currentAction = kActionPongFrames; + } else { + actor->_currentAction = kActionCycleFrames; + } + + actor->_actorFlags &= ~(kActorContinuous | kActorRandom | kActorBackwards); + + if (!(flags & kCycleOnce)) { + actor->_actorFlags |= kActorContinuous; + } + if (flags & kCycleRandom) { + actor->_actorFlags |= kActorRandom; + } + if (flags & kCycleReverse) { + actor->_actorFlags |= kActorBackwards; + } + + actor->_cycleFrameSequence = cycleFrameSequence; + actor->_cycleTimeCount = 0; + actor->_cycleDelay = cycleDelay; + actor->_actionCycle = 0; +} + +// Script function #38 (0x26) nonblocking +// Param1: actor id +// Param2: frame type +// Param3: frame offset +void Script::sfSetFrame(SCRIPTFUNC_PARAMS) { + int16 actorId; + int frameType; + int frameOffset; + ActorData *actor; + ActorFrameRange *frameRange; + + actorId = thread->pop(); + frameType = thread->pop(); + frameOffset = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + + frameRange = _vm->_actor->getActorFrameRange(actorId, frameType); + + actor->_frameNumber = frameRange->frameIndex + frameOffset; + + if (actor->_currentAction != kActionFall) { + actor->_currentAction = kActionFreeze; + } +} + +// Script function #39 (0x27) +// Sets the right-hand portrait +void Script::sfSetPortrait(SCRIPTFUNC_PARAMS) { + int16 param = thread->pop(); + + _vm->_interface->setRightPortrait(param); +} + +// Script function #40 (0x28) +// Sets the left-hand portrait +void Script::sfSetProtagPortrait(SCRIPTFUNC_PARAMS) { + int16 param = thread->pop(); + + _vm->_interface->setLeftPortrait(param); +} + +// Script function #41 (0x29) nonblocking +// Links the specified animations for playback + +// Param1: ? +// Param2: total linked frame count +// Param3: animation id link target +// Param4: animation id link source +void Script::sfChainBgdAnim(SCRIPTFUNC_PARAMS) { + int16 animId1 = thread->pop(); + int16 animId = thread->pop(); + int16 cycles = thread->pop(); + int16 speed = thread->pop(); + + if (speed >= 0) { + _vm->_anim->setCycles(animId, cycles); + _vm->_anim->stop(animId); + _vm->_anim->setFrameTime(animId, ticksToMSec(speed)); + } + + _vm->_anim->link(animId1, animId); + debug(1, "sfChainBgdAnim(%d, %d, %d, %d)", animId1, animId, cycles, speed); +} + +// Script function #42 (0x2A) +// Param1: actor id +// Param2: actor x +// Param3: actor y +// Param4: frame seq +void Script::sfScriptSpecialWalk(SCRIPTFUNC_PARAMS) { + int16 actorId; + int16 walkFrameSequence; + Location actorLocation; + ActorData *actor; + + actorId = thread->pop(); + actorLocation.x = thread->pop(); + actorLocation.y = thread->pop(); + walkFrameSequence = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + actorLocation.z = actor->_location.z; + + _vm->_actor->actorWalkTo(actorId, actorLocation); + + actor->_walkFrameSequence = walkFrameSequence; +} + +// Script function #43 (0x2B) nonblocking +// Param1: actor id +// Param2: actor x +// Param3: actor y +// Param4: actor direction +// Param5: actor action +// Param6: actor frame number +void Script::sfPlaceActor(SCRIPTFUNC_PARAMS) { + int16 actorId; + Location actorLocation; + int actorDirection; + int frameType; + int frameOffset; + ActorData *actor; + ActorFrameRange *frameRange; + + actorId = thread->pop(); + actorLocation.x = thread->pop(); + actorLocation.y = thread->pop(); + actorDirection = thread->pop(); + frameType = thread->pop(); + frameOffset = thread->pop(); + + debug(1, "sfPlaceActor(id = 0x%x, x=%d, y=%d, dir=%d, frameType=%d, frameOffset=%d)", actorId, actorLocation.x, + actorLocation.y, actorDirection, frameType, frameOffset); + + actor = _vm->_actor->getActor(actorId); + actor->_location.x = actorLocation.x; + actor->_location.y = actorLocation.y; + actor->_facingDirection = actor->_actionDirection = actorDirection; + + if (!actor->_frames) + _vm->_actor->loadActorResources(actor); //? is not it already loaded ? + + if (frameType >= 0) { + frameRange = _vm->_actor->getActorFrameRange(actorId, frameType); + + if (frameRange->frameCount <= frameOffset) { + error("Wrong frameOffset 0x%X", frameOffset); + } + + actor->_frameNumber = frameRange->frameIndex + frameOffset; + actor->_currentAction = kActionFreeze; + } else { + actor->_currentAction = kActionWait; + } + + actor->_targetObject = ID_NOTHING; +} + +// Script function #44 (0x2C) nonblocking +// Checks to see if the user has interrupted a currently playing +// game cinematic. Pushes a zero or positive value if the game +// has not been interrupted. +void Script::sfCheckUserInterrupt(SCRIPTFUNC_PARAMS) { + thread->_returnValue = (_skipSpeeches == true); +} + +// Script function #45 (0x2D) +// Param1: actor id +// Param2: object id +// Param3: actor x +// Param4: actor y +// Param5: actor walk flag +void Script::sfScriptWalkRelative(SCRIPTFUNC_PARAMS) { + int16 actorId; + int16 objectId; + uint16 walkFlags; + Location actorLocation; + ActorData *actor; + + actorId = thread->pop(); + objectId = thread->pop(); + actorLocation.x = thread->pop(); + actorLocation.y = thread->pop(); + walkFlags = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + actorLocation.z = actor->_location.z; + + _vm->_actor->realLocation(actorLocation, objectId, walkFlags); + + actor->_flags &= ~kFollower; + + if (_vm->_actor->actorWalkTo(actorId, actorLocation) && !(walkFlags & kWalkAsync)) { + thread->waitWalk(actor); + } + + if (walkFlags & kWalkBackPedal) { + actor->_actorFlags |= kActorBackwards; + } + + actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask); +} + +// Script function #46 (0x2E) +// Param1: actor id +// Param2: object id +// Param3: actor x +// Param4: actor y +// Param5: actor walk flag +void Script::sfScriptMoveRelative(SCRIPTFUNC_PARAMS) { + int16 actorId; + int16 objectId; + uint16 walkFlags; + Location actorLocation; + ActorData *actor; + + actorId = thread->pop(); + objectId = thread->pop(); + actorLocation.x = thread->pop(); + actorLocation.y = thread->pop(); + walkFlags = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + actorLocation.z = actor->_location.z; + + _vm->_actor->realLocation(actorLocation, objectId, walkFlags); + + + actor->_location = actorLocation; + actor->_actorFlags = (actor->_actorFlags & ~kActorFacingMask) | (walkFlags & kActorFacingMask); +} + +// Script function #47 (0x2F) +void Script::sfSimulSpeech2(SCRIPTFUNC_PARAMS) { + int16 stringId; + int16 actorsCount; + int16 speechFlags; + int i; + uint16 actorsIds[ACTOR_SPEECH_ACTORS_MAX]; + const char *string; + int16 sampleResourceId = -1; + + stringId = thread->pop(); + actorsCount = thread->pop(); + speechFlags = thread->pop(); + + if (actorsCount > ACTOR_SPEECH_ACTORS_MAX) + error("sfSimulSpeech2 actorsCount=0x%X exceed ACTOR_SPEECH_ACTORS_MAX", actorsCount); + + for (i = 0; i < actorsCount; i++) + actorsIds[i] = thread->pop(); + + string = thread->_strings->getString(stringId); + + if (thread->_voiceLUT->voices) { + sampleResourceId = thread->_voiceLUT->voices[stringId]; + if (sampleResourceId <= 0 || sampleResourceId > 4000) + sampleResourceId = -1; + } + + _vm->_actor->simulSpeech(string, actorsIds, actorsCount, speechFlags, sampleResourceId); + thread->wait(kWaitTypeSpeech); +} + + +// Script function #48 (0x30) +// Param1: string rid +void Script::sfPlacard(SCRIPTFUNC_PARAMS) { + int stringId; + Surface *backBuffer = _vm->_gfx->getBackBuffer(); + static PalEntry cur_pal[PAL_ENTRIES]; + PalEntry *pal; + Event event; + Event *q_event; + + if (_vm->getGameType() == GType_IHNM) { + warning("Psychic profile is not implemented"); + return; + } + + thread->wait(kWaitTypePlacard); + + _vm->_interface->rememberMode(); + _vm->_interface->setMode(kPanelPlacard); + + stringId = thread->pop(); + + event.type = kEvTOneshot; + event.code = kCursorEvent; + event.op = kEventHide; + + q_event = _vm->_events->queue(&event); + + _vm->_gfx->getCurrentPal(cur_pal); + + event.type = kEvTImmediate; + event.code = kPalEvent; + event.op = kEventPalToBlack; + event.time = 0; + event.duration = kNormalFadeDuration; + event.data = cur_pal; + + q_event = _vm->_events->chain(q_event, &event); + + event.type = kEvTOneshot; + event.code = kInterfaceEvent; + event.op = kEventClearStatus; + + q_event = _vm->_events->chain(q_event, &event); + + event.type = kEvTOneshot; + event.code = kGraphicsEvent; + event.op = kEventSetFlag; + event.param = RF_PLACARD; + + q_event = _vm->_events->chain(q_event, &event); + + event.type = kEvTOneshot; + event.code = kGraphicsEvent; + event.op = kEventFillRect; + event.data = backBuffer; + event.param = 138; + event.param2 = 0; + event.param3 = _vm->_scene->getHeight(); + event.param4 = 0; + event.param5 = _vm->getDisplayWidth(); + + q_event = _vm->_events->chain(q_event, &event); + + // Put the text in the center of the viewport, assuming it will fit on + // one line. If we cannot make that assumption we'll need to extend + // the text drawing function so that it can center text around a point. + // It doesn't end up in exactly the same spot as the original did it, + // but it's close enough for now at least. + + TextListEntry textEntry; + + textEntry.knownColor = kKnownColorBrightWhite; + textEntry.effectKnownColor = kKnownColorBlack; + textEntry.point.x = _vm->getDisplayWidth() / 2; + textEntry.point.y = (_vm->_scene->getHeight() - _vm->_font->getHeight(kKnownFontMedium)) / 2; + textEntry.font = kKnownFontMedium; + textEntry.flags = (FontEffectFlags)(kFontOutline | kFontCentered); + textEntry.text = thread->_strings->getString(stringId); + + _placardTextEntry = _vm->_scene->_textList.addEntry(textEntry); + + event.type = kEvTOneshot; + event.code = kTextEvent; + event.op = kEventDisplay; + event.data = _placardTextEntry; + + q_event = _vm->_events->chain(q_event, &event); + + _vm->_scene->getBGPal(pal); + + event.type = kEvTImmediate; + event.code = kPalEvent; + event.op = kEventBlackToPal; + event.time = 0; + event.duration = kNormalFadeDuration; + event.data = pal; + + q_event = _vm->_events->chain(q_event, &event); + + event.type = kEvTOneshot; + event.code = kScriptEvent; + event.op = kEventThreadWake; + event.param = kWaitTypePlacard; + + q_event = _vm->_events->chain(q_event, &event); + +} + +// Script function #49 (0x31) +void Script::sfPlacardOff(SCRIPTFUNC_PARAMS) { + static PalEntry cur_pal[PAL_ENTRIES]; + PalEntry *pal; + Event event; + Event *q_event; + + thread->wait(kWaitTypePlacard); + + _vm->_interface->restoreMode(); + + _vm->_gfx->getCurrentPal(cur_pal); + + event.type = kEvTImmediate; + event.code = kPalEvent; + event.op = kEventPalToBlack; + event.time = 0; + event.duration = kNormalFadeDuration; + event.data = cur_pal; + + q_event = _vm->_events->queue(&event); + + event.type = kEvTOneshot; + event.code = kGraphicsEvent; + event.op = kEventClearFlag; + event.param = RF_PLACARD; + + q_event = _vm->_events->chain(q_event, &event); + + event.type = kEvTOneshot; + event.code = kTextEvent; + event.op = kEventRemove; + event.data = _placardTextEntry; + + q_event = _vm->_events->chain(q_event, &event); + + _vm->_scene->getBGPal(pal); + + event.type = kEvTImmediate; + event.code = kPalEvent; + event.op = kEventBlackToPal; + event.time = 0; + event.duration = kNormalFadeDuration; + event.data = pal; + + q_event = _vm->_events->chain(q_event, &event); + + event.type = kEvTOneshot; + event.code = kCursorEvent; + event.op = kEventShow; + + q_event = _vm->_events->chain(q_event, &event); + + event.type = kEvTOneshot; + event.code = kScriptEvent; + event.op = kEventThreadWake; + event.param = kWaitTypePlacard; + + q_event = _vm->_events->chain(q_event, &event); + +} + +void Script::sfPsychicProfile(SCRIPTFUNC_PARAMS) { + SF_stub("sfPsychicProfile", thread, nArgs); +} + +void Script::sfPsychicProfileOff(SCRIPTFUNC_PARAMS) { + SF_stub("sfPsychicProfileOff", thread, nArgs); +} + +// Script function #50 (0x32) +void Script::sfSetProtagState(SCRIPTFUNC_PARAMS) { + int protagState = thread->pop(); + + _vm->_actor->setProtagState(protagState); +} + +// Script function #51 (0x33) +void Script::sfResumeBgdAnim(SCRIPTFUNC_PARAMS) { + int16 animId = thread->pop(); + int16 cycles = thread->pop(); + + _vm->_anim->resume(animId, cycles); + debug(1, "sfResumeBgdAnimSpeed(%d, %d)", animId, cycles); + +} + +// Script function #52 (0x34) +// Param1: actor id +// Param2: x +// Param3: y +// Param4: unknown +// Param5: actionCycle +// Param6: flags +void Script::sfThrowActor(SCRIPTFUNC_PARAMS) { + int16 actorId; + ActorData *actor; + int16 flags; + int32 actionCycle; + Location location; + + actorId = thread->pop(); + location.x = thread->pop(); + location.y = thread->pop(); + thread->pop(); + actionCycle = thread->pop(); + flags = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + location.z = actor->_location.z; + actor->_currentAction = kActionFall; + actor->_actionCycle = actionCycle; + actor->_fallAcceleration = -20; + actor->_fallVelocity = - (actor->_fallAcceleration * actor->_actionCycle) / 2; + actor->_fallPosition = actor->_location.z << 4; + + actor->_finalTarget = location; + actor->_actionCycle--; + if (!(flags & kWalkAsync)) { + thread->waitWalk(actor); + } +} + +// Script function #53 (0x35) +// Param1: actor id +// Param2: target object +void Script::sfWaitWalk(SCRIPTFUNC_PARAMS) { + int16 actorId; + ActorData *actor; + + actorId = thread->pop(); + actor = _vm->_actor->getActor(actorId); + + if ((actor->_currentAction == kActionWalkToPoint) || + (actor->_currentAction == kActionWalkToLink) || + (actor->_currentAction == kActionFall)) { + thread->waitWalk(actor); + } +} + +// Script function #54 (0x36) +void Script::sfScriptSceneID(SCRIPTFUNC_PARAMS) { + thread->_returnValue = _vm->_scene->currentSceneNumber(); +} + +// Script function #55 (0x37) +// Param1: actor id +// Param2: scene number +void Script::sfChangeActorScene(SCRIPTFUNC_PARAMS) { + int16 actorId; + int32 sceneNumber; + ActorData *actor; + + actorId = thread->pop(); + sceneNumber = thread->pop(); + actor = _vm->_actor->getActor(actorId); + actor->_sceneNumber = sceneNumber; +} + +// Script function #56 (0x38) +// Param1: actor id +// Param2: z +// Param3: frame seq +// Param4: flags +void Script::sfScriptClimb(SCRIPTFUNC_PARAMS) { + int16 actorId; + int16 z; + ActorData *actor; + uint16 flags; + int cycleFrameSequence; + + actorId = thread->pop(); + z = thread->pop(); + cycleFrameSequence = thread->pop(); + flags = thread->pop(); + + actor = _vm->_actor->getActor(actorId); + actor->_finalTarget.z = z; + actor->_flags &= ~kFollower; + actor->_actionCycle = 1; + actor->_cycleFrameSequence = cycleFrameSequence; + actor->_currentAction = kActionClimb; + if (!(flags & kWalkAsync)) { + thread->waitWalk(actor); + } +} + +// Script function #57 (0x39) +// Param1: door # +// Param2: door state +void Script::sfSetDoorState(SCRIPTFUNC_PARAMS) { + int16 doorNumber; + int16 doorState; + doorNumber = thread->pop(); + doorState = thread->pop(); + + if (_vm->_scene->getFlags() & kSceneFlagISO) { + _vm->_isoMap->setTileDoorState(doorNumber, doorState); + } else { + _vm->_scene->setDoorState(doorNumber, doorState); + } +} + +// Script function #58 (0x3A) +// Param1: actor id +// Param2: z +void Script::sfSetActorZ(SCRIPTFUNC_PARAMS) { + int16 objectId; + ActorData *actor; + ObjectData *obj; + int16 z; + + objectId = thread->pop(); + z = thread->pop(); + + + if (_vm->_actor->validActorId(objectId)) { + actor = _vm->_actor->getActor(objectId); + actor->_location.z = z; + } else { + if (_vm->_actor->validObjId(objectId)) { + obj = _vm->_actor->getObj(objectId); + obj->_location.z = z; + } + } +} + +// Script function #59 (0x3B) +// Param1: stringId +// Param2: flags +// Param3: color +// Param4: x +// Param5: y +void Script::sfScriptText(SCRIPTFUNC_PARAMS) { + int16 stringId; + int16 flags; + Rect rect; + int color; + Point point; + int width; + const char*text; + stringId = thread->pop(); + flags = thread->pop(); + color = thread->pop(); + point.x = thread->pop(); + point.y = thread->pop(); + + text = thread->_strings->getString(stringId); + + width = _vm->_font->getStringWidth(kKnownFontScript, text, 0, kFontOutline); + rect.top = point.y - 6; + rect.setHeight(12); + rect.left = point.x - width / 2; + rect.setWidth(width); + + _vm->_actor->setSpeechColor(color, _vm->KnownColor2ColorId(kKnownColorBlack)); + _vm->_actor->nonActorSpeech(rect, &text, 1, -1, flags); +} + +// Script function #60 (0x3C) +// Param1: actor id +void Script::sfGetActorX(SCRIPTFUNC_PARAMS) { + int16 actorId; + ActorData *actor; + + actorId = thread->pop(); + actor = _vm->_actor->getActor(actorId); + + thread->_returnValue = actor->_location.x >> 2; +} + +// Script function #61 (0x3D) +// Param1: actor id +void Script::sfGetActorY(SCRIPTFUNC_PARAMS) { + int16 actorId; + ActorData *actor; + + actorId = thread->pop(); + actor = _vm->_actor->getActor(actorId); + + thread->_returnValue = actor->_location.y >> 2; +} + +// Script function #62 (0x3E) +void Script::sfEraseDelta(SCRIPTFUNC_PARAMS) { + Surface *backGroundSurface; + BGInfo backGroundInfo; + + backGroundSurface = _vm->_render->getBackGroundSurface(); + _vm->_scene->getBGInfo(backGroundInfo); + + backGroundSurface->blit(backGroundInfo.bounds, backGroundInfo.buffer); +} + +// Script function #63 (0x3F) +void Script::sfPlayMusic(SCRIPTFUNC_PARAMS) { + if (_vm->getGameType() == GType_ITE) { + int16 param = thread->pop() + 9; + + if (param >= 9 && param <= 34) { + _vm->_music->setVolume(-1, 1); + _vm->_music->play(param); + } else { + _vm->_music->stop(); + } + } else { + int16 param1 = thread->pop(); + int16 param2 = thread->pop(); + + if (param1 < 0) { + _vm->_music->stop(); + return; + } + + if (param1 >= _vm->_music->_songTableLen) { + warning("sfPlayMusic: Wrong song number (%d > %d)", param1, _vm->_music->_songTableLen - 1); + } else { + _vm->_music->setVolume(-1, 1); + _vm->_music->play(_vm->_music->_songTable[param1], param2 ? MUSIC_LOOP : MUSIC_NORMAL); + } + } +} + +// Script function #64 (0x40) +void Script::sfPickClimbOutPos(SCRIPTFUNC_PARAMS) { + int16 u, v, t; + ActorData *protagonist = _vm->_actor->_protagonist; + while (true) { + + u = (_vm->_rnd.getRandomNumber(63) & 63) + 40; + v = (_vm->_rnd.getRandomNumber(63) & 63) + 40; + t = _vm->_isoMap->getTileIndex(u, v, 6); + if (t == 65) { + protagonist->_location.u() = (u << 4) + 4; + protagonist->_location.v() = (v << 4) + 4; + protagonist->_location.z = 48; + break; + } + + } +} + +// Script function #65 (0x41) +void Script::sfTossRif(SCRIPTFUNC_PARAMS) { + int16 uc , vc; + uint16 direction; + ActorData *protagonist = _vm->_actor->_protagonist; + + uc = protagonist->_location.u() >> 4; + vc = protagonist->_location.v() >> 4; + if (_vm->_isoMap->findNearestChasm(uc, vc, direction)) { + uc <<= 4; + vc <<= 4; + protagonist->_facingDirection = direction; + + protagonist->_finalTarget.u() = uc; + protagonist->_finalTarget.v() = vc; + protagonist->_finalTarget.z = -40; + protagonist->_currentAction = kActionFall; + protagonist->_actionCycle = 24; + protagonist->_fallAcceleration = - 20; + protagonist->_fallVelocity = - (protagonist->_fallAcceleration * 16) / 2 - (44 / 12); + protagonist->_fallPosition = protagonist->_location.z << 4; + protagonist->_actionCycle--; + } +} + +// Script function #66 (0x42) +void Script::sfShowControls(SCRIPTFUNC_PARAMS) { + // It has zero implementation in Win rerelase, and in DOS + // release it deals with video ports. +} + +// Script function #67 (0x43) +void Script::sfShowMap(SCRIPTFUNC_PARAMS) { + _vm->_interface->setMode(kPanelMap); +} + +// Script function #68 (0x44) +void Script::sfPuzzleWon(SCRIPTFUNC_PARAMS) { + thread->_returnValue = _vm->_puzzle->isSolved(); +} + +// Script function #69 (0x45) +void Script::sfEnableEscape(SCRIPTFUNC_PARAMS) { + if (thread->pop()) + _abortEnabled = true; + else { + _skipSpeeches = false; + _abortEnabled = false; + } +} + +// Script function #70 (0x46) +void Script::sfPlaySound(SCRIPTFUNC_PARAMS) { + int16 param = thread->pop(); + int res; + + if (param >= 0 && param < _vm->_sndRes->_fxTableLen) { + res = _vm->_sndRes->_fxTable[param].res; + if (_vm->getFeatures() & GF_CD_FX) + res -= 14; + _vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, false); + } else { + _vm->_sound->stopSound(); + } +} + +// Script function #71 (0x47) +void Script::sfPlayLoopedSound(SCRIPTFUNC_PARAMS) { + int16 param = thread->pop(); + int res; + + if (param >= 0 && param < _vm->_sndRes->_fxTableLen) { + res = _vm->_sndRes->_fxTable[param].res; + if (_vm->getFeatures() & GF_CD_FX) + res -= 14; + + _vm->_sndRes->playSound(res, _vm->_sndRes->_fxTable[param].vol, true); + } else { + _vm->_sound->stopSound(); + } +} + +// Script function #72 (0x48) +void Script::sfGetDeltaFrame(SCRIPTFUNC_PARAMS) { + uint16 animId = (uint16)thread->pop(); + + thread->_returnValue = _vm->_anim->getCurrentFrame(animId); +} + +// Script function #73 (0x49) +void Script::sfShowProtect(SCRIPTFUNC_PARAMS) { + if (_vm->_copyProtection) { + thread->wait(kWaitTypeRequest); + + _vm->_interface->setMode(kPanelProtect); + } +} + +// Script function #74 (0x4A) +void Script::sfProtectResult(SCRIPTFUNC_PARAMS) { + if (_vm->_copyProtection) { + thread->_returnValue = _vm->_interface->getProtectHash(); + } else { + int protectHash; + + //cheating + protectHash = thread->pop(); + thread->push(protectHash); + thread->_returnValue = protectHash; + } +} + +// Script function #75 (0x4b) +void Script::sfRand(SCRIPTFUNC_PARAMS) { + int16 param; + + param = thread->pop(); + thread->_returnValue = _vm->_rnd.getRandomNumber(param - 1); +} + +// Script function #76 (0x4c) +void Script::sfFadeMusic(SCRIPTFUNC_PARAMS) { + _vm->_music->setVolume(0, 1000); +} + +// Script function #77 (0x4d) +void Script::sfPlayVoice(SCRIPTFUNC_PARAMS) { + int16 param = thread->pop(); + + warning("sfPlayVoice(%d)", param); + if (param > 0) { + _vm->_sndRes->playVoice(param + 3712); + } else { + _vm->_sound->stopSound(); + } +} + +void Script::finishDialog(int replyID, int flags, int bitOffset) { + byte *addr; + + if (_conversingThread) { + _vm->_interface->setMode(kPanelNull); + + _conversingThread->_flags &= ~kTFlagWaiting; + + _conversingThread->push(replyID); + + if (flags & kReplyOnce) { + addr = _conversingThread->_staticBase + (bitOffset >> 3); + *addr |= (1 << (bitOffset & 7)); + } + } + + _conversingThread = NULL; + wakeUpThreads(kWaitTypeDialogBegin); +} + +void Script::sfSetChapterPoints(SCRIPTFUNC_PARAMS) { + int16 ethics = thread->pop(); + int16 barometer = thread->pop(); + int chapter = _vm->_scene->currentChapterNumber(); + + _vm->_ethicsPoints[chapter] = ethics; + _vm->_spiritualBarometer = ethics * 256 / barometer; +} + +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; + int16 fade; + + cut = thread->pop(); + thread->pop(); // Not used + fade = thread->pop(); + + _vm->_anim->playCutaway(cut, fade != 0); +} + +void Script::sfReturnFromCutAway(SCRIPTFUNC_PARAMS) { + _vm->_anim->returnFromCutaway(); +} + +void Script::sfEndCutAway(SCRIPTFUNC_PARAMS) { + _vm->_anim->endCutaway(); +} + +void Script::sfGetMouseClicks(SCRIPTFUNC_PARAMS) { + SF_stub("sfGetMouseClicks", thread, nArgs); +} + +void Script::sfResetMouseClicks(SCRIPTFUNC_PARAMS) { + SF_stub("sfResetMouseClicks", thread, nArgs); +} + +void Script::sfWaitFrames(SCRIPTFUNC_PARAMS) { + SF_stub("sfWaitFrames", thread, nArgs); +} + +void Script::sfScriptFade(SCRIPTFUNC_PARAMS) { + SF_stub("sfScriptFade", thread, nArgs); +} + +void Script::sfScriptStartVideo(SCRIPTFUNC_PARAMS) { + SF_stub("sfScriptStartVideo", thread, nArgs); +} + +void Script::sfScriptReturnFromVideo(SCRIPTFUNC_PARAMS) { + SF_stub("sfScriptReturnFromVideo", thread, nArgs); +} + +void Script::sfScriptEndVideo(SCRIPTFUNC_PARAMS) { + SF_stub("sfScriptEndVideo", thread, nArgs); +} + +void Script::sf87(SCRIPTFUNC_PARAMS) { + SF_stub("sf87", thread, nArgs); +} + +void Script::sf88(SCRIPTFUNC_PARAMS) { + SF_stub("sf88", thread, nArgs); +} + +void Script::sf89(SCRIPTFUNC_PARAMS) { + SF_stub("sf89", thread, nArgs); +} + +void Script::sfVstopFX(SCRIPTFUNC_PARAMS) { + _vm->_sound->stopSound(); +} + +void Script::sfVstopLoopedFX(SCRIPTFUNC_PARAMS) { + _vm->_sound->stopSound(); +} + +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); + _vm->_actor->_speechBoxScript.setHeight(param4); +} + +void Script::sfDebugShowData(SCRIPTFUNC_PARAMS) { + int16 param = thread->pop(); + char buf[50]; + + snprintf(buf, 50, "Reached breakpoint %d", param); + + _vm->_interface->setStatusText(buf); +} + +void Script::sfWaitFramesEsc(SCRIPTFUNC_PARAMS) { + thread->_returnValue = _vm->_framesEsc; +} + +void Script::sf103(SCRIPTFUNC_PARAMS) { + SF_stub("sf103", thread, nArgs); +} + +void Script::sfDisableAbortSpeeches(SCRIPTFUNC_PARAMS) { + int value = thread->pop(); + + _vm->_interface->disableAbortSpeeches(value != 0); +} + +void Script::sfNull(SCRIPTFUNC_PARAMS) { + for (int i = 0; i < nArgs; i++) + thread->pop(); +} + +void Script::SF_stub(const char *name, ScriptThread *thread, int nArgs) { + char buf[256], buf1[100]; + + snprintf(buf, 256, "STUB: %s(", name); + + for (int i = 0; i < nArgs; i++) { + snprintf(buf1, 100, "%d", thread->pop()); + strncat(buf, buf1, 256); + if (i + 1 < nArgs) + strncat(buf, ", ", 256); + } + + debug(0, "%s)", buf); +} + +} // End of namespace Saga diff --git a/engines/saga/sndres.cpp b/engines/saga/sndres.cpp new file mode 100644 index 0000000000..d3679599fd --- /dev/null +++ b/engines/saga/sndres.cpp @@ -0,0 +1,297 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Sound resource management class + +#include "saga/saga.h" + +#include "saga/itedata.h" +#include "saga/resnames.h" +#include "saga/rscfile.h" +#include "saga/sndres.h" +#include "saga/sound.h" +#include "saga/stream.h" + +#include "common/file.h" + +#include "sound/voc.h" +#include "sound/wave.h" +#include "sound/adpcm.h" +#include "sound/audiostream.h" + +namespace Saga { + +SndRes::SndRes(SagaEngine *vm) : _vm(vm) { + /* Load sound module resource file contexts */ + _sfxContext = _vm->_resource->getContext(GAME_SOUNDFILE); + if (_sfxContext == NULL) { + error("SndRes::SndRes resource context not found"); + } + + _voiceSerial = -1; + + setVoiceBank(0); + + if (_vm->getGameType() == GType_ITE) { + _fxTable = ITE_SfxTable; + _fxTableLen = ITE_SFXCOUNT; + } else { + ResourceContext *resourceContext; + + resourceContext = _vm->_resource->getContext(GAME_SOUNDFILE); + if (resourceContext == NULL) { + error("Resource::loadGlobalResources() resource context not found"); + } + + byte *resourcePointer; + size_t resourceLength; + + _vm->_resource->loadResource(resourceContext, RID_IHNM_SFX_LUT, + resourcePointer, resourceLength); + + if (resourceLength == 0) { + error("Sndres::SndRes can't read SfxIDs table"); + } + + _fxTableIDsLen = resourceLength / 2; + _fxTableIDs = (int16 *)malloc(_fxTableIDsLen * sizeof(int16)); + + MemoryReadStream metaS(resourcePointer, resourceLength); + for (int i = 0; i < _fxTableIDsLen; i++) + _fxTableIDs[i] = metaS.readSint16LE(); + + free(resourcePointer); + + _fxTable = 0; + _fxTableLen = 0; + } +} + +SndRes::~SndRes() { + if (_vm->getGameType() == GType_IHNM) { + free(_fxTable); + free(_fxTableIDs); + } +} + +void SndRes::setVoiceBank(int serial) +{ + if (_voiceSerial == serial) return; + + _voiceSerial = serial; + + _voiceContext = _vm->_resource->getContext(GAME_VOICEFILE, _voiceSerial); + if (_voiceContext == NULL) { + error("SndRes::SndRes resource context not found"); + } + + +} + +void SndRes::playSound(uint32 resourceId, int volume, bool loop) { + SoundBuffer buffer; + + debug(4, "SndRes::playSound %i", resourceId); + + if (!load(_sfxContext, resourceId, buffer, false)) { + warning("Failed to load sound"); + return; + } + + _vm->_sound->playSound(buffer, volume, loop); +} + +void SndRes::playVoice(uint32 resourceId) { + SoundBuffer buffer; + + debug(4, "SndRes::playVoice %i", resourceId); + + if (!load(_voiceContext, resourceId, buffer, false)) { + warning("Failed to load voice"); + return; + } + + _vm->_sound->playVoice(buffer); +} + +bool SndRes::load(ResourceContext *context, uint32 resourceId, SoundBuffer &buffer, bool onlyHeader) { + byte *soundResource; + AudioStream *voxStream; + size_t soundResourceLength; + bool result = false; + GameSoundTypes resourceType; + byte *data; + int rate; + int size; + byte flags; + size_t voxSize; + const GameSoundInfo *soundInfo; + + if (resourceId == (uint32)-1) { + return false; + } + + + _vm->_resource->loadResource(context, resourceId, soundResource, soundResourceLength); + + if ((context->fileType & GAME_VOICEFILE) != 0) { + soundInfo = _vm->getVoiceInfo(); + } else { + soundInfo = _vm->getSfxInfo(); + } + + context->table[resourceId].fillSoundPatch(soundInfo); + + MemoryReadStream readS(soundResource, soundResourceLength); + + resourceType = soundInfo->resourceType; + buffer.isBigEndian = soundInfo->isBigEndian; + + if (soundResourceLength >= 8) { + if (!memcmp(soundResource, "Creative", 8)) { + resourceType = kSoundVOC; + } else if (!memcmp(soundResource, "RIFF", 4) != 0) { + resourceType = kSoundWAV; + } + } + + + switch (resourceType) { + case kSoundPCM: + buffer.frequency = soundInfo->frequency; + buffer.isSigned = soundInfo->isSigned; + buffer.sampleBits = soundInfo->sampleBits; + buffer.size = soundResourceLength; + buffer.stereo = soundInfo->stereo; + if (onlyHeader) { + buffer.buffer = NULL; + free(soundResource); + } else { + buffer.buffer = soundResource; + } + result = true; + break; + case kSoundMacPCM: + buffer.frequency = soundInfo->frequency; + buffer.isSigned = soundInfo->isSigned; + buffer.sampleBits = soundInfo->sampleBits; + buffer.size = soundResourceLength - 36; + buffer.stereo = soundInfo->stereo; + if (onlyHeader) { + buffer.buffer = NULL; + } else { + buffer.buffer = (byte *)malloc(buffer.size); + memcpy(buffer.buffer, soundResource + 36, buffer.size); + } + free(soundResource); + result = true; + break; + case kSoundVOX: + buffer.frequency = soundInfo->frequency; + buffer.isSigned = soundInfo->isSigned; + buffer.sampleBits = soundInfo->sampleBits; + buffer.stereo = soundInfo->stereo; + buffer.size = soundResourceLength * 4; + if (onlyHeader) { + buffer.buffer = NULL; + free(soundResource); + } else { + voxStream = new ADPCMInputStream(&readS, soundResourceLength, kADPCMOki); + buffer.buffer = (byte *)malloc(buffer.size); + voxSize = voxStream->readBuffer((int16*)buffer.buffer, soundResourceLength * 2); + if (voxSize != soundResourceLength * 2) { + error("SndRes::load() wrong VOX output size"); + } + delete voxStream; + } + result = true; + break; + case kSoundVOC: + data = loadVOCFromStream(readS, size, rate); + if (data) { + buffer.frequency = rate; + buffer.sampleBits = 8; + buffer.stereo = false; + buffer.isSigned = false; + buffer.size = size; + if (onlyHeader) { + buffer.buffer = NULL; + free(data); + } else { + buffer.buffer = data; + } + result = true; + } + free(soundResource); + break; + case kSoundWAV: + if (loadWAVFromStream(readS, size, rate, flags)) { + buffer.frequency = rate; + buffer.sampleBits = 16; + buffer.stereo = ((flags & Audio::Mixer::FLAG_STEREO) != 0); + buffer.isSigned = true; + buffer.size = size; + if (onlyHeader) { + buffer.buffer = NULL; + } else { + buffer.buffer = (byte *)malloc(size); + readS.read(buffer.buffer, size); + } + result = true; + } + free(soundResource); + break; + default: + error("SndRes::load Unknown sound type"); + } + + // In ITE CD De some voices are absent and contain just 5 bytes header + // Round it to even number so soundmanager will not crash. + // See bug #1256701 + buffer.size &= ~(0x1); + + return result; +} + +int SndRes::getVoiceLength(uint32 resourceId) { + double msDouble; + SoundBuffer buffer; + + if (!load(_voiceContext, resourceId, buffer, true)) { + return -1; + } + + msDouble = (double)buffer.size; + if (buffer.sampleBits == 16) { + msDouble /= 2.0; + } + if (buffer.stereo) { + msDouble /= 2.0; + } + + msDouble = msDouble / buffer.frequency * 1000.0; + return (int)msDouble; +} + +} // End of namespace Saga diff --git a/engines/saga/sndres.h b/engines/saga/sndres.h new file mode 100644 index 0000000000..638fb80849 --- /dev/null +++ b/engines/saga/sndres.h @@ -0,0 +1,68 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Sound resource class header + +#ifndef SAGA_SNDRES_H_ +#define SAGA_SNDRES_H_ + +#include "saga/itedata.h" +#include "saga/sound.h" + +namespace Saga { + +class SndRes { +public: + + SndRes(SagaEngine *vm); + ~SndRes(); + + int loadSound(uint32 resourceId); + void playSound(uint32 resourceId, int volume, bool loop); + void playVoice(uint32 resourceId); + int getVoiceLength(uint32 resourceId); + void setVoiceBank(int serial); + + FxTable *_fxTable; + int _fxTableLen; + + int16 *_fxTableIDs; + int _fxTableIDsLen; + + private: + bool load(ResourceContext *context, uint32 resourceId, SoundBuffer &buffer, bool onlyHeader); + bool loadVocSound(byte *soundResource, size_t soundResourceLength, SoundBuffer &buffer); + bool loadWavSound(byte *soundResource, size_t soundResourceLength, SoundBuffer &buffer); + + ResourceContext *_sfxContext; + ResourceContext *_voiceContext; + + int _voiceSerial; // voice bank number + + SagaEngine *_vm; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/sound.cpp b/engines/saga/sound.cpp new file mode 100644 index 0000000000..37f9cf174f --- /dev/null +++ b/engines/saga/sound.cpp @@ -0,0 +1,149 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ +#include "saga/saga.h" + +#include "saga/sound.h" + +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "sound/adpcm.h" + +namespace Saga { + +Sound::Sound(SagaEngine *vm, Audio::Mixer *mixer, int volume) : + _vm(vm), _mixer(mixer), _voxStream(0) { + + for (int i = 0; i < SOUND_HANDLES; i++) + _handles[i].type = kFreeHandle; + + setVolume(volume == 10 ? 255 : volume * 25); +} + +Sound::~Sound() { + delete _voxStream; +} + +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 NULL; +} + +void Sound::playSoundBuffer(Audio::SoundHandle *handle, SoundBuffer &buffer, int volume, bool loop) { + byte flags; + + flags = Audio::Mixer::FLAG_AUTOFREE; + + if (loop) + flags |= Audio::Mixer::FLAG_LOOP; + + if (buffer.sampleBits == 16) { + flags |= Audio::Mixer::FLAG_16BITS; + + if (!buffer.isBigEndian) + flags |= Audio::Mixer::FLAG_LITTLE_ENDIAN; + } + if (buffer.stereo) + flags |= Audio::Mixer::FLAG_STEREO; + if (!buffer.isSigned) + flags |= Audio::Mixer::FLAG_UNSIGNED; + + _mixer->playRaw(handle, buffer.buffer, buffer.size, buffer.frequency, flags, -1, volume); +} + +void Sound::playSound(SoundBuffer &buffer, int volume, bool loop) { + SndHandle *handle = getHandle(); + + handle->type = kEffectHandle; + playSoundBuffer(&handle->handle, buffer, 2 * volume, 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; + } +} + +void Sound::playVoice(SoundBuffer &buffer) { + SndHandle *handle = getHandle(); + + handle->type = kVoiceHandle; + playSoundBuffer(&handle->handle, buffer, 255, 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(int volume) { + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume); + _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume); +} + +} // End of namespace Saga diff --git a/engines/saga/sound.h b/engines/saga/sound.h new file mode 100644 index 0000000000..85c3eda748 --- /dev/null +++ b/engines/saga/sound.h @@ -0,0 +1,97 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Sound class + +#ifndef SAGA_SOUND_H_ +#define SAGA_SOUND_H_ + +#include "sound/mixer.h" + +namespace Saga { + +#define SOUND_HANDLES 10 + +enum SOUND_FLAGS { + SOUND_LOOP = 1 +}; + +struct SoundBuffer { + uint16 frequency; + int sampleBits; + bool stereo; + bool isSigned; + + byte *buffer; + size_t size; + bool isBigEndian; +}; + +enum sndHandleType { + kFreeHandle, + kEffectHandle, + kVoiceHandle +}; + +struct SndHandle { + Audio::SoundHandle handle; + sndHandleType type; +}; + +class Sound { +public: + + Sound(SagaEngine *vm, Audio::Mixer *mixer, int volume); + ~Sound(); + + void playSound(SoundBuffer &buffer, int volume, bool loop); + void pauseSound(); + void resumeSound(); + void stopSound(); + + void playVoice(SoundBuffer &buffer); + void pauseVoice(); + void resumeVoice(); + void stopVoice(); + + void stopAll(); + + void setVolume(int volume); + + private: + + void playSoundBuffer(Audio::SoundHandle *handle, SoundBuffer &buffer, int volume, bool loop); + + SndHandle *getHandle(); + + SagaEngine *_vm; + Audio::Mixer *_mixer; + Common::MemoryReadStream *_voxStream; + + SndHandle _handles[SOUND_HANDLES]; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/sprite.cpp b/engines/saga/sprite.cpp new file mode 100644 index 0000000000..5309b1f109 --- /dev/null +++ b/engines/saga/sprite.cpp @@ -0,0 +1,444 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Sprite management module +#include "saga/saga.h" + +#include "saga/gfx.h" +#include "saga/scene.h" +#include "saga/resnames.h" +#include "saga/rscfile.h" +#include "saga/font.h" + +#include "saga/sprite.h" +#include "saga/stream.h" + +namespace Saga { + +Sprite::Sprite(SagaEngine *vm) : _vm(vm) { + debug(8, "Initializing sprite subsystem..."); + + // Load sprite module resource context + _spriteContext = _vm->_resource->getContext(GAME_RESOURCEFILE); + if (_spriteContext == NULL) { + error("Sprite::Sprite resource context not found"); + } + + _decodeBufLen = DECODE_BUF_LEN; + + _decodeBuf = (byte *)malloc(_decodeBufLen); + if (_decodeBuf == NULL) { + memoryError("Sprite::Sprite"); + } + + if (_vm->getGameType() == GType_ITE) { + loadList(_vm->getResourceDescription()->mainSpritesResourceId, _mainSprites); + _arrowSprites = _saveReminderSprites = _inventorySprites = _mainSprites; + } else { + loadList(RID_IHNM_ARROW_SPRITES, _arrowSprites); + loadList(RID_IHNM_SAVEREMINDER_SPRITES, _saveReminderSprites); + } +} + +Sprite::~Sprite(void) { + debug(8, "Shutting down sprite subsystem..."); + _mainSprites.freeMem(); + free(_decodeBuf); +} + +void Sprite::loadList(int resourceId, SpriteList &spriteList) { + SpriteInfo *spriteInfo; + byte *spriteListData; + size_t spriteListLength; + uint16 oldSpriteCount; + uint16 newSpriteCount; + uint16 spriteCount; + int i; + int outputLength, inputLength; + uint32 offset; + const byte *spritePointer; + const byte *spriteDataPointer; + + _vm->_resource->loadResource(_spriteContext, resourceId, spriteListData, spriteListLength); + + if (spriteListLength == 0) { + return; + } + + MemoryReadStreamEndian readS(spriteListData, spriteListLength, _spriteContext->isBigEndian); + + spriteCount = readS.readUint16(); + + debug(9, "Sprites: %d", spriteCount); + + oldSpriteCount = spriteList.spriteCount; + newSpriteCount = spriteList.spriteCount + spriteCount; + + spriteList.infoList = (SpriteInfo *)realloc(spriteList.infoList, newSpriteCount * sizeof(*spriteList.infoList)); + if (spriteList.infoList == NULL) { + memoryError("Sprite::loadList"); + } + + spriteList.spriteCount = newSpriteCount; + + bool bigHeader = _vm->getGameType() != GType_ITE || _vm->isMacResources(); + + for (i = oldSpriteCount; i < spriteList.spriteCount; i++) { + spriteInfo = &spriteList.infoList[i]; + if (bigHeader) + offset = readS.readUint32(); + else + offset = readS.readUint16(); + + if (offset >= spriteListLength) { + error("Sprite::loadList offset exceed"); + } + + spritePointer = spriteListData; + spritePointer += offset; + + if (bigHeader) { + 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 { + MemoryReadStreamEndian readS2(spritePointer, 4); + + spriteInfo->xAlign = readS2.readSByte(); + spriteInfo->yAlign = readS2.readSByte(); + + spriteInfo->width = readS2.readByte(); + spriteInfo->height = readS2.readByte(); + spriteDataPointer = spritePointer + readS2.pos(); + } + + outputLength = spriteInfo->width * spriteInfo->height; + inputLength = spriteListLength - (spriteDataPointer - spriteListData); + decodeRLEBuffer(spriteDataPointer, inputLength, outputLength); + spriteInfo->decodedBuffer = (byte *) malloc(outputLength); + if (spriteInfo->decodedBuffer == NULL) { + memoryError("Sprite::loadList"); + } + + // 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->getGameType() == GType_IHNM) { + byte *src = _decodeBuf + spriteInfo->width * (spriteInfo->height - 1); + byte *dst = spriteInfo->decodedBuffer; + + for (int j = 0; j < spriteInfo->height; j++) { + memcpy(dst, src, spriteInfo->width); + src -= spriteInfo->width; + dst += spriteInfo->width; + } + } else + memcpy(spriteInfo->decodedBuffer, _decodeBuf, outputLength); + } + + free(spriteListData); +} + +void Sprite::getScaledSpriteBuffer(SpriteList &spriteList, int spriteNumber, int scale, int &width, int &height, int &xAlign, int &yAlign, const byte *&buffer) { + SpriteInfo *spriteInfo; + assert(spriteList.spriteCount>spriteNumber); + spriteInfo = &spriteList.infoList[spriteNumber]; + + if (scale < 256) { + xAlign = (spriteInfo->xAlign * scale) >> 8; + yAlign = (spriteInfo->yAlign * scale) >> 8; + height = (spriteInfo->height * scale + 0x7f) >> 8; + width = (spriteInfo->width * scale + 0x7f) >> 8; + scaleBuffer(spriteInfo->decodedBuffer, spriteInfo->width, spriteInfo->height, scale); + buffer = _decodeBuf; + } else { + xAlign = spriteInfo->xAlign; + yAlign = spriteInfo->yAlign; + height = spriteInfo->height; + width = spriteInfo->width; + buffer = spriteInfo->decodedBuffer; + } +} + +void Sprite::drawClip(Surface *ds, const Rect &clipRect, const Point &spritePointer, int width, int height, const byte *spriteBuffer) { + int clipWidth; + int clipHeight; + + int i, j, jo, io; + byte *bufRowPointer; + const byte *srcRowPointer; + + bufRowPointer = (byte *)ds->pixels + ds->pitch * spritePointer.y; + srcRowPointer = spriteBuffer; + + clipWidth = width; + if (width > (clipRect.right - spritePointer.x)) { + clipWidth = (clipRect.right - spritePointer.x); + } + + clipHeight = height; + if (height > (clipRect.bottom - spritePointer.y)) { + clipHeight = (clipRect.bottom - spritePointer.y); + } + + jo = 0; + io = 0; + if (spritePointer.x < clipRect.left) { + jo = clipRect.left - spritePointer.x; + } + if (spritePointer.y < clipRect.top) { + io = clipRect.top - spritePointer.y; + bufRowPointer += ds->pitch * io; + srcRowPointer += width * io; + } + + for (i = io; i < clipHeight; i++) { + for (j = jo; j < clipWidth; j++) { + assert((byte *)ds->pixels <= (byte *)(bufRowPointer + j + spritePointer.x)); + assert(((byte *)ds->pixels + (_vm->getDisplayWidth() * + _vm->getDisplayHeight())) > (byte *)(bufRowPointer + j + spritePointer.x)); + assert((const byte *)spriteBuffer <= (const byte *)(srcRowPointer + j)); + assert(((const byte *)spriteBuffer + (width * height)) > (const byte *)(srcRowPointer + j)); + + if (*(srcRowPointer + j) != 0) { + *(bufRowPointer + j + spritePointer.x) = *(srcRowPointer + j); + } + } + bufRowPointer += ds->pitch; + srcRowPointer += width; + } +} + +void Sprite::draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Point &screenCoord, int scale) { + const byte *spriteBuffer; + int width; + int height; + int xAlign; + int yAlign; + Point spritePointer; + + getScaledSpriteBuffer(spriteList, spriteNumber, scale, width, height, xAlign, yAlign, spriteBuffer); + + spritePointer.x = screenCoord.x + xAlign; + spritePointer.y = screenCoord.y + yAlign; + + drawClip(ds, clipRect, spritePointer, width, height, spriteBuffer); +} + +void Sprite::draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Rect &screenRect, int scale) { + const byte *spriteBuffer; + int width; + int height; + int xAlign, spw; + int yAlign, 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(ds, clipRect, spritePointer, width, height, spriteBuffer); +} + +bool Sprite::hitTest(SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, const Point &testPoint) { + const byte *spriteBuffer; + int i, j; + const byte *srcRowPointer; + int width; + int height; + int xAlign; + int yAlign; + 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(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, int depth) { + const byte *spriteBuffer; + int x, y; + byte *destRowPointer; + const byte *sourceRowPointer; + const byte *sourcePointer; + byte *destPointer; + byte *maskPointer; + int width; + int height; + int xAlign; + int yAlign; + + ClipData clipData; + + // BG mask variables + int maskWidth; + int maskHeight; + byte *maskBuffer; + size_t maskBufferLength; + byte *maskRowPointer; + int maskZ; + + if (!_vm->_scene->isBGMaskPresent()) { + draw(ds, clipRect, spriteList, spriteNumber, screenCoord, scale); + return; + } + + _vm->_scene->getBGMaskInfo(maskWidth, maskHeight, maskBuffer, maskBufferLength); + + 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 = clipRect; + + if (!clipData.calcClip()) { + return; + } + + // Finally, draw the occluded sprite + + sourceRowPointer = spriteBuffer + clipData.drawSource.x + (clipData.drawSource.y * width); + destRowPointer = (byte *)ds->pixels + clipData.drawDest.x + (clipData.drawDest.y * ds->pitch); + 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 += ds->pitch; + maskRowPointer += maskWidth; + sourceRowPointer += width; + } +} + +void Sprite::decodeRLEBuffer(const byte *inputBuffer, size_t inLength, size_t outLength) { + int bg_runcount; + int fg_runcount; + byte *outPointer; + byte *outPointerEnd; + int c; + + if (outLength > _decodeBufLen) { // TODO: may we should make dynamic growing? + error("Sprite::decodeRLEBuffer outLength > _decodeBufLen"); + } + + outPointer = _decodeBuf; + outPointerEnd = _decodeBuf + outLength; + outPointerEnd--; + + memset(outPointer, 0, outLength); + + MemoryReadStream readS(inputBuffer, inLength); + + while (!readS.eos() && (outPointer < outPointerEnd)) { + bg_runcount = readS.readByte(); + 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 (outPointer < outPointerEnd) + outPointer++; + else + return; + } + } +} + +void Sprite::scaleBuffer(const byte *src, int width, int height, int scale) { + byte skip = 256 - scale; // skip factor + byte vskip = 0x80, hskip; + byte *dst = _decodeBuf; + + 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 diff --git a/engines/saga/sprite.h b/engines/saga/sprite.h new file mode 100644 index 0000000000..03bde8e050 --- /dev/null +++ b/engines/saga/sprite.h @@ -0,0 +1,102 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Sprite management module private header file + +#ifndef SAGA_SPRITE_H__ +#define SAGA_SPRITE_H__ + +namespace Saga { + +#define SPRITE_ZMAX 16 +#define SPRITE_ZMASK 0x0F + +#define DECODE_BUF_LEN 64000 + +struct SpriteInfo { + byte *decodedBuffer; + int width; + int height; + int xAlign; + int yAlign; +}; + +struct SpriteList { + int spriteListResourceId; + int spriteCount; + SpriteInfo *infoList; + + void freeMem() { + int i; + for (i = 0; i < spriteCount; i++) { + free(infoList[i].decodedBuffer); + } + free(infoList); + memset(this, 0, sizeof(*this)); + } + + SpriteList() { + memset(this, 0, sizeof(*this)); + } +}; + + +class Sprite { +public: + SpriteList _mainSprites; + SpriteList _saveReminderSprites; + SpriteList _arrowSprites; + SpriteList _inventorySprites; + + Sprite(SagaEngine *vm); + ~Sprite(void); + + // draw scaled sprite using background scene mask + void drawOccluded(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, int depth); + + // draw scaled sprite using background scene mask + void draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Point &screenCoord, int scale); + + // main function + void drawClip(Surface *ds, const Rect &clipRect, const Point &spritePointer, int width, int height, const byte *spriteBuffer); + + void draw(Surface *ds, const Rect &clipRect, SpriteList &spriteList, int32 spriteNumber, const Rect &screenRect, int scale); + + void loadList(int resourceId, SpriteList &spriteList); // load or append spriteList + bool hitTest(SpriteList &spriteList, int spriteNumber, const Point &screenCoord, int scale, const Point &testPoint); + void getScaledSpriteBuffer(SpriteList &spriteList, int 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); + + SagaEngine *_vm; + ResourceContext *_spriteContext; + byte *_decodeBuf; + size_t _decodeBufLen; +}; + +} // End of namespace Saga + +#endif diff --git a/engines/saga/sthread.cpp b/engines/saga/sthread.cpp new file mode 100644 index 0000000000..a6724b588c --- /dev/null +++ b/engines/saga/sthread.cpp @@ -0,0 +1,746 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// 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/stream.h" +#include "saga/scene.h" +#include "saga/resnames.h" + +namespace Saga { + +ScriptThread *Script::createThread(uint16 scriptModuleNumber, uint16 scriptEntryPointNumber) { + ScriptThread *newThread; + + loadModule(scriptModuleNumber); + if (_modules[scriptModuleNumber].entryPointsCount <= scriptEntryPointNumber) { + error("Script::createThread wrong scriptEntryPointNumber"); + } + + newThread = _threadList.pushFront().operator->(); + newThread->_flags = kTFlagNone; + newThread->_stackSize = DEFAULT_THREAD_STACK_SIZE; + newThread->_stackBuf = (uint16 *)malloc(newThread->_stackSize * sizeof(*newThread->_stackBuf)); + newThread->_stackTopIndex = newThread->_stackSize - 2; + newThread->_instructionOffset = _modules[scriptModuleNumber].entryPoints[scriptEntryPointNumber].offset; + newThread->_commonBase = _commonBuffer; + newThread->_staticBase = _commonBuffer + _modules[scriptModuleNumber].staticOffset; + newThread->_moduleBase = _modules[scriptModuleNumber].moduleBase; + newThread->_moduleBaseSize = _modules[scriptModuleNumber].moduleBaseSize; + + newThread->_strings = &_modules[scriptModuleNumber].strings; + + if (_vm->getGameType() == GType_IHNM) + newThread->_voiceLUT = &_globalVoiceLUT; + else + newThread->_voiceLUT = &_modules[scriptModuleNumber].voiceLUT; + + return newThread; +} + +void Script::wakeUpActorThread(int waitType, void *threadObj) { + ScriptThread *thread; + ScriptThreadList::iterator threadIterator; + + for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) { + thread = threadIterator.operator->(); + if ((thread->_flags & kTFlagWaiting) && (thread->_waitType == waitType) && (thread->_threadObj == threadObj)) { + thread->_flags &= ~kTFlagWaiting; + } + } +} + +void Script::wakeUpThreads(int waitType) { + ScriptThread *thread; + ScriptThreadList::iterator threadIterator; + + for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) { + thread = threadIterator.operator->(); + if ((thread->_flags & kTFlagWaiting) && (thread->_waitType == waitType)) { + thread->_flags &= ~kTFlagWaiting; + } + } +} + +void Script::wakeUpThreadsDelayed(int waitType, int sleepTime) { + ScriptThread *thread; + ScriptThreadList::iterator threadIterator; + + for (threadIterator = _threadList.begin(); threadIterator != _threadList.end(); ++threadIterator) { + thread = threadIterator.operator->(); + if ((thread->_flags & kTFlagWaiting) && (thread->_waitType == waitType)) { + thread->_waitType = kWaitTypeDelay; + thread->_sleepTime = sleepTime; + } + } +} + +void Script::executeThreads(uint msec) { + ScriptThread *thread; + ScriptThreadList::iterator threadIterator; + + if (_vm->_interface->_statusTextInput) { + return; + } + + threadIterator = _threadList.begin(); + + while (threadIterator != _threadList.end()) { + thread = threadIterator.operator->(); + + if (thread->_flags & (kTFlagFinished | kTFlagAborted)) { + if (thread->_flags & kTFlagFinished) + setPointerVerb(); + + if (_vm->getGameType() == GType_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; + actor = (ActorData *)thread->_threadObj; + if (actor->_currentAction == kActionWait) { + thread->_flags &= ~kTFlagWaiting; + } + } + break; + + case kWaitTypeWaitFrames: // IHNM + if (thread->_frameWait < _vm->_frameCount) + thread->_flags &= ~kTFlagWaiting; + break; + } + } + + if (!(thread->_flags & kTFlagWaiting)) { + if (runThread(thread, STHREAD_TIMESLICE)) { + break; + } + } + + ++threadIterator; + } + +} + +void Script::abortAllThreads(void) { + ScriptThread *thread; + ScriptThreadList::iterator threadIterator; + + threadIterator = _threadList.begin(); + + while (threadIterator != _threadList.end()) { + thread = threadIterator.operator->(); + thread->_flags |= kTFlagAborted; + ++threadIterator; + } + executeThreads(0); +} + +void Script::completeThread(void) { + int limit = (_vm->getGameType() == GType_IHNM) ? 100 : 40; + + for (int i = 0; i < limit && !_threadList.isEmpty(); i++) + executeThreads(0); +} + +bool Script::runThread(ScriptThread *thread, uint instructionLimit) { + const char*operandName; + uint instructionCount; + uint16 savedInstructionOffset; + + byte *addr; + byte mode; + uint16 jmpOffset1; + int16 iparam1; + int16 iparam2; + int16 iparam3; + + bool disContinue; + byte argumentsCount; + uint16 functionNumber; + uint16 checkStackTopIndex; + ScriptFunctionType scriptFunction; + + int operandChar; + int i; + + MemoryReadStream scriptS(thread->_moduleBase, thread->_moduleBaseSize); + + scriptS.seek(thread->_instructionOffset); + + for (instructionCount = 0; instructionCount < instructionLimit; instructionCount++) { + if (thread->_flags & (kTFlagAsleep)) + break; + + savedInstructionOffset = thread->_instructionOffset; + operandChar = scriptS.readByte(); + + +#define CASEOP(opName) case opName: \ + if (operandChar == opName) { \ + operandName = #opName; \ + debug(2, operandName); \ + _vm->_console->DebugPrintf("%s\n", operandName); \ + } + + debug(8, "Executing thread offset: %lu (%x) stack: %d", thread->_instructionOffset, operandChar, thread->pushedSize()); + operandName=""; + switch (operandChar) { + CASEOP(opNextBlock) + // Some sort of "jump to the start of the next memory + // page" instruction, I think. + thread->_instructionOffset = (((thread->_instructionOffset) >> 10) + 1) << 10; + break; + +// STACK INSTRUCTIONS + CASEOP(opDup) + thread->push(thread->stackTop()); + break; + CASEOP(opDrop) + thread->pop(); + break; + CASEOP(opZero) + thread->push(0); + break; + CASEOP(opOne) + thread->push(1); + break; + CASEOP(opConstint) + CASEOP(opStrlit) + iparam1 = scriptS.readSint16LE(); + thread->push(iparam1); + debug(8, "0x%X", iparam1); + break; + +// DATA INSTRUCTIONS + CASEOP(opGetFlag) + addr = thread->baseAddress(scriptS.readByte()); + iparam1 = scriptS.readSint16LE(); + addr += (iparam1 >> 3); + iparam1 = (1 << (iparam1 & 7)); + thread->push((*addr) & iparam1 ? 1 : 0); + break; + CASEOP(opGetInt) + mode = scriptS.readByte(); + addr = thread->baseAddress(mode); + iparam1 = scriptS.readSint16LE(); + addr += iparam1; + thread->push(readUint16(addr, mode)); + debug(8, "0x%X", readUint16(addr, mode)); + break; + CASEOP(opPutFlag) + addr = thread->baseAddress(scriptS.readByte()); + iparam1 = scriptS.readSint16LE(); + addr += (iparam1 >> 3); + iparam1 = (1 << (iparam1 & 7)); + if (thread->stackTop()) { + *addr |= iparam1; + } else { + *addr &= ~iparam1; + } + break; + CASEOP(opPutInt) + mode = scriptS.readByte(); + addr = thread->baseAddress(mode); + iparam1 = scriptS.readSint16LE(); + addr += iparam1; + writeUint16(addr, thread->stackTop(), mode); + break; + CASEOP(opPutFlagV) + addr = thread->baseAddress(scriptS.readByte()); + iparam1 = scriptS.readSint16LE(); + addr += (iparam1 >> 3); + iparam1 = (1 << (iparam1 & 7)); + if (thread->pop()) { + *addr |= iparam1; + } else { + *addr &= ~iparam1; + } + break; + CASEOP(opPutIntV) + mode = scriptS.readByte(); + addr = thread->baseAddress(mode); + iparam1 = scriptS.readSint16LE(); + addr += iparam1; + writeUint16(addr, thread->pop(), mode); + break; + +// FUNCTION CALL INSTRUCTIONS + CASEOP(opCall) + argumentsCount = scriptS.readByte(); + iparam1 = scriptS.readByte(); + if (iparam1 != kAddressModule) { + error("Script::runThread iparam1 != kAddressModule"); + } + addr = thread->baseAddress(iparam1); + iparam1 = scriptS.readSint16LE(); + addr += iparam1; + thread->push(argumentsCount); + + jmpOffset1 = scriptS.pos(); + // NOTE: The original pushes the program + // counter as a pointer here. But I don't think + // we will have to do that. + thread->push(jmpOffset1); + // NOTE2: program counter is 32bit - so we should "emulate" it size - because kAddressStack relies on it + thread->push(0); + thread->_instructionOffset = iparam1; + + break; + CASEOP(opCcall) + CASEOP(opCcallV) + argumentsCount = scriptS.readByte(); + functionNumber = scriptS.readUint16LE(); + if (functionNumber >= ((_vm->getGameType() == GType_IHNM) ? + IHNM_SCRIPT_FUNCTION_MAX : ITE_SCRIPT_FUNCTION_MAX)) { + error("Script::runThread() Invalid script function number (%d)", functionNumber); + } + + debug(2, "Calling #%d %s argCount=%i", functionNumber, _scriptFunctionsList[functionNumber].scriptFunctionName, argumentsCount); + scriptFunction = _scriptFunctionsList[functionNumber].scriptFunction; + checkStackTopIndex = thread->_stackTopIndex + argumentsCount; + disContinue = false; + (this->*scriptFunction)(thread, argumentsCount, disContinue); + if (disContinue) { + return true; + } + if (scriptFunction == &Saga::Script::sfScriptGotoScene || + scriptFunction == &Saga::Script::sfVsetTrack) { + return true; // cause abortAllThreads called and _this_ thread destroyed + } + + thread->_stackTopIndex = checkStackTopIndex; + + if (operandChar == opCcall) {// CALL function + thread->push(thread->_returnValue); + } + + if (thread->_flags & kTFlagAsleep) + instructionCount = instructionLimit; // break out of loop! + break; + CASEOP(opEnter) + thread->push(thread->_frameIndex); + thread->_frameIndex = thread->_stackTopIndex; + thread->_stackTopIndex -= (scriptS.readSint16LE() / 2); + break; + CASEOP(opReturn) + thread->_returnValue = thread->pop(); + CASEOP(opReturnV) + thread->_stackTopIndex = thread->_frameIndex; + thread->_frameIndex = thread->pop(); + if (thread->pushedSize() == 0) { + thread->_flags |= kTFlagFinished; + return true; + } else { + thread->pop(); //cause it 0 + thread->_instructionOffset = thread->pop(); + + // Pop all the call parameters off the stack + iparam1 = thread->pop(); + while (iparam1--) { + thread->pop(); + } + + if (operandChar == opReturn) { + thread->push(thread->_returnValue); + } + } + break; + +// BRANCH INSTRUCTIONS + CASEOP(opJmp) + jmpOffset1 = scriptS.readUint16LE(); + thread->_instructionOffset = jmpOffset1; + break; + CASEOP(opJmpTrueV) + jmpOffset1 = scriptS.readUint16LE(); + if (thread->pop()) { + thread->_instructionOffset = jmpOffset1; + } + break; + CASEOP(opJmpFalseV) + jmpOffset1 = scriptS.readUint16LE(); + if (!thread->pop()) { + thread->_instructionOffset = jmpOffset1; + } + break; + CASEOP(opJmpTrue) + jmpOffset1 = scriptS.readUint16LE(); + if (thread->stackTop()) { + thread->_instructionOffset = jmpOffset1; + } + break; + CASEOP(opJmpFalse) + jmpOffset1 = scriptS.readUint16LE(); + if (!thread->stackTop()) { + thread->_instructionOffset = jmpOffset1; + } + break; + CASEOP(opJmpSwitch) + iparam1 = scriptS.readSint16LE(); + iparam2 = thread->pop(); + while (iparam1--) { + iparam3 = scriptS.readUint16LE(); + thread->_instructionOffset = scriptS.readUint16LE(); + if (iparam3 == iparam2) { + break; + } + } + if (iparam1 < 0) { + thread->_instructionOffset = scriptS.readUint16LE(); + } + break; + CASEOP(opJmpRandom) + // Supposedly the number of possible branches. + // The original interpreter ignores it. + scriptS.readUint16LE(); + iparam1 = scriptS.readSint16LE(); + iparam1 = _vm->_rnd.getRandomNumber(iparam1 - 1); + while (1) { + iparam2 = scriptS.readSint16LE(); + thread->_instructionOffset = scriptS.readUint16LE(); + + iparam1 -= iparam2; + if (iparam1 < 0) { + break; + } + } + break; + +// UNARY INSTRUCTIONS + CASEOP(opNegate) + thread->push(-thread->pop()); + break; + CASEOP(opNot) + thread->push(!thread->pop()); + break; + CASEOP(opCompl) + thread->push(~thread->pop()); + break; + + CASEOP(opIncV) + mode = scriptS.readByte(); + addr = thread->baseAddress(mode); + iparam1 = scriptS.readSint16LE(); + addr += iparam1; + iparam1 = readUint16(addr, mode); + writeUint16(addr, iparam1 + 1, mode); + break; + CASEOP(opDecV) + mode = scriptS.readByte(); + addr = thread->baseAddress(mode); + iparam1 = scriptS.readSint16LE(); + addr += iparam1; + iparam1 = readUint16(addr, mode); + writeUint16(addr, iparam1 - 1, mode); + break; + CASEOP(opPostInc) + mode = scriptS.readByte(); + addr = thread->baseAddress(mode); + iparam1 = scriptS.readSint16LE(); + addr += iparam1; + iparam1 = readUint16(addr, mode); + thread->push(iparam1); + writeUint16(addr, iparam1 + 1, mode); + break; + CASEOP(opPostDec) + mode = scriptS.readByte(); + addr = thread->baseAddress(mode); + iparam1 = scriptS.readSint16LE(); + addr += iparam1; + iparam1 = readUint16(addr, mode); + thread->push(iparam1); + writeUint16(addr, iparam1 - 1, mode); + break; + +// ARITHMETIC INSTRUCTIONS + CASEOP(opAdd) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 += iparam2; + thread->push(iparam1); + break; + CASEOP(opSub) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 -= iparam2; + thread->push(iparam1); + break; + CASEOP(opMul) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 *= iparam2; + thread->push(iparam1); + break; + CASEOP(opDiv) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 /= iparam2; + thread->push(iparam1); + break; + CASEOP(opMod) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 %= iparam2; + thread->push(iparam1); + break; + +// COMPARISION INSTRUCTIONS + CASEOP(opEq) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + thread->push((iparam1 == iparam2) ? 1 : 0); + debug(8, "0x%X 0x%X", iparam1, iparam2); + break; + CASEOP(opNe) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + thread->push((iparam1 != iparam2) ? 1 : 0); + break; + CASEOP(opGt) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + thread->push((iparam1 > iparam2) ? 1 : 0); + break; + CASEOP(opLt) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + thread->push((iparam1 < iparam2) ? 1 : 0); + break; + CASEOP(opGe) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + thread->push((iparam1 >= iparam2) ? 1 : 0); + break; + CASEOP(opLe) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + thread->push((iparam1 <= iparam2) ? 1 : 0); + break; + +// SHIFT INSTRUCTIONS + CASEOP(opRsh) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 >>= iparam2; + thread->push(iparam1); + break; + CASEOP(opLsh) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 <<= iparam2; + thread->push(iparam1); + break; + +// BITWISE INSTRUCTIONS + CASEOP(opAnd) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 &= iparam2; + thread->push(iparam1); + break; + CASEOP(opOr) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 |= iparam2; + thread->push(iparam1); + break; + CASEOP(opXor) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + iparam1 ^= iparam2; + thread->push(iparam1); + break; + +// LOGICAL INSTRUCTIONS + CASEOP(opLAnd) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + thread->push((iparam1 && iparam2) ? 1 : 0); + break; + CASEOP(opLOr) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + thread->push((iparam1 || iparam2) ? 1 : 0); + break; + CASEOP(opLXor) + iparam2 = thread->pop(); + iparam1 = thread->pop(); + thread->push(((iparam1 && !iparam2) || (!iparam1 && iparam2)) ? 1 : 0); + break; + +// GAME INSTRUCTIONS + CASEOP(opSpeak) { + int stringsCount; + uint16 actorId; + uint16 speechFlags; + int sampleResourceId = -1; + int16 first; + const char *strings[ACTOR_SPEECH_STRING_MAX]; + + if (_vm->_actor->isSpeaking()) { + thread->wait(kWaitTypeSpeech); + return false; + } + + stringsCount = scriptS.readByte(); + actorId = scriptS.readUint16LE(); + speechFlags = scriptS.readByte(); + scriptS.readUint16LE(); // x,y skip + + if (stringsCount == 0) + error("opSpeak stringsCount == 0"); + + if (stringsCount > ACTOR_SPEECH_STRING_MAX) + error("opSpeak stringsCount=0x%X exceed ACTOR_SPEECH_STRING_MAX", stringsCount); + + iparam1 = first = thread->stackTop(); + for (i = 0; i < stringsCount; i++) { + iparam1 = thread->pop(); + strings[i] = thread->_strings->getString(iparam1); + } + // now data contains last string index + + if (_vm->getGameId() == GID_ITE_DISK_G) { // special ITE dos + if ((_vm->_scene->currentSceneNumber() == ITE_DEFAULT_SCENE) && + (iparam1 >= 288) && (iparam1 <= (RID_SCENE1_VOICE_138 - RID_SCENE1_VOICE_009 + 288))) { + sampleResourceId = RID_SCENE1_VOICE_009 + iparam1 - 288; + } + } else { + if (thread->_voiceLUT->voicesCount > first) { + sampleResourceId = thread->_voiceLUT->voices[first]; + } + } + + if (sampleResourceId < 0 || sampleResourceId > 4000) + sampleResourceId = -1; + + if (_vm->getGameType() == GType_ITE && !sampleResourceId) + sampleResourceId = -1; + + _vm->_actor->actorSpeech(actorId, strings, stringsCount, sampleResourceId, speechFlags); + + if (!(speechFlags & kSpeakAsync)) { + thread->wait(kWaitTypeSpeech); + } + } + break; + CASEOP(opDialogBegin) + if (_conversingThread) { + thread->wait(kWaitTypeDialogBegin); + return false; + } + _conversingThread = thread; + _vm->_interface->converseClear(); + break; + CASEOP(opDialogEnd) + if (thread == _conversingThread) { + _vm->_interface->activate(); + _vm->_interface->setMode(kPanelConverse); + thread->wait(kWaitTypeDialogEnd); + return false; + } + break; + CASEOP(opReply) { + const char *str; + byte replyNum; + byte flags; + replyNum = scriptS.readByte(); + flags = scriptS.readByte(); + iparam1 = 0; + + if (flags & kReplyOnce) { + iparam1 = scriptS.readSint16LE(); + addr = thread->_staticBase + (iparam1 >> 3); + if (*addr & (1 << (iparam1 & 7))) { + break; + } + } + + str = thread->_strings->getString(thread->pop()); + if (_vm->_interface->converseAddText(str, replyNum, flags, iparam1)) + warning("Error adding ConverseText (%s, %d, %d, %d)", str, replyNum, flags, iparam1); + } + break; + CASEOP(opAnimate) + scriptS.readUint16LE(); + scriptS.readUint16LE(); + jmpOffset1 = scriptS.readByte(); + thread->_instructionOffset += jmpOffset1; + break; + + default: + error("Script::runThread() Invalid opcode encountered 0x%X", operandChar); + } + + 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); + } + } + return false; +} + +} // End of namespace Saga + diff --git a/engines/saga/stream.h b/engines/saga/stream.h new file mode 100644 index 0000000000..4a407216b2 --- /dev/null +++ b/engines/saga/stream.h @@ -0,0 +1,57 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef SAGA_STREAM_H__ +#define SAGA_STREAM_H__ + +#include "common/stream.h" + +namespace Saga { + +using Common::MemoryReadStream; + +class MemoryReadStreamEndian : public Common::MemoryReadStream { +private: +public: + bool _bigEndian; + MemoryReadStreamEndian(const byte *buf, uint32 len, bool bigEndian = false) : MemoryReadStream(buf, len), _bigEndian(bigEndian) {} + + uint16 readUint16() { + return (_bigEndian) ? readUint16BE(): readUint16LE(); + } + + uint32 readUint32() { + return (_bigEndian) ? readUint32BE(): readUint32LE(); + } + + inline int16 readSint16() { + return (int16)readUint16(); + } + + + inline int32 readSint32() { + return (int32)readUint32(); + } +}; + +} // End of namespace Saga +#endif diff --git a/engines/saga/xref.txt b/engines/saga/xref.txt new file mode 100644 index 0000000000..1d3aff0824 --- /dev/null +++ b/engines/saga/xref.txt @@ -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 |