diff options
Diffstat (limited to 'engines/draci/game.cpp')
-rw-r--r-- | engines/draci/game.cpp | 461 |
1 files changed, 379 insertions, 82 deletions
diff --git a/engines/draci/game.cpp b/engines/draci/game.cpp index 5d78011e8d..d641809dc2 100644 --- a/engines/draci/game.cpp +++ b/engines/draci/game.cpp @@ -40,7 +40,6 @@ const Common::String dialoguePath("ROZH"); static double real_to_double(byte real[6]); Game::Game(DraciEngine *vm) : _vm(vm) { - unsigned int i; BArchive *initArchive = _vm->_initArchive; @@ -91,12 +90,12 @@ Game::Game(DraciEngine *vm) : _vm(vm) { _info._startRoom = gameData.readByte() - 1; _info._mapRoom = gameData.readByte() - 1; _info._numObjects = gameData.readUint16LE(); - _info._numIcons = gameData.readUint16LE(); + _info._numItems = gameData.readUint16LE(); _info._numVariables = gameData.readByte(); _info._numPersons = gameData.readByte(); _info._numDialogues = gameData.readByte(); - _info._maxIconWidth = gameData.readUint16LE(); - _info._maxIconHeight = gameData.readUint16LE(); + _info._maxItemWidth = gameData.readUint16LE(); + _info._maxItemHeight = gameData.readUint16LE(); _info._musicLength = gameData.readUint16LE(); _info._crc[0] = gameData.readUint16LE(); _info._crc[1] = gameData.readUint16LE(); @@ -126,8 +125,9 @@ Game::Game(DraciEngine *vm) : _vm(vm) { // Read in item icon status file = initArchive->getFile(1); - _iconStatus = file->_data; - uint numIcons = file->_length; + _itemStatus = file->_data; + uint numItems = file->_length; + _items = new GameItem[numItems]; // Read in object status @@ -154,7 +154,7 @@ Game::Game(DraciEngine *vm) : _vm(vm) { assert(numPersons == _info._numPersons); assert(numVariables == _info._numVariables); assert(numObjects == _info._numObjects); - assert(numIcons == _info._numIcons); + assert(numItems == _info._numItems); } void Game::start() { @@ -218,13 +218,17 @@ void Game::init() { _animUnderCursor = kOverlayImage; - _currentIcon = kNoIcon; + _currentItem = kNoItem; + _itemUnderCursor = kNoItem; _vm->_mouse->setCursorType(kNormalCursor); _loopStatus = kStatusOrdinary; _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); @@ -235,8 +239,16 @@ void Game::init() { Text *speech = new Text("", _vm->_bigFont, kFontColour1, 0, 0); speechAnim->addFrame(speech); + // Initialize inventory animation + 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); + inventoryAnim->setRelative((kScreenWidth - inventorySprite->getWidth()) / 2, + (kScreenHeight - inventorySprite->getHeight()) / 2); + for (uint i = 0; i < kDialogueLines; ++i) { - _dialogueAnims[i] = _vm->_anims->addText(-10 - i, true); + _dialogueAnims[i] = _vm->_anims->addText(kDialogueLinesID - i, true); Text *dialogueLine = new Text("", _vm->_smallFont, kLineInactiveColour, 0, 0); _dialogueAnims[i]->addFrame(dialogueLine); @@ -248,6 +260,10 @@ void Game::init() { text->setText(""); } + for (uint i = 0; i < _info._numItems; ++i) { + loadItem(i); + } + loadObject(kDragonObject); GameObject *dragon = getObject(kDragonObject); @@ -273,11 +289,11 @@ void Game::loop() { Surface *surface = _vm->_screen->getSurface(); - debugC(6, kDraciLogicDebugLevel, "loopstatus: %d, loopsubstatus: %d", - _loopStatus, _loopSubstatus); - do { + debugC(4, kDraciLogicDebugLevel, "loopstatus: %d, loopsubstatus: %d", + _loopStatus, _loopSubstatus); + _vm->handleEvents(); // Fetch mouse coordinates @@ -286,9 +302,6 @@ void Game::loop() { if (_loopStatus == kStatusDialogue && _loopSubstatus == kSubstatusOrdinary) { - // Find animation under cursor - _animUnderCursor = _vm->_anims->getTopAnimationID(x, y); - Text *text; for (int i = 0; i < kDialogueLines; ++i) { text = reinterpret_cast<Text *>(_dialogueAnims[i]->getFrame()); @@ -307,33 +320,28 @@ void Game::loop() { } } - if (_currentRoom._mouseOn) { + 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()); - if (_loopStatus == kStatusOrdinary && _loopSubstatus == kSubstatusOrdinary) { - if(_vm->_mouse->isCursorOn()) { - // Find the game object under the cursor - // (to be more precise, one that corresponds to the animation under the cursor) - _animUnderCursor = _vm->_anims->getTopAnimationID(x, y); - int curObject = getObjectWithAnimation(_animUnderCursor); + updateCursor(); + updateTitle(); - updateTitle(); + if (_loopStatus == kStatusOrdinary && _loopSubstatus == kSubstatusOrdinary) { - _objUnderCursor = curObject; - if (_objUnderCursor != _oldObjUnderCursor) { - _oldObjUnderCursor = _objUnderCursor; + if (_vm->_mouse->lButtonPressed()) { + _vm->_mouse->lButtonSet(false); + + if (_currentItem != kNoItem) { + putItem(_currentItem, 0); + _currentItem = kNoItem; updateCursor(); - } - - if (_vm->_mouse->lButtonPressed()) { - _vm->_mouse->lButtonSet(false); - + } else { if (_objUnderCursor != kObjectNotFound) { GameObject *obj = &_objects[_objUnderCursor]; - + _vm->_mouse->cursorOff(); titleAnim->markDirtyRect(surface); title->setText(""); @@ -353,51 +361,128 @@ void Game::loop() { walkHero(x, y); } } + } - if (_vm->_mouse->rButtonPressed()) { - _vm->_mouse->rButtonSet(false); + if (_vm->_mouse->rButtonPressed()) { + _vm->_mouse->rButtonSet(false); - if (_objUnderCursor != kObjectNotFound) { - GameObject *obj = &_objects[_objUnderCursor]; + if (_objUnderCursor != kObjectNotFound) { + 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 == -1) { - walkHero(x, y); - } else { - walkHero(obj->_useX, obj->_useY); - } - } + if (_vm->_script->testExpression(obj->_program, obj->_canUse)) { + _vm->_mouse->cursorOff(); + titleAnim->markDirtyRect(surface); + title->setText(""); + _objUnderCursor = kObjectNotFound; - _vm->_script->run(obj->_program, obj->_use); - _vm->_mouse->cursorOn(); - } else { - walkHero(x, y); + if (!obj->_imUse) { + if (obj->_useDir == -1) { + walkHero(x, y); + } else { + walkHero(obj->_useX, obj->_useY); + } } + + _vm->_script->run(obj->_program, obj->_use); + _vm->_mouse->cursorOn(); } else { - if (_vm->_script->testExpression(_currentRoom._program, _currentRoom._canUse)) { - _vm->_mouse->cursorOff(); - titleAnim->markDirtyRect(surface); - title->setText(""); + walkHero(x, y); + } + } 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); - } + _vm->_script->run(_currentRoom._program, _currentRoom._use); + _vm->_mouse->cursorOn(); + } else { + walkHero(x, y); } } } } - } - debug(8, "Anim under cursor: %d", _animUnderCursor); + 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 GPL2Program &program = _items[_itemUnderCursor]._program; + const int lookOffset = _items[_itemUnderCursor]._look; + + _vm->_script->run(program, lookOffset); + // 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); + + Animation *inventoryAnim = _vm->_anims->getAnimation(kInventorySprite); + + // If we right-clicked outside the inventory, close it + if (!inventoryAnim->getFrame()->getRect().contains(x, y)) { + 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 GPL2Program &program = _items[_itemUnderCursor]._program; + const int canUseOffset = _items[_itemUnderCursor]._canUse; + const int useOffset = _items[_itemUnderCursor]._use; + + if (_vm->_script->testExpression(program, canUseOffset)) { + _vm->_script->run(program, useOffset); + } + } + updateCursor(); + } + } + } + } + + debugC(5, kDraciLogicDebugLevel, "Anim under cursor: %d", _animUnderCursor); // Handle character talking (if there is any) if (_loopSubstatus == kSubstatusTalk) { @@ -425,9 +510,7 @@ void Game::loop() { return; // Advance animations and redraw screen - if (_loopStatus != kStatusInventory) { - _vm->_anims->drawScene(surface); - } + _vm->_anims->drawScene(surface); _vm->_screen->copyToScreen(); _vm->_system->delayMillis(20); @@ -440,40 +523,106 @@ void Game::loop() { void Game::updateCursor() { - _vm->_mouse->setCursorType(kNormalCursor); + // 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 GPL2Program &program = _items[_itemUnderCursor]._program; + const int canUseOffset = _items[_itemUnderCursor]._canUse; + + if (_vm->_script->testExpression(program, canUseOffset)) { + if (_currentItem == kNoItem) { + _vm->_mouse->setCursorType(kHighlightedCursor); + } else { + _vm->_mouse->loadItemCursor(_currentItem, true); + } + } + } - if (_currentIcon != kNoIcon) { - _vm->_mouse->loadItemCursor(_currentIcon); + 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 (_currentIcon == kNoIcon) { + if (_currentItem == kNoItem) { _vm->_mouse->setCursorType(kHighlightedCursor); } else { - _vm->_mouse->loadItemCursor(_currentIcon, true); + _vm->_mouse->loadItemCursor(_currentItem, true); } } + // If there *is* a game object under the cursor, update the cursor image } else { GameObject *obj = &_objects[_objUnderCursor]; - - _vm->_mouse->setCursorType((CursorType)obj->_walkDir); - if (!(obj->_walkDir > 0)) { + // 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 (_currentIcon == kNoIcon) { + if (_currentItem == kNoItem) { _vm->_mouse->setCursorType(kHighlightedCursor); } else { - _vm->_mouse->loadItemCursor(_currentIcon, true); + _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(); @@ -488,6 +637,8 @@ void Game::updateTitle() { // Mark dirty rectangle to delete the previous text titleAnim->markDirtyRect(surface); + // If there is no object under the cursor, delete the title. + // Otherwise, show the object's title. if (_objUnderCursor == kObjectNotFound) { title->setText(""); } else { @@ -500,6 +651,8 @@ void Game::updateTitle() { int newY = surface->centerOnY(y - smallFontHeight / 2, title->getHeight() * 2); 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 { @@ -521,6 +674,115 @@ int Game::getObjectWithAnimation(int animID) { 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; + + uint i = position; + + if (position >= 0 && + position < kInventoryLines * kInventoryColumns && + _inventory[position] == kNoItem) { + _inventory[position] = itemID; + } else { + for (i = 0; i < kInventorySlots; ++i) { + if (_inventory[i] == kNoItem) { + _inventory[i] = itemID; + break; + } + } + } + + const int line = i / kInventoryColumns + 1; + const int column = i % kInventoryColumns + 1; + + Animation *anim = _vm->_anims->getAnimation(kInventoryItemsID - itemID); + 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(kInventoryItemsID - itemID); + } +} + +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 + _loopStatus = 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(); + _loopStatus = 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::dialogueMenu(int dialogueID) { int oldLines, hit; @@ -578,7 +840,6 @@ int Game::dialogueDraw() { 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[_dialogueLines]; @@ -757,6 +1018,33 @@ void Game::walkHero(int x, int y) { _vm->_anims->play(animID); } +void Game::loadItem(int itemID) { + + 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) { BAFile *f; @@ -1135,12 +1423,20 @@ void Game::setVariable(int numVar, int value) { _variables[numVar] = value; } -int Game::getIconStatus(int iconID) { - return _iconStatus[iconID]; +int Game::getItemStatus(int itemID) { + return _itemStatus[itemID]; +} + +void Game::setItemStatus(int itemID, int status) { + _itemStatus[itemID] = status; +} + +int Game::getCurrentItem() { + return _currentItem; } -int Game::getCurrentIcon() { - return _currentIcon; +void Game::setCurrentItem(int itemID) { + _currentItem = itemID; } Person *Game::getPerson(int personID) { @@ -1186,6 +1482,7 @@ Game::~Game() { delete[] _variables; delete[] _dialogueOffsets; delete[] _objects; + delete[] _items; } |