scummvm/engines/draci/game.cpp
Robert Špalek baec8c2421 Set all sound/subtitle-related parameters from ConfMan.
Made it intelligent so that when, for example, the dubbing file doesn't exist,
we don't fail, but instead always show subtitles even if the GUI settings
says dubbing only, etc.

svn-id: r45002
2009-10-13 05:38:45 +00:00

1762 lines
50 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
*/
#include "common/stream.h"
#include "draci/draci.h"
#include "draci/game.h"
#include "draci/barchive.h"
#include "draci/script.h"
#include "draci/animation.h"
#include <cmath>
namespace Draci {
static const Common::String dialoguePath("ROZH");
static double real_to_double(byte real[6]);
Game::Game(DraciEngine *vm) : _vm(vm) {
uint i;
BArchive *initArchive = _vm->_initArchive;
const BAFile *file;
// Read in persons
file = initArchive->getFile(5);
Common::MemoryReadStream personData(file->_data, file->_length);
uint numPersons = file->_length / personSize;
_persons = new Person[numPersons];
for (i = 0; i < numPersons; ++i) {
_persons[i]._x = personData.readUint16LE();
_persons[i]._y = personData.readUint16LE();
_persons[i]._fontColour = personData.readByte();
}
// Read in dialogue offsets
file = initArchive->getFile(4);
Common::MemoryReadStream dialogueData(file->_data, file->_length);
uint numDialogues = file->_length / sizeof(uint16);
_dialogueOffsets = new uint[numDialogues];
uint curOffset;
for (i = 0, curOffset = 0; i < numDialogues; ++i) {
_dialogueOffsets[i] = curOffset;
curOffset += dialogueData.readUint16LE();
}
_dialogueVars = new int[curOffset];
memset(_dialogueVars, 0, sizeof (int) * curOffset);
// Read in game info
file = initArchive->getFile(3);
Common::MemoryReadStream gameData(file->_data, file->_length);
_info._startRoom = gameData.readByte() - 1;
_info._mapRoom = gameData.readByte() - 1;
_info._numObjects = gameData.readUint16LE();
_info._numItems = gameData.readUint16LE();
_info._numVariables = gameData.readByte();
_info._numPersons = gameData.readByte();
_info._numDialogues = gameData.readByte();
_info._maxItemWidth = gameData.readUint16LE();
_info._maxItemHeight = gameData.readUint16LE();
_info._musicLength = gameData.readUint16LE();
_info._crc[0] = gameData.readUint16LE();
_info._crc[1] = gameData.readUint16LE();
_info._crc[2] = gameData.readUint16LE();
_info._crc[3] = gameData.readUint16LE();
_info._numDialogueBlocks = curOffset;
// Read in variables
file = initArchive->getFile(2);
uint numVariables = file->_length / sizeof (int16);
_variables = new int[numVariables];
Common::MemoryReadStream variableData(file->_data, file->_length);
for (i = 0; i < numVariables; ++i) {
_variables[i] = variableData.readUint16LE();
}
// Read in item icon status
file = initArchive->getFile(1);
uint numItems = file->_length;
_itemStatus = new byte[numItems];
memcpy(_itemStatus, file->_data, numItems);
_items = new GameItem[numItems];
// Read in object status
file = initArchive->getFile(0);
uint numObjects = file->_length;
_objects = new GameObject[numObjects];
Common::MemoryReadStream objStatus(file->_data, file->_length);
for (i = 0; i < numObjects; ++i) {
byte tmp = objStatus.readByte();
// Set object visibility
_objects[i]._visible = tmp & (1 << 7);
// Set object location
_objects[i]._location = (~(1 << 7) & tmp) - 1;
}
assert(numDialogues == _info._numDialogues);
assert(numPersons == _info._numPersons);
assert(numVariables == _info._numVariables);
assert(numObjects == _info._numObjects);
assert(numItems == _info._numItems);
// Deallocate all cached files, because we have copied them into our own data structures.
initArchive->clearCache();
}
void Game::start() {
while (!shouldQuit()) {
debugC(1, kDraciGeneralDebugLevel, "Game::start()");
const bool force_reload = shouldExitLoop() > 1;
// Whenever the top-level loop is entered, it should not finish unless
// the exit is triggered by a script
_shouldExitLoop = false;
enterNewRoom(force_reload);
loop();
}
}
void Game::init() {
_shouldQuit = false;
_shouldExitLoop = false;
_scheduledPalette = 0;
setLoopStatus(kStatusGate);
setLoopSubstatus(kSubstatusOrdinary);
_animUnderCursor = kOverlayImage;
_currentItem = kNoItem;
_itemUnderCursor = kNoItem;
_vm->_mouse->setCursorType(kNormalCursor);
_oldObjUnderCursor = _objUnderCursor = kOverlayImage;
// Set the inventory to empty initially
memset(_inventory, kNoItem, kInventorySlots * sizeof(int));
// Initialize animation for object / room titles
Animation *titleAnim = _vm->_anims->addText(kTitleText, true);
Text *title = new Text("", _vm->_smallFont, kTitleColour, 0, 0);
titleAnim->addFrame(title, NULL);
// Initialize animation for speech text
Animation *speechAnim = _vm->_anims->addText(kSpeechText, true);
Text *speech = new Text("", _vm->_bigFont, kFontColour1, 0, 0);
speechAnim->addFrame(speech, NULL);
// Initialize inventory animation
const BAFile *f = _vm->_iconsArchive->getFile(13);
Animation *inventoryAnim = _vm->_anims->addAnimation(kInventorySprite, 255, false);
Sprite *inventorySprite = new Sprite(f->_data, f->_length, 0, 0, true);
inventoryAnim->addFrame(inventorySprite, NULL);
inventoryAnim->setRelative((kScreenWidth - inventorySprite->getWidth()) / 2,
(kScreenHeight - inventorySprite->getHeight()) / 2);
for (uint i = 0; i < kDialogueLines; ++i) {
_dialogueAnims[i] = _vm->_anims->addText(kDialogueLinesID - i, true);
Text *dialogueLine = new Text("", _vm->_smallFont, kLineInactiveColour, 0, 0);
_dialogueAnims[i]->addFrame(dialogueLine, NULL);
_dialogueAnims[i]->setZ(254);
_dialogueAnims[i]->setRelative(1,
kScreenHeight - (i + 1) * _vm->_smallFont->getFontHeight());
Text *text = reinterpret_cast<Text *>(_dialogueAnims[i]->getFrame());
text->setText("");
}
for (uint i = 0; i < _info._numItems; ++i) {
loadItem(i);
}
loadObject(kDragonObject);
const GameObject *dragon = getObject(kDragonObject);
debugC(4, kDraciLogicDebugLevel, "Running init program for the dragon object...");
_vm->_script->run(dragon->_program, dragon->_init);
// Make sure we enter the right room in start().
setRoomNum(kNoEscRoom);
rememberRoomNumAsPrevious();
scheduleEnteringRoomUsingGate(_info._startRoom, 0);
_pushedNewRoom = _pushedNewGate = -1;
}
void Game::loop() {
Surface *surface = _vm->_screen->getSurface();
do {
debugC(4, kDraciLogicDebugLevel, "loopstatus: %d, loopsubstatus: %d",
_loopStatus, _loopSubstatus);
_vm->handleEvents();
if (shouldExitLoop() > 1) // after loading
break;
// Fetch mouse coordinates
int x = _vm->_mouse->getPosX();
int y = _vm->_mouse->getPosY();
if (_loopStatus == kStatusDialogue && _loopSubstatus == kSubstatusOrdinary) {
Text *text;
for (int i = 0; i < kDialogueLines; ++i) {
text = reinterpret_cast<Text *>(_dialogueAnims[i]->getFrame());
if (_animUnderCursor == _dialogueAnims[i]->getID()) {
text->setColour(kLineActiveColour);
} else {
text->setColour(kLineInactiveColour);
}
}
if (_vm->_mouse->lButtonPressed() || _vm->_mouse->rButtonPressed()) {
_shouldExitLoop = true;
_vm->_mouse->lButtonSet(false);
_vm->_mouse->rButtonSet(false);
}
}
if (_vm->_mouse->isCursorOn()) {
// Fetch the dedicated objects' title animation / current frame
Animation *titleAnim = _vm->_anims->getAnimation(kTitleText);
Text *title = reinterpret_cast<Text *>(titleAnim->getFrame());
updateCursor();
updateTitle();
if (_loopStatus == kStatusOrdinary && _loopSubstatus == kSubstatusOrdinary) {
if (_vm->_mouse->lButtonPressed()) {
_vm->_mouse->lButtonSet(false);
if (_currentItem != kNoItem) {
putItem(_currentItem, 0);
_currentItem = kNoItem;
updateCursor();
} else {
if (_objUnderCursor != kObjectNotFound) {
const GameObject *obj = &_objects[_objUnderCursor];
_vm->_mouse->cursorOff();
titleAnim->markDirtyRect(surface);
title->setText("");
_objUnderCursor = kObjectNotFound;
if (!obj->_imLook) {
if (obj->_lookDir == kDirectionLast) {
walkHero(x, y, obj->_lookDir);
} else {
walkHero(obj->_lookX, obj->_lookY, obj->_lookDir);
}
}
_vm->_script->run(obj->_program, obj->_look);
_vm->_mouse->cursorOn();
} else {
walkHero(x, y, kDirectionLast);
}
}
}
if (_vm->_mouse->rButtonPressed()) {
_vm->_mouse->rButtonSet(false);
if (_objUnderCursor != kObjectNotFound) {
const GameObject *obj = &_objects[_objUnderCursor];
if (_vm->_script->testExpression(obj->_program, obj->_canUse)) {
_vm->_mouse->cursorOff();
titleAnim->markDirtyRect(surface);
title->setText("");
_objUnderCursor = kObjectNotFound;
if (!obj->_imUse) {
if (obj->_useDir == kDirectionLast) {
walkHero(x, y, obj->_useDir);
} else {
walkHero(obj->_useX, obj->_useY, obj->_useDir);
}
}
_vm->_script->run(obj->_program, obj->_use);
_vm->_mouse->cursorOn();
} else {
walkHero(x, y, kDirectionLast);
}
} else {
if (_vm->_script->testExpression(_currentRoom._program, _currentRoom._canUse)) {
_vm->_mouse->cursorOff();
titleAnim->markDirtyRect(surface);
title->setText("");
_vm->_script->run(_currentRoom._program, _currentRoom._use);
_vm->_mouse->cursorOn();
} else {
walkHero(x, y, kDirectionLast);
}
}
}
}
if (_loopStatus == kStatusInventory && _loopSubstatus == kSubstatusOrdinary) {
if (_inventoryExit) {
inventoryDone();
}
// If we are in inventory mode, all the animations except game items'
// images will necessarily be paused so we can safely assume that any
// animation under the cursor (a value returned by
// AnimationManager::getTopAnimationID()) will be an item animation or
// an overlay, for which we check. Item animations have their IDs
// calculated by offseting their itemID from the ID of the last "special"
// animation ID. In this way, we obtain its itemID.
if (_animUnderCursor != kOverlayImage && _animUnderCursor != kInventorySprite) {
_itemUnderCursor = kInventoryItemsID - _animUnderCursor;
} else {
_itemUnderCursor = kNoItem;
}
// If the user pressed the left mouse button
if (_vm->_mouse->lButtonPressed()) {
_vm->_mouse->lButtonSet(false);
// If there is an inventory item under the cursor and we aren't
// holding any item, run its look GPL program
if (_itemUnderCursor != kNoItem && _currentItem == kNoItem) {
const GameItem *item = &_items[_itemUnderCursor];
_vm->_script->run(item->_program, item->_look);
// Otherwise, if we are holding an item, try to place it inside the
// inventory
} else if (_currentItem != kNoItem) {
// FIXME: This should place the item in the nearest inventory slot,
// not the first one available
putItem(_currentItem, 0);
// Remove it from our hands
_currentItem = kNoItem;
}
} else if (_vm->_mouse->rButtonPressed()) {
_vm->_mouse->rButtonSet(false);
// If we right-clicked outside the inventory, close it
if (_animUnderCursor != kInventorySprite && _itemUnderCursor == kNoItem) {
inventoryDone();
// If there is an inventory item under our cursor
} else if (_itemUnderCursor != kNoItem) {
// Again, we have two possibilities:
// The first is that there is no item in our hands.
// In that case, just take the inventory item from the inventory.
if (_currentItem == kNoItem) {
_currentItem = _itemUnderCursor;
removeItem(_itemUnderCursor);
// The second is that there *is* an item in our hands.
// In that case, run the canUse script for the inventory item
// which will check if the two items are combinable and, finally,
// run the use script for the item.
} else {
const GameItem *item = &_items[_itemUnderCursor];
if (_vm->_script->testExpression(item->_program, item->_canUse)) {
_vm->_script->run(item->_program, item->_use);
}
}
updateCursor();
}
}
}
}
debugC(5, kDraciLogicDebugLevel, "Anim under cursor: %d", _animUnderCursor);
// Handle character talking (if there is any)
if (_loopSubstatus == kSubstatusTalk) {
// If the current speech text has expired or the user clicked a mouse button,
// advance to the next line of text
if (_vm->_mouse->lButtonPressed() ||
_vm->_mouse->rButtonPressed() ||
(_vm->_system->getMillis() - _speechTick) >= _speechDuration) {
_shouldExitLoop = true;
_vm->_mouse->lButtonSet(false);
_vm->_mouse->rButtonSet(false);
}
}
// This returns true if we got a signal to quit the game
if (shouldQuit())
return;
// Advance animations and redraw screen
_vm->_anims->drawScene(surface);
_vm->_screen->copyToScreen();
_vm->_system->delayMillis(20);
// HACK: Won't be needed once the game loop is implemented properly
_shouldExitLoop = _shouldExitLoop || (_newRoom != getRoomNum() &&
(_loopStatus == kStatusOrdinary || _loopStatus == kStatusGate));
} while (!shouldExitLoop());
}
void Game::updateCursor() {
// Fetch mouse coordinates
int x = _vm->_mouse->getPosX();
int y = _vm->_mouse->getPosY();
// Find animation under cursor
_animUnderCursor = _vm->_anims->getTopAnimationID(x, y);
// If we are inside a dialogue, all we need is to update the ID of the current
// animation under the cursor. This enables us to update the currently selected
// dialogue line (by recolouring it) but still leave the cursor unupdated when
// over background objects.
if (_loopStatus == kStatusDialogue)
return;
// If we are in inventory mode, we do a different kind of updating that handles
// inventory items and return early
if (_loopStatus == kStatusInventory && _loopSubstatus == kSubstatusOrdinary) {
if (_currentItem == kNoItem) {
_vm->_mouse->setCursorType(kNormalCursor);
} else {
_vm->_mouse->loadItemCursor(_currentItem);
}
if (_itemUnderCursor != kNoItem) {
const GameItem *item = &_items[_itemUnderCursor];
if (_vm->_script->testExpression(item->_program, item->_canUse)) {
if (_currentItem == kNoItem) {
_vm->_mouse->setCursorType(kHighlightedCursor);
} else {
_vm->_mouse->loadItemCursor(_currentItem, true);
}
}
}
return;
}
// Find the game object under the cursor
// (to be more precise, one that corresponds to the animation under the cursor)
int curObject = getObjectWithAnimation(_animUnderCursor);
// Update the game object under the cursor
_objUnderCursor = curObject;
if (_objUnderCursor != _oldObjUnderCursor) {
_oldObjUnderCursor = _objUnderCursor;
}
// Load the appropriate cursor (item image if an item is held or ordinary cursor
// if not)
if (_currentItem == kNoItem) {
_vm->_mouse->setCursorType(kNormalCursor);
} else {
_vm->_mouse->loadItemCursor(_currentItem);
}
// TODO: Handle main menu
// If there is no game object under the cursor, try using the room itself
if (_objUnderCursor == kObjectNotFound) {
if (_vm->_script->testExpression(_currentRoom._program, _currentRoom._canUse)) {
if (_currentItem == kNoItem) {
_vm->_mouse->setCursorType(kHighlightedCursor);
} else {
_vm->_mouse->loadItemCursor(_currentItem, true);
}
}
// If there *is* a game object under the cursor, update the cursor image
} else {
const GameObject *obj = &_objects[_objUnderCursor];
// If there is no walking direction set on the object (i.e. the object
// is not a gate / exit), test whether it can be used and, if so,
// update the cursor image (highlight it).
if (obj->_walkDir == 0) {
if (_vm->_script->testExpression(obj->_program, obj->_canUse)) {
if (_currentItem == kNoItem) {
_vm->_mouse->setCursorType(kHighlightedCursor);
} else {
_vm->_mouse->loadItemCursor(_currentItem, true);
}
}
// If the walking direction *is* set, the game object is a gate, so update
// the cursor image to the appropriate arrow.
} else {
_vm->_mouse->setCursorType((CursorType)obj->_walkDir);
}
}
}
void Game::updateTitle() {
// If we are inside a dialogue, don't update titles
if (_loopStatus == kStatusDialogue)
return;
// Fetch current surface and height of the small font (used for titles)
Surface *surface = _vm->_screen->getSurface();
const int smallFontHeight = _vm->_smallFont->getFontHeight();
// Fetch mouse coordinates
int x = _vm->_mouse->getPosX();
int y = _vm->_mouse->getPosY();
// Fetch the dedicated objects' title animation / current frame
Animation *titleAnim = _vm->_anims->getAnimation(kTitleText);
Text *title = reinterpret_cast<Text *>(titleAnim->getFrame());
// Mark dirty rectangle to delete the previous text
titleAnim->markDirtyRect(surface);
if (_loopStatus == kStatusInventory) {
// If there is no item under the cursor, delete the title.
// Otherwise, show the item's title.
if (_itemUnderCursor == kNoItem) {
title->setText("");
} else {
const GameItem *item = &_items[_itemUnderCursor];
title->setText(item->_title);
}
} else {
// If there is no object under the cursor, delete the title.
// Otherwise, show the object's title.
if (_objUnderCursor == kObjectNotFound) {
title->setText("");
} else {
const GameObject *obj = &_objects[_objUnderCursor];
title->setText(obj->_title);
}
}
// Move the title to the correct place (just above the cursor)
int newX = surface->centerOnX(x, title->getWidth());
int newY = surface->putAboveY(y - smallFontHeight / 2, title->getHeight());
titleAnim->setRelative(newX, newY);
// If we are currently playing the title, mark it dirty so it gets updated.
// Otherwise, start playing the title animation.
if (titleAnim->isPlaying()) {
titleAnim->markDirtyRect(surface);
} else {
_vm->_anims->play(titleAnim->getID());
}
}
int Game::getObjectWithAnimation(int animID) const {
for (uint i = 0; i < _info._numObjects; ++i) {
GameObject *obj = &_objects[i];
for (uint j = 0; j < obj->_anim.size(); ++j) {
if (obj->_anim[j] == animID) {
return i;
}
}
}
return kObjectNotFound;
}
void Game::removeItem(int itemID) {
for (uint i = 0; i < kInventorySlots; ++i) {
if (_inventory[i] == itemID) {
_inventory[i] = kNoItem;
_vm->_anims->stop(kInventoryItemsID - itemID);
break;
}
}
}
void Game::putItem(int itemID, int position) {
if (itemID == kNoItem)
return;
if (position >= 0 &&
position < kInventoryLines * kInventoryColumns &&
(_inventory[position] == kNoItem || _inventory[position] == itemID)) {
_inventory[position] = itemID;
} else {
for (position = 0; position < kInventorySlots; ++position) {
if (_inventory[position] == kNoItem) {
_inventory[position] = itemID;
break;
}
}
}
const int line = position / kInventoryColumns + 1;
const int column = position % kInventoryColumns + 1;
const int anim_id = kInventoryItemsID - itemID;
Animation *anim = _vm->_anims->getAnimation(anim_id);
if (!anim) {
anim = _vm->_anims->addItem(anim_id);
const BAFile *img = _vm->_itemImagesArchive->getFile(2 * itemID);
Sprite *sp = new Sprite(img->_data, img->_length, 0, 0, true);
anim->addFrame(sp, NULL);
}
Drawable *frame = anim->getFrame();
const int x = kInventoryX +
(column * kInventoryItemWidth) -
(kInventoryItemWidth / 2) -
(frame->getWidth() / 2);
const int y = kInventoryY +
(line * kInventoryItemHeight) -
(kInventoryItemHeight / 2) -
(frame->getHeight() / 2);
debug(2, "itemID: %d position: %d line: %d column: %d x: %d y: %d", itemID, position, line, column, x, y);
anim->setRelative(x, y);
// If we are in inventory mode, we need to play the item animation, immediately
// upon returning it to its slot but *not* in other modes because it should be
// invisible then (along with the inventory)
if (_loopStatus == kStatusInventory && _loopSubstatus == kSubstatusOrdinary) {
_vm->_anims->play(anim_id);
}
}
void Game::inventoryInit() {
// Pause all "background" animations
_vm->_anims->pauseAnimations();
// Draw the inventory and the current items
inventoryDraw();
// Turn cursor on if it is off
_vm->_mouse->cursorOn();
// Set the appropriate loop status
setLoopStatus(kStatusInventory);
// TODO: This will be used for exiting the inventory automatically when the mouse
// is outside it for some time
_inventoryExit = false;
}
void Game::inventoryDone() {
_vm->_mouse->cursorOn();
setLoopStatus(kStatusOrdinary);
_vm->_anims->unpauseAnimations();
_vm->_anims->stop(kInventorySprite);
for (uint i = 0; i < kInventorySlots; ++i) {
if (_inventory[i] != kNoItem) {
_vm->_anims->stop(kInventoryItemsID - _inventory[i]);
}
}
// Reset item under cursor
_itemUnderCursor = kNoItem;
// TODO: Handle main menu
}
void Game::inventoryDraw() {
_vm->_anims->play(kInventorySprite);
for (uint i = 0; i < kInventorySlots; ++i) {
if (_inventory[i] != kNoItem) {
_vm->_anims->play(kInventoryItemsID - _inventory[i]);
}
}
}
void Game::inventoryReload() {
// Make sure all items are loaded into memory (e.g., after loading a
// savegame) by re-putting them on the same spot in the inventory.
for (uint i = 0; i < kInventorySlots; ++i) {
putItem(_inventory[i], i);
}
}
void Game::dialogueMenu(int dialogueID) {
int oldLines, hit;
char tmp[5];
sprintf(tmp, "%d", dialogueID+1);
Common::String ext(tmp);
_dialogueArchive = new BArchive(dialoguePath + ext + ".dfw");
debugC(4, kDraciLogicDebugLevel, "Starting dialogue (ID: %d, Archive: %s)",
dialogueID, (dialoguePath + ext + ".dfw").c_str());
_currentDialogue = dialogueID;
oldLines = 255;
dialogueInit(dialogueID);
do {
_dialogueExit = false;
hit = dialogueDraw();
debugC(7, kDraciLogicDebugLevel,
"hit: %d, _lines[hit]: %d, lastblock: %d, dialogueLines: %d, dialogueExit: %d",
hit, _lines[hit], _lastBlock, _dialogueLinesNum, _dialogueExit);
if ((!_dialogueExit) && (hit != -1) && (_lines[hit] != -1)) {
if ((oldLines == 1) && (_dialogueLinesNum == 1) && (_lines[hit] == _lastBlock)) {
break;
}
_currentBlock = _lines[hit];
runDialogueProg(_dialogueBlocks[_lines[hit]]._program, 1);
} else {
break;
}
_lastBlock = _lines[hit];
_dialogueVars[_dialogueOffsets[dialogueID] + _lastBlock] += 1;
_dialogueBegin = false;
oldLines = _dialogueLinesNum;
} while (!_dialogueExit);
dialogueDone();
_currentDialogue = kNoDialogue;
}
int Game::dialogueDraw() {
_dialogueLinesNum = 0;
int i = 0;
int ret = 0;
Animation *anim;
Text *dialogueLine;
while ((_dialogueLinesNum < 4) && (i < _blockNum)) {
GPL2Program blockTest;
blockTest._bytecode = _dialogueBlocks[i]._canBlock;
blockTest._length = _dialogueBlocks[i]._canLen;
debugC(3, kDraciLogicDebugLevel, "Testing dialogue block %d", i);
if (_vm->_script->testExpression(blockTest, 1)) {
anim = _dialogueAnims[_dialogueLinesNum];
dialogueLine = reinterpret_cast<Text *>(anim->getFrame());
dialogueLine->setText(_dialogueBlocks[i]._title);
dialogueLine->setColour(kLineInactiveColour);
_lines[_dialogueLinesNum] = i;
_dialogueLinesNum++;
}
++i;
}
for (i = _dialogueLinesNum; i < kDialogueLines; ++i) {
_lines[i] = -1;
anim = _dialogueAnims[i];
dialogueLine = reinterpret_cast<Text *>(anim->getFrame());
dialogueLine->setText("");
}
_oldObjUnderCursor = kObjectNotFound;
if (_dialogueLinesNum > 1) {
_vm->_mouse->cursorOn();
_shouldExitLoop = false;
loop();
_vm->_mouse->cursorOff();
bool notDialogueAnim = true;
for (uint j = 0; j < kDialogueLines; ++j) {
if (_dialogueAnims[j]->getID() == _animUnderCursor) {
notDialogueAnim = false;
break;
}
}
if (notDialogueAnim) {
ret = -1;
} else {
ret = _dialogueAnims[0]->getID() - _animUnderCursor;
}
} else {
ret = _dialogueLinesNum - 1;
}
for (i = 0; i < kDialogueLines; ++i) {
dialogueLine = reinterpret_cast<Text *>(_dialogueAnims[i]->getFrame());
_dialogueAnims[i]->markDirtyRect(_vm->_screen->getSurface());
dialogueLine->setText("");
}
return ret;
}
void Game::dialogueInit(int dialogID) {
_vm->_mouse->setCursorType(kDialogueCursor);
_blockNum = _dialogueArchive->size() / 3;
_dialogueBlocks = new Dialogue[_blockNum];
const BAFile *f;
for (uint i = 0; i < kDialogueLines; ++i) {
_lines[i] = 0;
}
for (int i = 0; i < _blockNum; ++i) {
f = _dialogueArchive->getFile(i * 3);
_dialogueBlocks[i]._canLen = f->_length;
_dialogueBlocks[i]._canBlock = f->_data;
f = _dialogueArchive->getFile(i * 3 + 1);
// The first byte of the file is the length of the string (without the length)
assert(f->_length - 1 == f->_data[0]);
_dialogueBlocks[i]._title = Common::String((char *)(f->_data+1), f->_length-1);
f = _dialogueArchive->getFile(i * 3 + 2);
_dialogueBlocks[i]._program._bytecode = f->_data;
_dialogueBlocks[i]._program._length = f->_length;
}
for (uint i = 0; i < kDialogueLines; ++i) {
_vm->_anims->play(_dialogueAnims[i]->getID());
}
setLoopStatus(kStatusDialogue);
_lastBlock = -1;
_dialogueBegin = true;
}
void Game::dialogueDone() {
for (uint i = 0; i < kDialogueLines; ++i) {
_vm->_anims->stop(_dialogueAnims[i]->getID());
}
delete _dialogueArchive;
delete[] _dialogueBlocks;
setLoopStatus(kStatusOrdinary);
_vm->_mouse->setCursorType(kNormalCursor);
}
void Game::runDialogueProg(GPL2Program prog, int offset) {
// Mark last animation
int lastAnimIndex = _vm->_anims->getLastIndex();
// Run the dialogue program
_vm->_script->run(prog, offset);
deleteAnimationsAfterIndex(lastAnimIndex);
}
bool Game::isDialogueBegin() const {
return _dialogueBegin;
}
bool Game::shouldExitDialogue() const {
return _dialogueExit;
}
void Game::setDialogueExit(bool exit) {
_dialogueExit = exit;
}
int Game::getDialogueBlockNum() const {
return _blockNum;
}
int Game::getDialogueVar(int dialogueID) const {
return _dialogueVars[dialogueID];
}
void Game::setDialogueVar(int dialogueID, int value) {
_dialogueVars[dialogueID] = value;
}
int Game::getCurrentDialogue() const {
return _currentDialogue;
}
int Game::getDialogueLastBlock() const {
return _lastBlock;
}
int Game::getDialogueLinesNum() const {
return _dialogueLinesNum;
}
int Game::getDialogueCurrentBlock() const {
return _currentBlock;
}
int Game::getCurrentDialogueOffset() const {
return _dialogueOffsets[_currentDialogue];
}
void Game::playHeroAnimation(int anim_index) {
const GameObject *dragon = getObject(kDragonObject);
const int animID = dragon->_anim[anim_index];
Animation *anim = _vm->_anims->getAnimation(animID);
stopObjectAnimations(dragon);
positionAnimAsHero(anim);
_vm->_anims->play(animID);
}
void Game::walkHero(int x, int y, SightDirection dir) {
// Needed for the map room with empty walking map. For some reason,
// findNearestWalkable() takes several seconds with 100% CPU to finish
// (correctly).
if (!_currentRoom._heroOn)
return;
Surface *surface = _vm->_screen->getSurface();
_hero = _currentRoom._walkingMap.findNearestWalkable(x, y, surface->getRect());
debugC(3, kDraciLogicDebugLevel, "Walk to x: %d y: %d", _hero.x, _hero.y);
// FIXME: Need to add proper walking (this only warps the dragon to position)
Movement movement = kStopRight;
switch (dir) {
case kDirectionLeft:
movement = kStopLeft;
break;
case kDirectionRight:
movement = kStopRight;
break;
default: {
const GameObject *dragon = getObject(kDragonObject);
const int anim_index = playingObjectAnimation(dragon);
if (anim_index >= 0) {
movement = static_cast<Movement> (anim_index);
}
break;
}
}
playHeroAnimation(movement);
}
void Game::loadItem(int itemID) {
const BAFile *f = _vm->_itemsArchive->getFile(itemID * 3);
Common::MemoryReadStream itemReader(f->_data, f->_length);
GameItem *item = _items + itemID;
item->_init = itemReader.readSint16LE();
item->_look = itemReader.readSint16LE();
item->_use = itemReader.readSint16LE();
item->_canUse = itemReader.readSint16LE();
item->_imInit = itemReader.readByte();
item->_imLook = itemReader.readByte();
item->_imUse = itemReader.readByte();
f = _vm->_itemsArchive->getFile(itemID * 3 + 1);
// The first byte is the length of the string
item->_title = Common::String((const char *)f->_data + 1, f->_length - 1);
assert(f->_data[0] == item->_title.size());
f = _vm->_itemsArchive->getFile(itemID * 3 + 2);
item->_program._bytecode = f->_data;
item->_program._length = f->_length;
}
void Game::loadRoom(int roomNum) {
const BAFile *f;
f = _vm->_roomsArchive->getFile(roomNum * 4);
Common::MemoryReadStream roomReader(f->_data, f->_length);
roomReader.readUint32LE(); // Pointer to room program, not used
roomReader.readUint16LE(); // Program length, not used
roomReader.readUint32LE(); // Pointer to room title, not used
_currentRoom._music = roomReader.readByte();
int mapID = roomReader.readByte() - 1;
loadWalkingMap(mapID);
_currentRoom._palette = roomReader.readByte() - 1;
_currentRoom._numOverlays = roomReader.readSint16LE();
_currentRoom._init = roomReader.readSint16LE();
_currentRoom._look = roomReader.readSint16LE();
_currentRoom._use = roomReader.readSint16LE();
_currentRoom._canUse = roomReader.readSint16LE();
_currentRoom._imInit = roomReader.readByte();
_currentRoom._imLook = roomReader.readByte();
_currentRoom._imUse = roomReader.readByte();
_currentRoom._mouseOn = roomReader.readByte();
_currentRoom._heroOn = roomReader.readByte();
// Read in pers0 and persStep (stored as 6-byte Pascal reals)
byte real[6];
for (int i = 5; i >= 0; --i) {
real[i] = roomReader.readByte();
}
_currentRoom._pers0 = real_to_double(real);
for (int i = 5; i >= 0; --i) {
real[i] = roomReader.readByte();
}
_currentRoom._persStep = real_to_double(real);
_currentRoom._escRoom = roomReader.readByte() - 1;
_currentRoom._numGates = roomReader.readByte();
debugC(4, kDraciLogicDebugLevel, "Music: %d", _currentRoom._music);
debugC(4, kDraciLogicDebugLevel, "Map: %d", mapID);
debugC(4, kDraciLogicDebugLevel, "Palette: %d", _currentRoom._palette);
debugC(4, kDraciLogicDebugLevel, "Overlays: %d", _currentRoom._numOverlays);
debugC(4, kDraciLogicDebugLevel, "Init: %d", _currentRoom._init);
debugC(4, kDraciLogicDebugLevel, "Look: %d", _currentRoom._look);
debugC(4, kDraciLogicDebugLevel, "Use: %d", _currentRoom._use);
debugC(4, kDraciLogicDebugLevel, "CanUse: %d", _currentRoom._canUse);
debugC(4, kDraciLogicDebugLevel, "ImInit: %d", _currentRoom._imInit);
debugC(4, kDraciLogicDebugLevel, "ImLook: %d", _currentRoom._imLook);
debugC(4, kDraciLogicDebugLevel, "ImUse: %d", _currentRoom._imUse);
debugC(4, kDraciLogicDebugLevel, "MouseOn: %d", _currentRoom._mouseOn);
debugC(4, kDraciLogicDebugLevel, "HeroOn: %d", _currentRoom._heroOn);
debugC(4, kDraciLogicDebugLevel, "Pers0: %f", _currentRoom._pers0);
debugC(4, kDraciLogicDebugLevel, "PersStep: %f", _currentRoom._persStep);
debugC(4, kDraciLogicDebugLevel, "EscRoom: %d", _currentRoom._escRoom);
debugC(4, kDraciLogicDebugLevel, "Gates: %d", _currentRoom._numGates);
// Read in the gates' numbers
_currentRoom._gates.clear();
for (uint i = 0; i < _currentRoom._numGates; ++i) {
_currentRoom._gates.push_back(roomReader.readSint16LE());
}
// Load the room's objects
for (uint i = 0; i < _info._numObjects; ++i) {
debugC(7, kDraciLogicDebugLevel,
"Checking if object %d (%d) is at the current location (%d)", i,
_objects[i]._location, roomNum);
if (_objects[i]._location == roomNum) {
debugC(6, kDraciLogicDebugLevel, "Loading object %d from room %d", i, roomNum);
loadObject(i);
}
}
// Run the init scripts for room objects
// We can't do this in the above loop because some objects' scripts reference
// other objects that may not yet be loaded
for (uint i = 0; i < _info._numObjects; ++i) {
if (_objects[i]._location == roomNum) {
const GameObject *obj = getObject(i);
debugC(6, kDraciLogicDebugLevel,
"Running init program for object %d (offset %d)", i, obj->_init);
_vm->_script->run(obj->_program, obj->_init);
}
}
// Load the room's GPL program and run the init part
f = _vm->_roomsArchive->getFile(roomNum * 4 + 3);
_currentRoom._program._bytecode = f->_data;
_currentRoom._program._length = f->_length;
debugC(4, kDraciLogicDebugLevel, "Running room init program...");
_vm->_script->run(_currentRoom._program, _currentRoom._init);
// Set room palette
f = _vm->_paletteArchive->getFile(_currentRoom._palette);
_vm->_screen->setPalette(f->_data, 0, kNumColours);
// HACK: Create a visible overlay from the walking map so we can test it
byte *wlk = new byte[kScreenWidth * kScreenHeight];
memset(wlk, 255, kScreenWidth * kScreenHeight);
for (uint i = 0; i < kScreenWidth; ++i) {
for (uint j = 0; j < kScreenHeight; ++j) {
if (_currentRoom._walkingMap.isWalkable(i, j)) {
wlk[j * kScreenWidth + i] = 2;
}
}
}
Sprite *ov = new Sprite(wlk, kScreenWidth, kScreenHeight, 0, 0, false);
delete[] wlk;
Animation *map = _vm->_anims->addAnimation(kWalkingMapOverlay, 255, false);
map->addFrame(ov, NULL);
}
int Game::loadAnimation(uint animNum, uint z) {
const BAFile *animFile = _vm->_animationsArchive->getFile(animNum);
Common::MemoryReadStream animationReader(animFile->_data, animFile->_length);
uint numFrames = animationReader.readByte();
// FIXME: handle these properly
animationReader.readByte(); // Memory logic field, not used
animationReader.readByte(); // Disable erasing field, not used
bool cyclic = animationReader.readByte();
animationReader.readByte(); // Relative field, not used
Animation *anim = _vm->_anims->addAnimation(animNum, z, false);
anim->setLooping(cyclic);
for (uint i = 0; i < numFrames; ++i) {
uint spriteNum = animationReader.readUint16LE() - 1;
int x = animationReader.readSint16LE();
int y = animationReader.readSint16LE();
uint scaledWidth = animationReader.readUint16LE();
uint scaledHeight = animationReader.readUint16LE();
byte mirror = animationReader.readByte();
int sample = animationReader.readUint16LE() - 1;
uint freq = animationReader.readUint16LE();
uint delay = animationReader.readUint16LE();
const BAFile *spriteFile = _vm->_spritesArchive->getFile(spriteNum);
Sprite *sp = new Sprite(spriteFile->_data, spriteFile->_length, x, y, true);
// Some frames set the scaled dimensions to 0 even though other frames
// from the same animations have them set to normal values
// We work around this by assuming it means no scaling is necessary
if (scaledWidth == 0) {
scaledWidth = sp->getWidth();
}
if (scaledHeight == 0) {
scaledHeight = sp->getHeight();
}
sp->setScaled(scaledWidth, scaledHeight);
if (mirror)
sp->setMirrorOn();
sp->setDelay(delay * 10);
const SoundSample *sam = _vm->_soundsArchive->getSample(sample, freq);
anim->addFrame(sp, sam);
}
return animNum;
}
void Game::loadObject(uint objNum) {
const BAFile *file;
file = _vm->_objectsArchive->getFile(objNum * 3);
Common::MemoryReadStream objReader(file->_data, file->_length);
GameObject *obj = _objects + objNum;
obj->_init = objReader.readUint16LE();
obj->_look = objReader.readUint16LE();
obj->_use = objReader.readUint16LE();
obj->_canUse = objReader.readUint16LE();
obj->_imInit = objReader.readByte();
obj->_imLook = objReader.readByte();
obj->_imUse = objReader.readByte();
obj->_walkDir = objReader.readByte() - 1;
obj->_z = objReader.readByte();
objReader.readUint16LE(); // idxSeq field, not used
objReader.readUint16LE(); // numSeq field, not used
obj->_lookX = objReader.readUint16LE();
obj->_lookY = objReader.readUint16LE();
obj->_useX = objReader.readUint16LE();
obj->_useY = objReader.readUint16LE();
obj->_lookDir = static_cast<SightDirection> (objReader.readByte());
obj->_useDir = static_cast<SightDirection> (objReader.readByte());
obj->_absNum = objNum;
file = _vm->_objectsArchive->getFile(objNum * 3 + 1);
// The first byte of the file is the length of the string (without the length)
assert(file->_length - 1 == file->_data[0]);
obj->_title = Common::String((char *)(file->_data+1), file->_length-1);
file = _vm->_objectsArchive->getFile(objNum * 3 + 2);
obj->_program._bytecode = file->_data;
obj->_program._length = file->_length;
}
void Game::loadWalkingMap(int mapID) {
const BAFile *f;
f = _vm->_walkingMapsArchive->getFile(mapID);
_currentRoom._walkingMap.load(f->_data, f->_length);
}
GameObject *Game::getObject(uint objNum) {
return _objects + objNum;
}
uint Game::getNumObjects() const {
return _info._numObjects;
}
void Game::loadOverlays() {
uint x, y, z, num;
const BAFile *overlayHeader;
overlayHeader = _vm->_roomsArchive->getFile(getRoomNum() * 4 + 2);
Common::MemoryReadStream overlayReader(overlayHeader->_data, overlayHeader->_length);
for (int i = 0; i < _currentRoom._numOverlays; i++) {
num = overlayReader.readUint16LE() - 1;
x = overlayReader.readUint16LE();
y = overlayReader.readUint16LE();
z = overlayReader.readByte();
const BAFile *overlayFile;
overlayFile = _vm->_overlaysArchive->getFile(num);
Sprite *sp = new Sprite(overlayFile->_data, overlayFile->_length, x, y, true);
_vm->_anims->addOverlay(sp, z);
}
_vm->_overlaysArchive->clearCache();
_vm->_screen->getSurface()->markDirty();
}
void Game::deleteObjectAnimations() {
for (uint i = 0; i < _info._numObjects; ++i) {
GameObject *obj = &_objects[i];
if (i != 0 && (obj->_location == getPreviousRoomNum())) {
for (uint j = 0; j < obj->_anim.size(); ++j) {
_vm->_anims->deleteAnimation(obj->_anim[j]);
}
obj->_anim.clear();
}
}
}
int Game::playingObjectAnimation(const GameObject *obj) const {
for (uint i = 0; i < obj->_anim.size(); ++i) {
const int animID = obj->_anim[i];
const Animation *anim = _vm->_anims->getAnimation(animID);
if (anim && anim->isPlaying()) {
return i;
}
}
return -1;
}
void Game::enterNewRoom(bool force_reload) {
if (_newRoom == getRoomNum() && !force_reload) {
return;
}
debugC(1, kDraciLogicDebugLevel, "Entering room %d using gate %d", _newRoom, _newGate);
// TODO: maybe wait till all sounds end instead of stopping them.
// In any case, make sure all sounds are stopped before we deallocate
// their memory by clearing the cache.
_vm->_sound->stopAll();
// Clear archives
_vm->_roomsArchive->clearCache();
_vm->_spritesArchive->clearCache();
_vm->_paletteArchive->clearCache();
_vm->_animationsArchive->clearCache();
_vm->_walkingMapsArchive->clearCache();
_vm->_soundsArchive->clearCache();
_vm->_dubbingArchive->clearCache();
_vm->_screen->clearScreen();
_vm->_anims->deleteOverlays();
// Delete walking map testing overlay
_vm->_anims->deleteAnimation(kWalkingMapOverlay);
// TODO: Make objects capable of stopping their own animations
const GameObject *dragon = getObject(kDragonObject);
stopObjectAnimations(dragon);
// Remember the previous room for returning back from the map.
rememberRoomNumAsPrevious();
deleteObjectAnimations();
// Set the current room to the new value
_currentRoom._roomNum = _newRoom;
// Before setting these variables we have to convert the values to 1-based indexing
// because this is how everything is stored in the data files
_variables[0] = _newGate + 1;
_variables[1] = _newRoom + 1;
// If the new room is the map room, set the appropriate coordinates
// for the dragon in the persons array
if (_newRoom == _info._mapRoom) {
_persons[kDragonObject]._x = 160;
_persons[kDragonObject]._y = 0;
}
// Set the appropriate loop statu before loading the room
setLoopStatus(kStatusGate);
setLoopSubstatus(kSubstatusOrdinary);
loadRoom(_newRoom);
loadOverlays();
// Run the program for the gate the dragon came through
runGateProgram(_newGate);
// Set cursor state
// Need to do this after we set the palette since the cursors use it
if (_currentRoom._mouseOn) {
debugC(6, kDraciLogicDebugLevel, "Mouse: ON");
_vm->_mouse->cursorOn();
} else {
debugC(6, kDraciLogicDebugLevel, "Mouse: OFF");
_vm->_mouse->cursorOff();
}
// Reset the loop status.
setLoopStatus(kStatusOrdinary);
}
void Game::runGateProgram(int gate) {
debugC(6, kDraciLogicDebugLevel, "Running program for gate %d", gate);
// Mark last animation
int lastAnimIndex = _vm->_anims->getLastIndex();
// Run gate program
_vm->_script->run(_currentRoom._program, _currentRoom._gates[gate]);
deleteAnimationsAfterIndex(lastAnimIndex);
setExitLoop(false);
}
void Game::positionAnimAsHero(Animation *anim) {
// Calculate scaling factors
const double scale = getPers0() + getPersStep() * _hero.y;
// Set the Z coordinate for the dragon's animation
anim->setZ(_hero.y + 1);
// Fetch current frame
Drawable *frame = anim->getFrame();
// Fetch base dimensions of the frame
uint height = frame->getHeight();
uint width = frame->getWidth();
// We naturally want the dragon to position its feet to the location of the
// click but sprites are drawn from their top-left corner so we subtract
// the current height of the dragon's sprite
Common::Point p = _hero;
p.x -= (int)(scale * width) / 2;
p.y -= (int)(scale * height);
// Since _persons[] is used for placing talking text, we use the non-adjusted x value
// so the text remains centered over the dragon.
_persons[kDragonObject]._x = _hero.x;
_persons[kDragonObject]._y = p.y;
// Set the per-animation scaling factor
anim->setScaleFactors(scale, scale);
anim->setRelative(p.x, p.y);
}
int Game::getHeroX() const {
return _hero.x;
}
int Game::getHeroY() const {
return _hero.y;
}
double Game::getPers0() const {
return _currentRoom._pers0;
}
double Game::getPersStep() const {
return _currentRoom._persStep;
}
int Game::getRoomNum() const {
return _currentRoom._roomNum;
}
void Game::setRoomNum(int num) {
_currentRoom._roomNum = num;
}
int Game::getPreviousRoomNum() const {
return _previousRoom;
}
void Game::rememberRoomNumAsPrevious() {
_previousRoom = getRoomNum();
}
void Game::scheduleEnteringRoomUsingGate(int room, int gate) {
_newRoom = room;
_newGate = gate;
}
void Game::pushNewRoom() {
_pushedNewRoom = _newRoom;
_pushedNewGate = _newGate;
}
void Game::popNewRoom() {
if (_loopStatus != kStatusInventory && _pushedNewRoom >= 0) {
scheduleEnteringRoomUsingGate(_pushedNewRoom, _pushedNewGate);
_pushedNewRoom = _pushedNewGate = -1;
}
}
void Game::setLoopStatus(LoopStatus status) {
_loopStatus = status;
}
void Game::setLoopSubstatus(LoopSubstatus status) {
_loopSubstatus = status;
}
LoopStatus Game::getLoopStatus() const {
return _loopStatus;
}
LoopSubstatus Game::getLoopSubstatus() const {
return _loopSubstatus;
}
int Game::getVariable(int numVar) const {
return _variables[numVar];
}
void Game::setVariable(int numVar, int value) {
_variables[numVar] = value;
}
int Game::getItemStatus(int itemID) const {
return _itemStatus[itemID];
}
void Game::setItemStatus(int itemID, int status) {
_itemStatus[itemID] = status;
}
int Game::getCurrentItem() const {
return _currentItem;
}
void Game::setCurrentItem(int itemID) {
_currentItem = itemID;
}
const Person *Game::getPerson(int personID) const {
return &_persons[personID];
}
void Game::setSpeechTiming(uint tick, uint duration) {
_speechTick = tick;
_speechDuration = duration;
}
void Game::shiftSpeechTick(int delta) {
_speechTick += delta;
}
int Game::getEscRoom() const {
return _currentRoom._escRoom;
}
int Game::getMapRoom() const {
return _info._mapRoom;
}
void Game::schedulePalette(int paletteID) {
_scheduledPalette = paletteID;
}
int Game::getScheduledPalette() const {
return _scheduledPalette;
}
/**
* The GPL command Mark sets the animation index (which specifies the order in which
* animations were loaded in) which is then used by the Release command to delete
* all animations that have an index greater than the one marked.
*/
int Game::getMarkedAnimationIndex() const {
return _markedAnimationIndex;
}
void Game::deleteAnimationsAfterIndex(int lastAnimIndex) {
// Delete all animations loaded after the marked one
// (from objects and from the AnimationManager)
for (uint i = 0; i < getNumObjects(); ++i) {
GameObject *obj = &_objects[i];
for (uint j = 0; j < obj->_anim.size(); ++j) {
Animation *anim;
anim = _vm->_anims->getAnimation(obj->_anim[j]);
if (anim != NULL && anim->getIndex() > lastAnimIndex)
obj->_anim.remove_at(j--);
}
}
_vm->_anims->deleteAfterIndex(lastAnimIndex);
}
void Game::stopObjectAnimations(const GameObject *obj) {
for (uint i = 0; i < obj->_anim.size(); ++i) {
_vm->_anims->stop(obj->_anim[i]);
}
}
/**
* See Game::getMarkedAnimationIndex().
*/
void Game::setMarkedAnimationIndex(int index) {
_markedAnimationIndex = index;
}
Game::~Game() {
delete[] _persons;
delete[] _variables;
delete[] _dialogueOffsets;
delete[] _dialogueVars;
delete[] _objects;
delete[] _itemStatus;
delete[] _items;
}
void Game::DoSync(Common::Serializer &s) {
s.syncAsUint16LE(_currentRoom._roomNum);
for (uint i = 0; i < _info._numObjects; ++i) {
GameObject& obj = _objects[i];
s.syncAsSint16LE(obj._location);
s.syncAsByte(obj._visible);
}
for (uint i = 0; i < _info._numItems; ++i) {
s.syncAsByte(_itemStatus[i]);
}
for (int i = 0; i < kInventorySlots; ++i) {
s.syncAsSint16LE(_inventory[i]);
}
for (int i = 0; i < _info._numVariables; ++i) {
s.syncAsSint16LE(_variables[i]);
}
for (uint i = 0; i < _info._numDialogueBlocks; ++i) {
s.syncAsSint16LE(_dialogueVars[i]);
}
}
bool WalkingMap::isWalkable(int x, int y) const {
// Convert to map pixels
x = x / _deltaX;
y = y / _deltaY;
int pixelIndex = _mapWidth * y + x;
int byteIndex = pixelIndex / 8;
int mapByte = _data[byteIndex];
return mapByte & (1 << pixelIndex % 8);
}
/**
* @brief For a given point, find a nearest walkable point on the walking map
*
* @param startX x coordinate of the point
* @param startY y coordinate of the point
*
* @return A Common::Point representing the nearest walkable point
*
* The algorithm was copied from the original engine for exactness.
* TODO: Study this algorithm in more detail so it can be documented properly and
* possibly improved / simplified.
*/
Common::Point WalkingMap::findNearestWalkable(int startX, int startY, Common::Rect searchRect) const {
// If the starting point is walkable, just return that
if (searchRect.contains(startX, startY) && isWalkable(startX, startY)) {
return Common::Point(startX, startY);
}
int signs[] = { 1, -1 };
const uint kSignsNum = 2;
int radius = 0;
int x, y;
int dx, dy;
int prediction;
// The place where, eventually, the result coordinates will be stored
int finalX, finalY;
// The algorithm appears to start off with an ellipse with the minor radius equal to
// zero and the major radius equal to the walking map delta (the number of pixels
// one map pixel represents). It then uses a heuristic to gradually reshape it into
// a circle (by shortening the major radius and lengthening the minor one). At each
// such resizing step, it checks some select points on the ellipse for walkability.
// It also does the same check for the ellipse perpendicular to it (rotated by 90 degrees).
while (1) {
// The default major radius
radius += _deltaX;
// The ellipse radii (minor, major) that get resized
x = 0;
y = radius;
// Heuristic variables
prediction = 1 - radius;
dx = 3;
dy = 2 * radius - 2;
do {
// The following two loops serve the purpose of checking the points on the two
// ellipses for walkability. The signs[] array is there to obliterate the need
// of writing out all combinations manually.
for (uint i = 0; i < kSignsNum; ++i) {
finalY = startY + y * signs[i];
for (uint j = 0; j < kSignsNum; ++j) {
finalX = startX + x * signs[j];
// If the current point is walkable, return it
if (searchRect.contains(finalX, finalY) && isWalkable(finalX, finalY)) {
return Common::Point(finalX, finalY);
}
}
}
if (x == y) {
// If the starting point is walkable, just return that
if (searchRect.contains(finalX, finalY) && isWalkable(finalX, finalY)) {
return Common::Point(finalX, finalY);
}
}
for (uint i = 0; i < kSignsNum; ++i) {
finalY = startY + x * signs[i];
for (uint j = 0; j < kSignsNum; ++j) {
finalX = startX + y * signs[j];
// If the current point is walkable, return it
if (searchRect.contains(finalX, finalY) && isWalkable(finalX, finalY)) {
return Common::Point(finalX, finalY);
}
}
}
// If prediction is non-negative, we need to decrease the major radius of the
// ellipse
if (prediction >= 0) {
prediction -= dy;
dy -= 2 * _deltaX;
y -= _deltaX;
}
// Increase the minor radius of the ellipse and update heuristic variables
prediction += dx;
dx += 2 * _deltaX;
x += _deltaX;
// If the current ellipse has been reshaped into a circle,
// end this loop and enlarge the radius
} while (x <= y);
}
}
static double real_to_double(byte real[6]) {
// Extract sign bit
int sign = real[0] & (1 << 7);
// Extract exponent and adjust for bias
int exp = real[5] - 129;
double mantissa;
double tmp = 0.0;
if (real[5] == 0) {
mantissa = 0.0;
} else {
// Process the first four least significant bytes
for (int i = 4; i >= 1; --i) {
tmp += real[i];
tmp /= 1 << 8;
}
// Process the most significant byte (remove the sign bit)
tmp += real[0] & ((1 << 7) - 1);
tmp /= 1 << 8;
// Calculate mantissa
mantissa = 1.0;
mantissa += 2.0 * tmp;
}
// Flip sign if necessary
if (sign) {
mantissa = -mantissa;
}
// Calculate final value
return ldexp(mantissa, exp);
}
}