https://github.com/scummvm/scummvm
Tip revision: 840ea0219937f08ef2470bc355a3cc95b5ac159a authored by Thierry Crozat on 08 December 2017, 22:00:35 UTC
RELEASE: This is 2.0.1pre
RELEASE: This is 2.0.1pre
Tip revision: 840ea02
inventory.cpp
/* 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.
*
*/
#include "lastexpress/game/inventory.h"
#include "lastexpress/data/cursor.h"
#include "lastexpress/data/scene.h"
#include "lastexpress/data/snd.h"
#include "lastexpress/game/entities.h"
#include "lastexpress/game/logic.h"
#include "lastexpress/game/savegame.h"
#include "lastexpress/game/scenes.h"
#include "lastexpress/game/state.h"
#include "lastexpress/menu/menu.h"
#include "lastexpress/sound/queue.h"
#include "lastexpress/graphics.h"
#include "lastexpress/lastexpress.h"
#include "lastexpress/resource.h"
namespace LastExpress {
Inventory::Inventory(LastExpressEngine *engine) : _engine(engine), _selectedItem(kItemNone), _highlightedItemIndex(0), _itemsShown(0),
_showingHourGlass(false), _blinkingDirection(1), _blinkingBrightness(0),
_useMagnifier(false), _portraitHighlighted(false), _isOpened(false), _eggHightlighted(false), _itemScene(NULL) {
//_inventoryRect = Common::Rect(0, 0, 32, 32);
_menuEggRect = Common::Rect(608, 448, 640, 480);
_selectedItemRect = Common::Rect(44, 0, 76, 32);
init();
debug(9, "_showingHourGlass: %d", _showingHourGlass);
}
Inventory::~Inventory() {
_itemScene = NULL;
// Zero passed pointers
_engine = NULL;
}
//////////////////////////////////////////////////////////////////////////
// Inventory handling
//////////////////////////////////////////////////////////////////////////
// Initialize inventory contents
void Inventory::init() {
// ID
_entries[kItemMatchBox].cursor = kCursorMatchBox;
_entries[kItemTelegram].cursor = kCursorTelegram;
_entries[kItemPassengerList].cursor = kCursorPassengerList;
_entries[kItemArticle].cursor = kCursorArticle;
_entries[kItemScarf].cursor = kCursorScarf;
_entries[kItemPaper].cursor = kCursorPaper;
_entries[kItemParchemin].cursor = kCursorParchemin;
_entries[kItemMatch].cursor = kCursorMatch;
_entries[kItemWhistle].cursor = kCursorWhistle;
_entries[kItemKey].cursor = kCursorKey;
_entries[kItemBomb].cursor = kCursorBomb;
_entries[kItemFirebird].cursor = kCursorFirebird;
_entries[kItemBriefcase].cursor = kCursorBriefcase;
_entries[kItemCorpse].cursor = kCursorCorpse;
// Selectable
_entries[kItemMatchBox].isSelectable = true;
_entries[kItemMatch].isSelectable = true;
_entries[kItemTelegram].isSelectable = true;
_entries[kItemWhistle].isSelectable = true;
_entries[kItemKey].isSelectable = true;
_entries[kItemFirebird].isSelectable = true;
_entries[kItemBriefcase].isSelectable = true;
_entries[kItemCorpse].isSelectable = true;
_entries[kItemPassengerList].isSelectable = true;
// Auto selection
_entries[kItem2].floating = false;
_entries[kItem3].floating = false;
_entries[kItem5].floating = false;
_entries[kItem7].floating = false;
_entries[kItem9].floating = false;
_entries[kItem11].floating = false;
_entries[kItemBeetle].floating = false;
_entries[kItem17].floating = false;
_entries[kItemFirebird].floating = false;
_entries[kItemBriefcase].floating = false;
_entries[kItemCorpse].floating = false;
_entries[kItemGreenJacket].floating = false;
_entries[kItem22].floating = false;
// Scene
_entries[kItemMatchBox].scene = kSceneMatchbox;
_entries[kItemTelegram].scene = kSceneTelegram;
_entries[kItemPassengerList].scene = kScenePassengerList;
_entries[kItemScarf].scene = kSceneScarf;
_entries[kItemParchemin].scene = kSceneParchemin;
_entries[kItemArticle].scene = kSceneArticle;
_entries[kItemPaper].scene = kScenePaper;
_entries[kItemFirebird].scene = kSceneFirebird;
_entries[kItemBriefcase].scene = kSceneBriefcase;
// Has item
_entries[kItemTelegram].inPocket = true;
_entries[kItemArticle].inPocket = true;
_selectedItem = kItemNone;
}
void Inventory::handleMouseEvent(const Common::Event &ev) {
_useMagnifier = false;
// Egg (menu)
if (!_menuEggRect.contains(ev.mouse)) {
// Remove highlight if needed
if (_eggHightlighted) {
if (!getGlobalTimer()) {
drawItem((CursorStyle)(getMenu()->getGameId() + 39), 608, 448, 1);
askForRedraw();
}
_eggHightlighted = false;
}
} else {
// Highlight menu
if (!_eggHightlighted) {
if (!getGlobalTimer()) {
drawItem((CursorStyle)(getMenu()->getGameId() + 39), 608, 448);
askForRedraw();
}
_eggHightlighted = true;
}
// If clicked, show the menu
if (ev.type == Common::EVENT_LBUTTONDOWN) {
_eggHightlighted = false;
_portraitHighlighted = false;
_isOpened = false;
getSound()->playSoundWithSubtitles("LIB039.SND", kFlagMenuClock, kEntityPlayer);
getMenu()->show(true, kSavegameTypeIndex, 0);
} else if (ev.type == Common::EVENT_RBUTTONDOWN && getGlobalTimer()) {
if (getSoundQueue()->isBuffered("TIMER"))
getSoundQueue()->removeFromQueue("TIMER");
setGlobalTimer(900);
}
}
// Selected item
if (ev.mouse.x >= 32) {
if (_isOpened) {
// If we are currently inside inventory with the mouse pressed
if (getFlags()->mouseLeftPressed) {
if (_highlightedItemIndex)
drawHighlight(_highlightedItemIndex, true);
} else {
// The user released the mouse button, we need to update the inventory state (clear highlight and items)
drawItem((CursorStyle)getProgress().portrait, 0, 0, 1);
_engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(0, 44, 32, (int16)(40 * _itemsShown + 40)));
_isOpened = false;
askForRedraw();
drawSelectedItem();
// Load backup scene
if (getState()->sceneUseBackup) {
SceneIndex scene = kSceneNone;
if (getState()->sceneBackup2) {
scene = getState()->sceneBackup2;
getState()->sceneBackup2 = kSceneNone;
// Load our scene
getScenes()->loadScene(scene);
} else if (!getEvent(kEventKronosBringFirebird) && !getProgress().isEggOpen) {
getState()->sceneUseBackup = false;
scene = getState()->sceneBackup;
if (getEntities()->getPosition(getScenes()->get(scene)->car, getScenes()->get(scene)->position))
scene = getScenes()->processIndex(getState()->sceneBackup);
// Load our scene
getScenes()->loadScene(scene);
}
}
}
} else {
if (_portraitHighlighted) {
drawItem((CursorStyle)getProgress().portrait, 0, 0, 1);
askForRedraw();
_portraitHighlighted = false;
}
// Magnifier
if (_selectedItem != kItemNone
&& get(_selectedItem)->scene != kSceneNone
&& _selectedItemRect.contains(ev.mouse)) {
if (!getState()->sceneUseBackup || (getState()->sceneBackup2 && getFirstExaminableItem() == _selectedItem))
_useMagnifier = true;
if (getFlags()->mouseLeftPressed) {
if (getState()->sceneUseBackup) {
if (getState()->sceneBackup2 && getFirstExaminableItem() == _selectedItem) {
SceneIndex sceneIndex = getState()->sceneBackup2;
getState()->sceneBackup2 = kSceneNone;
getScenes()->loadScene(sceneIndex);
}
} else {
getState()->sceneBackup = getState()->scene;
getState()->sceneUseBackup = true;
getScenes()->loadScene(get(_selectedItem)->scene);
}
}
}
}
return;
}
// Opened inventory
if (ev.mouse.y >= 32) {
// Draw portrait (darkened) if the inventory is closed (and we are not on top of it)
if (!_isOpened) {
if (_portraitHighlighted) {
drawItem((CursorStyle)getProgress().portrait, 0, 0, 1);
askForRedraw();
_portraitHighlighted = false;
}
return;
}
// Change item highlight on list
if (getFlags()->mouseLeftPressed) {
uint32 index = (uint16)ev.mouse.y / 40;
if (_highlightedItemIndex && _highlightedItemIndex != index)
drawHighlight(_highlightedItemIndex, true);
if (index && index <= _itemsShown && index != _highlightedItemIndex)
drawHighlight(index, false);
return;
}
// User released the mouse button, check if we were on a valid item
uint32 index = _highlightedItemIndex ? getItemIndex(_highlightedItemIndex) : 0;
// Reset items and portrait
drawItem((CursorStyle)getProgress().portrait, 0, 0, 1);
_engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(0, 44, 32, (int16)(40 * _itemsShown + 40)));
askForRedraw();
_highlightedItemIndex = 0;
_itemsShown = 0;
// Load the scene if an item has been selected
if (index) {
InventoryEntry entry = _entries[index];
// If a scene is affected to the item
if (entry.scene) {
if (getState()->sceneUseBackup) {
if (getFirstExaminableItem() && !getState()->sceneBackup2)
getState()->sceneBackup2 = getState()->scene;
} else {
getState()->sceneUseBackup = true;
getState()->sceneBackup = getState()->scene;
}
getScenes()->loadScene(entry.scene);
}
if (entry.usable)
selectItem((InventoryItem)index);
else
drawSelectedItem();
// Set inventory as closed (will cause a cleanup on next call)
_isOpened = false;
return;
}
// Draw the currently selected item
drawSelectedItem();
// Stop processing if we are not looking at an item already
if (!getState()->sceneUseBackup) {
_isOpened = false;
return;
}
SceneIndex scene = kSceneNone;
if (getState()->sceneBackup2) {
scene = getState()->sceneBackup2;
getState()->sceneBackup2 = kSceneNone;
} else {
if (getEvent(kEventKronosBringFirebird) || getProgress().isEggOpen) {
_isOpened = false;
return;
}
getState()->sceneUseBackup = false;
scene = getState()->sceneBackup;
if (getEntities()->getPosition(getScenes()->get(scene)->car, getScenes()->get(scene)->position))
scene = getScenes()->processIndex(getState()->sceneBackup);
}
getScenes()->loadScene(scene);
_isOpened = false;
return;
}
//
if (!getProgress().field_84
&& getEntityData(kEntityPlayer)->location != kLocationOutsideTrain
&& getProgress().field_18 != 4
&& (_selectedItem == kItemNone || get(_selectedItem)->floating || getState()->sceneUseBackup)) {
// Draw inventory contents when clicking on portrait
if (ev.type == Common::EVENT_LBUTTONDOWN) {
open();
return;
}
if (!_portraitHighlighted && !_isOpened) {
drawItem((CursorStyle)getProgress().portrait, 0, 0);
_portraitHighlighted = true;
} else if (!_isOpened || getFlags()->mouseLeftPressed) {
// Do nothing
} else if (_isOpened) {
close();
// Select item
if (_selectedItem == kItemNone || get(_selectedItem)->floating) {
_selectedItem = getFirstExaminableItem();
if (_selectedItem != kItemNone)
drawItem(get(_selectedItem)->cursor, 44, 0);
}
// Restore scene
if (getState()->sceneBackup) {
if (getState()->sceneBackup2) {
SceneIndex backup = getState()->sceneBackup2;
getState()->sceneBackup2 = kSceneNone;
getScenes()->loadScene(backup);
} else if (!getEvent(kEventKronosBringFirebird)) {
if (!getProgress().isEggOpen) {
getState()->sceneBackup = kSceneNone;
Scene *backup = getScenes()->get(getState()->sceneBackup);
if (getEntities()->getPosition(backup->car, backup->position))
getScenes()->loadScene(getScenes()->processIndex(getState()->sceneBackup));
else
getScenes()->loadScene(getState()->sceneBackup);
}
}
}
_portraitHighlighted = true;
}
// Draw highlighted item
if (_highlightedItemIndex)
drawHighlight(_highlightedItemIndex, true);
}
}
//////////////////////////////////////////////////////////////////////////
// UI
//////////////////////////////////////////////////////////////////////////
void Inventory::show() {
clearBg(GraphicsManager::kBackgroundInventory);
askForRedraw();
// Show portrait (first draw, cannot be highlighted)
drawItem((CursorStyle)getProgress().portrait, 0, 0, 1);
// Show selected item
if (_selectedItem != kItemNone)
drawItem(get(_selectedItem)->cursor, 44, 0);
drawEgg();
}
void Inventory::setPortrait(InventoryItem item) const {
getProgress().portrait = item;
drawItem((CursorStyle)getProgress().portrait, 0, 0);
}
void Inventory::showHourGlass() const {
if (!getMenu()->isShown())
drawItem(kCursorHourGlass, 608, 448);
getFlags()->shouldRedraw = false;
askForRedraw();
getFlags()->shouldDrawEggOrHourGlass = true;
}
//////////////////////////////////////////////////////////////////////////
// Items
//////////////////////////////////////////////////////////////////////////
Inventory::InventoryEntry *Inventory::get(InventoryItem item) {
if (item >= kPortraitOriginal)
error("[Inventory::get] Invalid inventory item");
return &_entries[item];
}
void Inventory::addItem(InventoryItem item) {
if (item >= kPortraitOriginal)
return;
get(item)->inPocket = true;
get(item)->location = kObjectLocationNone;
// Auto-select item if necessary
if (get(item)->cursor && !get(item)->floating) {
_selectedItem = item;
drawItem(get(_selectedItem)->cursor, 44, 0);
askForRedraw();
}
}
void Inventory::removeItem(InventoryItem item, ObjectLocation newLocation) {
if (item >= kPortraitOriginal)
return;
get(item)->inPocket = false;
get(item)->location = newLocation;
if (get(item)->cursor == get(_selectedItem)->cursor) {
_selectedItem = kItemNone;
_engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(44, 0, 44 + 32, 32));
askForRedraw();
}
}
bool Inventory::hasItem(InventoryItem item) {
if (get(item)->inPocket && item < kPortraitOriginal)
return true;
return false;
}
void Inventory::selectItem(InventoryItem item) {
_selectedItem = item;
drawItem(get(_selectedItem)->cursor, 44, 0);
askForRedraw();
}
void Inventory::unselectItem() {
_selectedItem = kItemNone;
_engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(44, 0, 44 + 32, 32));
askForRedraw();
}
void Inventory::setLocationAndProcess(InventoryItem item, ObjectLocation location) {
if (item >= kPortraitOriginal)
return;
if (get(item)->location == location)
return;
get(item)->location = location;
if (isItemSceneParameter(item) && !getFlags()->flag_0)
getScenes()->processScene();
}
//////////////////////////////////////////////////////////////////////////
// Serializable
//////////////////////////////////////////////////////////////////////////
void Inventory::saveLoadWithSerializer(Common::Serializer &s) {
for (uint i = 0; i < ARRAYSIZE(_entries); i++)
_entries[i].saveLoadWithSerializer(s);
}
void Inventory::saveSelectedItem(Common::Serializer &s) {
s.syncAsUint32LE(_selectedItem);
}
//////////////////////////////////////////////////////////////////////////
// toString
//////////////////////////////////////////////////////////////////////////
Common::String Inventory::toString() {
Common::String ret = "";
for (int i = 0; i < kPortraitOriginal; i++)
ret += Common::String::format("%d : %s\n", i, _entries[i].toString().c_str());
return ret;
}
//////////////////////////////////////////////////////////////////////////
// Private methods
//////////////////////////////////////////////////////////////////////////
InventoryItem Inventory::getFirstExaminableItem() const {
int index = 0;
do {
InventoryEntry entry = _entries[index];
// Check if it is an examinable item
if (entry.inPocket && entry.cursor && !entry.floating)
return (InventoryItem)index;
index++;
} while (index < kPortraitOriginal);
return kItemNone;
}
bool Inventory::isItemSceneParameter(InventoryItem item) const {
Scene *scene = getScenes()->get(getState()->scene);
switch(scene->type) {
default:
return false;
case Scene::kTypeItem:
if (scene->param1 == item)
return true;
break;
case Scene::kTypeItem2:
if (scene->param1 == item || scene->param2 == item)
return true;
break;
case Scene::kTypeObjectItem:
if (scene->param2 == item)
return true;
break;
case Scene::kTypeItem3:
if (scene->param1 == item || scene->param2 == item || scene->param3 == item)
return true;
break;
case Scene::kTypeCompartmentsItem:
if (scene->param2 == item)
return true;
break;
}
return false;
}
// Examine an inventory item
void Inventory::examine(InventoryItem item) {
SceneIndex index = get(item)->scene;
if (!index)
return;
/*if (!getState()->sceneUseBackup ||
(getState()->sceneBackup2 && getFirstExaminableItem() == _selectedItem))
flag = 1;*/
if (!getState()->sceneUseBackup) {
getState()->sceneBackup = getState()->scene;
getState()->sceneUseBackup = true;
getScenes()->loadScene(index);
} else {
if (!getState()->sceneBackup2)
return;
if (getFirstExaminableItem() == _selectedItem) {
index = getState()->sceneBackup2;
getState()->sceneBackup2 = kSceneNone;
getScenes()->loadScene(index);
}
}
}
void Inventory::drawEgg() const {
if (!getMenu()->isShown())
drawItem((CursorStyle)(getMenu()->getGameId() + 39), 608, 448, _eggHightlighted ? 0 : 1);
getFlags()->shouldDrawEggOrHourGlass = false;
}
// Blinking egg: we need to blink the egg for delta time, with the blinking getting faster until it's always lit.
void Inventory::drawBlinkingEgg(uint ticks) {
uint globalTimer = (uint)getGlobalTimer();
uint timerValue = (getProgress().jacket == kJacketGreen) ? 450 : 225;
if (globalTimer == timerValue || globalTimer == 900) {
_blinkingBrightness = 0;
_blinkingDirection = 1;
}
globalTimer = globalTimer <= ticks ? 0 : globalTimer - ticks;
setGlobalTimer(globalTimer);
if (getFlags()->flag_0
|| (globalTimer % 5) == 0
|| (globalTimer <= 500 && (globalTimer % ((globalTimer + 100) / 100)) == 0))
blinkEgg();
if (globalTimer < 90) {
if ((globalTimer + ticks) >= 90)
getSound()->playSoundWithSubtitles("TIMER", (SoundFlag)(kFlagType13|kFlagDefault), kEntityPlayer);
if (!getSoundQueue()->isBuffered("TIMER"))
setGlobalTimer(0);
}
if (globalTimer == 0) {
drawItem((CursorStyle)(getMenu()->getGameId() + 39), 608, 448, _menuEggRect.contains(getCoords()) ? 1 : -1);
askForRedraw();
getSaveLoad()->saveGame(kSavegameTypeAuto, kEntityChapters, 0);
}
}
void Inventory::blinkEgg() {
drawItem((CursorStyle)(getMenu()->getGameId() + 39), 608, 448, (_blinkingBrightness == 0) ? -1 : (int16)_blinkingBrightness);
askForRedraw();
_blinkingBrightness += _blinkingDirection;
if (_blinkingBrightness == 0 || _blinkingBrightness == 3)
_blinkingDirection = -_blinkingDirection;
}
void Inventory::drawItem(CursorStyle id, uint16 x, uint16 y, int16 brightnessIndex) const {
Icon icon(id);
icon.setPosition(x, y);
if (brightnessIndex != -1)
icon.setBrightness(brightnessIndex);
_engine->getGraphicsManager()->draw(&icon, GraphicsManager::kBackgroundInventory);
}
void Inventory::drawSelectedItem() {
// Draw the selected item if any
if (!_selectedItem || get(_selectedItem)->floating) {
_selectedItem = getFirstExaminableItem();
if (_selectedItem) {
drawItem(get(_selectedItem)->cursor, 44, 0);
} else {
clearSelectedItem();
}
askForRedraw();
}
}
void Inventory::clearSelectedItem() const {
_engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(44, 0, 44 + 32, 32));
}
// Open inventory: show portrait selected and draw contents
void Inventory::open() {
_portraitHighlighted = false;
_isOpened = true;
// Draw highlighted portrait
drawItem((CursorStyle)(getProgress().portrait + 1), 0, 0);
// Draw at most 11 items in the inventory
_itemsShown = 0;
for (int i = 1; i < ARRAYSIZE(_entries); i++) {
if (!_entries[i].inPocket)
continue;
if (!_entries[i].floating)
continue;
if (_itemsShown < 11) {
drawItem(_entries[i].cursor, 0, 40 * _itemsShown + 44, 1);
++_itemsShown;
}
}
askForRedraw();
}
// Close inventory: clear items and reset icon
void Inventory::close() {
_isOpened = false;
// Fallback to unselected state
drawItem((CursorStyle)getProgress().portrait, 0, 0);
// Erase rectangle for inventory items shown
_engine->getGraphicsManager()->clear(GraphicsManager::kBackgroundInventory, Common::Rect(0, 44, 32, (int16)(44 + 40 * _itemsShown)));
_itemsShown = 0;
askForRedraw();
}
void Inventory::drawHighlight(uint32 currentIndex, bool reset) {
uint32 index = getItemIndex(currentIndex);
if (index) {
drawItem(_entries[index].cursor, 0, 40 * currentIndex + 4, reset ? 1 : -1);
_highlightedItemIndex = reset ? 0 : currentIndex;
askForRedraw();
}
}
uint32 Inventory::getItemIndex(uint32 currentIndex) const {
uint32 count = 0;
for (uint32 i = 1; i < ARRAYSIZE(_entries); i++) {
if (!_entries[i].inPocket)
continue;
if (!_entries[i].floating)
continue;
if (count < 11) {
++count;
if (count == currentIndex)
return i;
}
}
return 0;
}
} // End of namespace LastExpress