https://github.com/scummvm/scummvm
Raw File
Tip revision: bd2bf31c5fd8bb0d40226094d67775c12b0aa095 authored by Torbjörn Andersson on 23 February 2022, 10:43:05 UTC
SCUMM: Fix regression in ScummEngine_v5::decodeParseString()
Tip revision: bd2bf31
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", kSoundTypeMenu | kSoundFlagFixedVolume | kVolumeFull, kEntityPlayer);

			getMenu()->show(true, kSavegameTypeIndex, 0);

		} else if (ev.type == Common::EVENT_RBUTTONDOWN && getGlobalTimer()) {
			if (getSoundQueue()->isBuffered("TIMER"))
				getSoundQueue()->stop("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", kSoundTypeMenu | kVolumeFull, 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
back to top