scummvm/engines/draci/game.cpp
Robert Špalek 354d7f6366 Debugged smooth walking except for 1 bug.
Adjusting to the edge is done such that it respects slight sideways movements of the dragon.
Fixed rounding issues in the whole game.  Improved debug messages.  Made sure that the dragon
does not turn like crazy around when clicking on the same pixel: the final point is always the
clicked one although the middle points made by shifted to make the animations smooth, and
preserve the dragons direction if he has not walked.

There is a bug with running turning animations as they seem to disappear for 1 frame and have
incorrect Z coordinate.  Will investigate it next.

svn-id: r45742
2009-11-08 03:16:22 +00:00

1642 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]);
enum {
kWalkingMapOverlayColour = 2,
kWalkingShortestPathOverlayColour = 120,
kWalkingObliquePathOverlayColour = 73
};
Game::Game(DraciEngine *vm) : _vm(vm), _walkingState(vm) {
uint i;
BArchive *initArchive = _vm->_initArchive;
const BAFile *file;
// Read in persons
file = initArchive->getFile(5);
Common::MemoryReadStream personData(file->_data, file->_length);
const int personSize = sizeof(uint16) * 2 + sizeof(byte);
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()) {
if (enterNewRoom()) {
// Call the outer loop doing all the hard job.
loop(kOuterLoop, false);
}
}
}
void Game::init() {
setQuit(false);
setExitLoop(false);
setIsReloaded(false);
_scheduledPalette = 0;
_fadePhases = _fadePhase = 0;
setEnableQuickHero(true);
setWantQuickHero(false);
setEnableSpeedText(true);
setLoopStatus(kStatusGate);
setLoopSubstatus(kOuterLoop);
_animUnderCursor = kOverlayImage;
_currentItem = kNoItem;
_itemUnderCursor = kNoItem;
_vm->_mouse->setCursorType(kHighlightedCursor); // anything different from kNormalCursor
_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, 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, 0);
speechAnim->addFrame(speech, NULL);
// Initialize inventory animation. _iconsArchive is never flushed.
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, 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]->getCurrentFrame());
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::handleOrdinaryLoop(int x, int y) {
// During the normal game-play, in particular not when
// running the init-scripts, enable interactivity.
if (_loopSubstatus != kOuterLoop) {
return;
}
if (_vm->_mouse->lButtonPressed()) {
_vm->_mouse->lButtonSet(false);
if (_currentItem != kNoItem) {
putItem(_currentItem, 0);
_currentItem = kNoItem;
updateOrdinaryCursor();
} else {
if (_objUnderCursor != kObjectNotFound) {
const GameObject *obj = &_objects[_objUnderCursor];
_walkingState.setCallback(&obj->_program, obj->_look);
if (obj->_imLook || !_currentRoom._heroOn) {
_walkingState.callback();
} else {
if (obj->_lookDir == kDirectionLast) {
walkHero(x, y, obj->_lookDir);
} else {
walkHero(obj->_lookX, obj->_lookY, obj->_lookDir);
}
}
} else {
_walkingState.setCallback(NULL, 0);
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)) {
_walkingState.setCallback(&obj->_program, obj->_use);
if (obj->_imUse || !_currentRoom._heroOn) {
_walkingState.callback();
} else {
if (obj->_useDir == kDirectionLast) {
walkHero(x, y, obj->_useDir);
} else {
walkHero(obj->_useX, obj->_useY, obj->_useDir);
}
}
} else {
_walkingState.setCallback(NULL, 0);
walkHero(x, y, kDirectionLast);
}
} else {
if (_vm->_script->testExpression(_currentRoom._program, _currentRoom._canUse)) {
_walkingState.setCallback(&_currentRoom._program, _currentRoom._use);
_walkingState.callback();
} else {
_walkingState.setCallback(NULL, 0);
walkHero(x, y, kDirectionLast);
}
}
}
}
void Game::handleInventoryLoop() {
if (_loopSubstatus != kOuterLoop) {
return;
}
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);
}
}
updateInventoryCursor();
}
}
}
void Game::handleDialogueLoop() {
if (_loopSubstatus != kInnerDuringDialogue) {
return;
}
Text *text;
for (int i = 0; i < kDialogueLines; ++i) {
text = reinterpret_cast<Text *>(_dialogueAnims[i]->getCurrentFrame());
if (_animUnderCursor == _dialogueAnims[i]->getID()) {
text->setColour(kLineActiveColour);
} else {
text->setColour(kLineInactiveColour);
}
}
if (_vm->_mouse->lButtonPressed() || _vm->_mouse->rButtonPressed()) {
setExitLoop(true);
_vm->_mouse->lButtonSet(false);
_vm->_mouse->rButtonSet(false);
}
}
void Game::advanceAnimationsAndTestLoopExit() {
// Fade the palette if requested
if (_fadePhase > 0 && (_vm->_system->getMillis() - _fadeTick) >= kFadingTimeUnit) {
_fadeTick = _vm->_system->getMillis();
--_fadePhase;
const byte *startPal = _currentRoom._palette >= 0 ? _vm->_paletteArchive->getFile(_currentRoom._palette)->_data : NULL;
const byte *endPal = getScheduledPalette() >= 0 ? _vm->_paletteArchive->getFile(getScheduledPalette())->_data : NULL;
_vm->_screen->interpolatePalettes(startPal, endPal, 0, kNumColours, _fadePhases - _fadePhase, _fadePhases);
if (_fadePhase == 0) {
if (_loopSubstatus == kInnerWhileFade) {
setExitLoop(true);
}
// Rewrite the palette index of the current room. This
// is necessary when two fadings are called after each
// other, such as in the intro.
_currentRoom._palette = getScheduledPalette();
}
}
// Handle character talking (if there is any)
if (_loopSubstatus == kInnerWhileTalk) {
// If the current speech text has expired or the user clicked a mouse button,
// advance to the next line of text
if ((getEnableSpeedText() && (_vm->_mouse->lButtonPressed() || _vm->_mouse->rButtonPressed())) ||
(_vm->_system->getMillis() - _speechTick) >= _speechDuration) {
setExitLoop(true);
}
_vm->_mouse->lButtonSet(false);
_vm->_mouse->rButtonSet(false);
}
// A script has scheduled changing the room (either triggered
// by the user clicking on something or run at the end of a
// gate script in the intro).
if ((_loopStatus == kStatusOrdinary || _loopStatus == kStatusGate) && _newRoom != getRoomNum()) {
setExitLoop(true);
}
// This returns true if we got a signal to quit the game
if (shouldQuit()) {
setExitLoop(true);
}
// Walk the hero. The WalkingState class handles everything including
// proper timing.
bool walkingFinished = false;
if (_walkingState.isActive()) {
walkingFinished = !_walkingState.continueWalking();
}
// Advance animations (this may also call setExitLoop(true) in the
// callbacks) and redraw screen
_vm->_anims->drawScene(_vm->_screen->getSurface());
_vm->_screen->copyToScreen();
_vm->_system->delayMillis(20);
// If the hero has arrived at his destination, after even the last
// phase was correctly animated, run the callback.
if (walkingFinished) {
bool exitLoop = false;
if (_loopSubstatus == kInnerUntilExit) {
// The callback may run another inner loop (for
// example, a dialogue). Reset the loop
// substatus temporarily to the outer one.
exitLoop = true;
setLoopSubstatus(kOuterLoop);
}
debugC(2, kDraciWalkingDebugLevel, "Finished walking");
_walkingState.callback(); // clears callback pointer first
if (exitLoop) {
debugC(3, kDraciWalkingDebugLevel, "Exiting from the inner loop");
setExitLoop(true);
setLoopSubstatus(kInnerUntilExit);
}
}
}
void Game::loop(LoopSubstatus substatus, bool shouldExit) {
// Can run both as an outer and inner loop. In both mode it updates
// the screen according to the timer. It the outer mode (kOuterLoop)
// it also reacts to user events. In the inner mode (all kInner*
// enums), the loop runs until its stopping condition, possibly
// stopping earlier if the user interrupts it, however no other user
// intervention is allowed.
assert(getLoopSubstatus() == kOuterLoop);
setLoopSubstatus(substatus);
setExitLoop(shouldExit);
// Always enter the first pass of the loop, even if shouldExitLoop() is
// true, exactly to ensure to make at least one pass.
do {
debugC(4, kDraciLogicDebugLevel, "loopstatus: %d, loopsubstatus: %d",
_loopStatus, _loopSubstatus);
_vm->handleEvents();
if (isReloaded()) {
// Cannot continue with the same animation objects,
// because the real data structures of the game have
// completely been changed.
break;
}
if (_vm->_mouse->isCursorOn()) {
// Find animation under cursor and the game object
// corresponding to it
int x = _vm->_mouse->getPosX();
int y = _vm->_mouse->getPosY();
_animUnderCursor = _vm->_anims->getTopAnimationID(x, y);
_objUnderCursor = getObjectWithAnimation(_animUnderCursor);
debugC(5, kDraciLogicDebugLevel, "Anim under cursor: %d", _animUnderCursor);
switch (_loopStatus) {
case kStatusOrdinary:
updateOrdinaryCursor();
updateTitle(x, y);
handleOrdinaryLoop(x, y);
break;
case kStatusInventory:
updateInventoryCursor();
updateTitle(x, y);
handleInventoryLoop();
break;
case kStatusDialogue:
handleDialogueLoop();
break;
case kStatusGate: ;
// cannot happen when isCursonOn; added for completeness
}
// TODO: Handle main menu
}
advanceAnimationsAndTestLoopExit();
} while (!shouldExitLoop());
setLoopSubstatus(kOuterLoop);
setExitLoop(false);
}
void Game::updateOrdinaryCursor() {
// Fetch mouse coordinates
bool mouseChanged = false;
// 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);
}
mouseChanged = 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);
}
mouseChanged = 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);
mouseChanged = true;
}
}
// Load the appropriate cursor (item image if an item is held or ordinary cursor
// if not)
if (!mouseChanged) {
if (_currentItem == kNoItem) {
_vm->_mouse->setCursorType(kNormalCursor);
} else {
_vm->_mouse->loadItemCursor(_currentItem, false);
}
}
}
void Game::updateInventoryCursor() {
// Fetch mouse coordinates
bool mouseChanged = false;
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);
}
mouseChanged = true;
}
}
if (!mouseChanged) {
if (_currentItem == kNoItem) {
_vm->_mouse->setCursorType(kNormalCursor);
} else {
_vm->_mouse->loadItemCursor(_currentItem, false);
}
}
}
void Game::updateTitle(int x, int y) {
// Fetch current surface and height of the small font (used for titles)
Surface *surface = _vm->_screen->getSurface();
const int smallFontHeight = _vm->_smallFont->getFontHeight();
// Fetch the dedicated objects' title animation / current frame
Animation *titleAnim = _vm->_anims->getAnimation(kTitleText);
Text *title = reinterpret_cast<Text *>(titleAnim->getCurrentFrame());
// 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, false);
// _itemImagesArchive is never flushed.
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->getCurrentFrame();
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 == kOuterLoop) {
_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;
}
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->getCurrentFrame());
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->getCurrentFrame());
dialogueLine->setText("");
}
if (_dialogueLinesNum > 1) {
// Call the game loop to enable interactivity until the user
// selects his choice. _animUnderCursor will be set.
_vm->_mouse->cursorOn();
loop(kInnerDuringDialogue, false);
_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]->getCurrentFrame());
_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);
}
int Game::playHeroAnimation(int anim_index) {
const GameObject *dragon = getObject(kDragonObject);
const int current_anim_index = playingObjectAnimation(dragon);
const int animID = dragon->_anim[anim_index];
Animation *anim = _vm->_anims->getAnimation(animID);
if (anim_index == current_anim_index) {
anim->markDirtyRect(_vm->_screen->getSurface());
} else {
stopObjectAnimations(dragon);
}
positionAnimAsHero(anim);
if (anim_index == current_anim_index) {
anim->markDirtyRect(_vm->_screen->getSurface());
} else {
_vm->_anims->play(animID);
}
return anim->currentFrameNum();
}
void Game::redrawWalkingPath(int id, byte colour, const WalkingPath &path) {
Animation *anim = _vm->_anims->getAnimation(id);
Sprite *ov = _walkingMap.newOverlayFromPath(path, colour);
delete anim->getFrame(0);
anim->replaceFrame(0, ov, NULL);
anim->markDirtyRect(_vm->_screen->getSurface());
}
void Game::setHeroPosition(const Common::Point &p) {
debugC(3, kDraciWalkingDebugLevel, "Jump to x: %d y: %d", p.x, p.y);
_hero = p;
}
Common::Point Game::findNearestWalkable(int x, int y) const {
Surface *surface = _vm->_screen->getSurface();
return _walkingMap.findNearestWalkable(x, y, surface->getDimensions());
}
void Game::walkHero(int x, int y, SightDirection dir) {
if (!_currentRoom._heroOn) {
// Nothing to do. Happens for example in the map.
return;
}
Common::Point target = findNearestWalkable(x, y);
// Compute the shortest and obliqued path.
WalkingPath shortestPath, obliquePath;
_walkingMap.findShortestPath(_hero, target, &shortestPath);
// TODO: test reachability and react
_walkingMap.obliquePath(shortestPath, &obliquePath);
debugC(2, kDraciWalkingDebugLevel, "Walking path lengths: shortest=%d oblique=%d", shortestPath.size(), obliquePath.size());
if (_vm->_showWalkingMap) {
redrawWalkingPath(kWalkingShortestPathOverlay, kWalkingShortestPathOverlayColour, shortestPath);
redrawWalkingPath(kWalkingObliquePathOverlay, kWalkingObliquePathOverlayColour, obliquePath);
}
// Start walking. Walking will be gradually advanced by
// advanceAnimationsAndTestLoopExit(), which also handles calling the
// callback and stopping the walk at the end. If the hero is already
// walking at this point, this command will cancel the previous path
// and replace it by the current one (the callback has already been
// reset by our caller).
_walkingState.startWalking(_hero, target, Common::Point(x, y), dir,
_walkingMap.getDelta(), obliquePath);
}
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
// Music will be played by the GPL2 command startMusic when needed.
setMusicTrack(roomReader.readByte());
_currentRoom._mapID = roomReader.readByte() - 1;
_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", getMusicTrack());
debugC(4, kDraciLogicDebugLevel, "Map: %d", getMapID());
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());
}
// Add overlays for the walking map and shortest/obliqued paths.
Animation *map = _vm->_anims->addAnimation(kWalkingMapOverlay, 256, _vm->_showWalkingMap);
map->addFrame(NULL, NULL); // rewritten below by loadWalkingMap()
Animation *sPath = _vm->_anims->addAnimation(kWalkingShortestPathOverlay, 257, _vm->_showWalkingMap);
Animation *oPath = _vm->_anims->addAnimation(kWalkingObliquePathOverlay, 258, _vm->_showWalkingMap);
WalkingPath emptyPath;
Sprite *ov = _walkingMap.newOverlayFromPath(emptyPath, 0);
sPath->addFrame(ov, NULL);
ov = _walkingMap.newOverlayFromPath(emptyPath, 0);
oPath->addFrame(ov, NULL);
// Load the walking map
loadWalkingMap(getMapID());
// 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);
}
int Game::loadAnimation(uint animNum, uint z) {
// Make double-sure that an animation isn't loaded more than twice,
// otherwise horrible things happen in the AnimationManager, because
// they use a simple link-list without duplicate checking. This should
// never happen unless there is a bug in the game, because all GPL2
// commands are guarded.
assert(!_vm->_anims->getAnimation(animNum));
const BAFile *animFile = _vm->_animationsArchive->getFile(animNum);
Common::MemoryReadStream animationReader(animFile->_data, animFile->_length);
uint numFrames = animationReader.readByte();
// The following two flags are ignored by the played. Memory logic was
// a hint to the old player whether it should cache the sprites or load
// them on demand. We have 1 memory manager and ignore these hints.
animationReader.readByte();
// The disable erasing field is just a (poor) optimization flag that
// turns of drawing the background underneath the sprite. By reading
// the source code of the old player, I'm not sure if that would ever
// have worked. There are only 6 animations in the game with this flag
// true. All of them have just 1 animation phase and they are used to
// patch a part of the original background by a new sprite. This
// should work with the default logic as well---just play this
// animation on top of the background. Since the only meaning of the
// flag was optimization, ignoring should be OK even without dipping
// into details.
animationReader.readByte();
const bool cyclic = animationReader.readByte();
const bool relative = animationReader.readByte();
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();
// _spritesArchive is flushed when entering a room. All
// scripts in a room are responsible for loading their animations.
const BAFile *spriteFile = _vm->_spritesArchive->getFile(spriteNum);
Sprite *sp = new Sprite(spriteFile->_data, spriteFile->_length,
relative ? 0 : x, relative ? 0 : 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);
if (relative) {
anim->makeLastFrameRelative(x, y);
}
}
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);
_walkingMap.load(f->_data, f->_length);
Animation *anim = _vm->_anims->getAnimation(kWalkingMapOverlay);
Sprite *ov = _walkingMap.newOverlayFromMap(kWalkingMapOverlayColour);
delete anim->getFrame(0);
anim->replaceFrame(0, ov, NULL);
anim->markDirtyRect(_vm->_screen->getSurface());
}
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();
// _overlaysArchive is flushed when entering a room and this
// code is called after the flushing has been done.
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->_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;
}
bool Game::enterNewRoom() {
if (_newRoom == getRoomNum() && !isReloaded()) {
// If the game has been reloaded, force reloading all animations.
return true;
}
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->_overlaysArchive->clearCache();
_vm->_screen->clearScreen();
_vm->_anims->deleteOverlays();
// Delete walking map testing overlay
_vm->_anims->deleteAnimation(kWalkingMapOverlay);
_vm->_anims->deleteAnimation(kWalkingShortestPathOverlay);
_vm->_anims->deleteAnimation(kWalkingObliquePathOverlay);
// 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 status before loading the room
setLoopStatus(kStatusGate);
// Make sure the possible walking path from the previous room is
// cleaned up. Some rooms (e.g., the map) don't support walking.
_walkingState.stopWalking();
// Clean the mouse and animation title. It gets first updated in
// loop(), hence if the hero walks during the initialization scripts,
// the old values would remain otherwise.
_vm->_mouse->setCursorType(kNormalCursor);
Animation *titleAnim = _vm->_anims->getAnimation(kTitleText);
titleAnim->markDirtyRect(_vm->_screen->getSurface());
Text *title = reinterpret_cast<Text *>(titleAnim->getCurrentFrame());
title->setText("");
// Reset the flag allowing to run the scripts. It may have been turned
// on by pressing Escape in the intro or in the map room.
_vm->_script->endCurrentProgram(false);
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);
setIsReloaded(false);
if (_vm->_script->shouldEndProgram()) {
// Escape pressed during the intro or map animations run in the
// init scripts. This flag was turned on to skip the rest of
// those programs. Return false to make start() rerun us from
// the beginning, because the room number has changed.
return false;
}
return true;
}
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->getCurrentFrame();
// 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 -= scummvm_lround(scale * frame->getWidth() / 2);
p.y -= scummvm_lround(scale * frame->getHeight());
// 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);
// Clear the animation's shift so that the real sprite stays at place
// regardless of what the current phase is. If the animation starts
// from the beginning, the shift is already [0,0], but if it is in the
// middle, it may be different.
anim->clearShift();
}
void Game::positionHeroAsAnim(Animation *anim) {
// Check out where the hero has moved to by composing the relative
// shifts of the sprites.
_hero = anim->getCurrentFramePosition();
// Update our hero coordinates (don't forget that our control point is
// elsewhere).
// TODO: what about rounding errors?
Drawable *frame = anim->getCurrentFrame();
_hero.x += scummvm_lround(anim->getScaleX() * frame->getWidth() / 2);
_hero.y += scummvm_lround(anim->getScaleY() * frame->getHeight());
}
void Game::pushNewRoom() {
_pushedNewRoom = _newRoom;
_pushedNewGate = _newGate;
}
void Game::popNewRoom() {
if (_loopStatus != kStatusInventory && _pushedNewRoom >= 0) {
scheduleEnteringRoomUsingGate(_pushedNewRoom, _pushedNewGate);
_pushedNewRoom = _pushedNewGate = -1;
}
}
void Game::setSpeechTiming(uint tick, uint duration) {
_speechTick = tick;
_speechDuration = duration;
}
void Game::shiftSpeechAndFadeTick(int delta) {
_speechTick += delta;
_fadeTick += delta;
}
void Game::initializeFading(int phases) {
_fadePhases = _fadePhase = phases;
_fadeTick = _vm->_system->getMillis();
}
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]);
}
}
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]);
}
}
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);
}
}