/* 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 "access/inventory.h" #include "access/access.h" #include "access/resources.h" #include "access/amazon/amazon_resources.h" #include "access/martian/martian_resources.h" namespace Access { void InventoryEntry::load(const Common::String &name, const int *data) { _value = ITEM_NOT_FOUND; _name = name; if (data) { _otherItem1 = *data++; _newItem1 = *data++; _otherItem2 = *data++; _newItem2 = *data; } else { _otherItem1 = -1; _newItem1 = -1; _otherItem2 = -1; _newItem2 = -1; } } int InventoryEntry::checkItem(int itemId) { if (_otherItem1 == itemId) return _newItem1; else if (_otherItem2 == itemId) return _newItem2; else return -1; } /*------------------------------------------------------------------------*/ InventoryManager::InventoryManager(AccessEngine *vm) : Manager(vm) { _startInvItem = 0; _startInvBox = 0; _invChangeFlag = true; _invRefreshFlag = false; _invModeFlag = false; _startAboutItem = 0; _startTravelItem = 0; _iconDisplayFlag = true; _boxNum = 0; const char *const *names; const int *combineP; switch (vm->getGameID()) { case GType_Amazon: names = Amazon::INVENTORY_NAMES; combineP = &Amazon::COMBO_TABLE[0][0]; _inv.resize(85); for (uint i = 0; i < _inv.size(); ++i, combineP += 4) _inv[i].load(names[i], combineP); break; case GType_MartianMemorandum: names = Martian::INVENTORY_NAMES; combineP = nullptr; _inv.resize(55); for (uint i = 0; i < _inv.size(); ++i) _inv[i].load(names[i], nullptr); break; default: error("Unknown game"); } for (uint i = 0; i < 26; ++i) { const int *r = INVCOORDS[i]; _invCoords.push_back(Common::Rect(r[0], r[2], r[1], r[3])); } } int &InventoryManager::operator[](int idx) { // WORKAROUND: At least in Amazon, some game scripts accidentally do reads // beyond the length of the inventory array static int invalid = 0; return (idx >= (int)_inv.size()) ? invalid : _inv[idx]._value; } int InventoryManager::useItem() { return _vm->_useItem; } void InventoryManager::setUseItem(int itemId) { _vm->_useItem = itemId; } void InventoryManager::refreshInventory() { // The original version was using pre-rendering for the inventory to spare some time. // This is not needed on modern hardware, and it breaks a couple of things. // Therefore it was removed in order to keep the same logic than for the CD version // if (_vm->_screen->_vesaMode) { // _invRefreshFlag = true; // newDisplayInv(); // } } int InventoryManager::newDisplayInv() { Screen &screen = *_vm->_screen; EventsManager &events = *_vm->_events; Room &room = *_vm->_room; FileManager &files = *_vm->_files; _invModeFlag = true; _vm->_timers.saveTimers(); if (!room._tile && !_invRefreshFlag) { saveScreens(); } savedFields(); screen.setPanel(1); events._cursorExitFlag = false; getList(); initFields(); files.loadScreen(&_vm->_buffer1, 99, 0); _vm->_buffer1.copyTo(&_vm->_buffer2); _vm->copyBF2Vid(); // Set cells Common::Array cells; cells.push_back(CellIdent(99, 99, 1)); _vm->loadCells(cells); showAllItems(); if (!_invRefreshFlag) { chooseItem(); if (_vm->_useItem != -1) { int savedScale = _vm->_scale; _vm->_scale = 153; _vm->_screen->setScaleTable(_vm->_scale); _vm->_buffer1.clearBuffer(); SpriteResource *spr = _vm->_objectsTable[99]; SpriteFrame *frame = spr->getFrame(_vm->_useItem); int w = screen._scaleTable1[46]; int h = screen._scaleTable1[35]; _vm->_buffer1.sPlotF(frame, Common::Rect(0, 0, w, h)); events.setCursorData(&_vm->_buffer1, Common::Rect(0, 0, w, h)); _vm->_scale = savedScale; screen.setScaleTable(_vm->_scale); } } freeInvCells(); screen.setPanel(0); events.debounceLeft(); restoreFields(); screen.restorePalette(); // The original was testing the vesa mode too. // We removed this check as we don't use pre-rendering if (!_invRefreshFlag) { screen.clearScreen(); screen.setPalette(); } if (!room._tile && !_invRefreshFlag) { restoreScreens(); } else { screen.setBufferScan(); room.buildScreen(); // The original was doing a check on the vesa mode at this point. // We don't need it as we don't do inventory pre-rendering screen.fadeOut(); _vm->copyBF2Vid(); } events._cursorExitFlag = false; screen._screenChangeFlag = false; _invModeFlag = false; events.debounceLeft(); _vm->_timers.restoreTimers(); _vm->_startup = 1; int result = 0; if (!_invRefreshFlag) { if (_vm->_useItem == -1) { result = 2; events.forceSetCursor(CURSOR_CROSSHAIRS); } else events.forceSetCursor(CURSOR_INVENTORY); } _invRefreshFlag = false; _invChangeFlag = false; return result; } int InventoryManager::displayInv() { int *inv = (int *) malloc (Martian::INVENTORY_SIZE * sizeof(int)); for (int i = 0; i < Martian::INVENTORY_SIZE; i++) inv[i] = _inv[i]._value; _vm->_events->forceSetCursor(CURSOR_CROSSHAIRS); _vm->_invBox->getList(Martian::INVENTORY_NAMES, inv); int btnSelected = 0; int boxX = _vm->_invBox->doBox_v1(_startInvItem, _startInvBox, btnSelected); _startInvItem = _vm->BOXDATASTART; _startInvBox= _vm->BOXSELECTY; if (boxX == -1) btnSelected = 2; if (btnSelected != 2) _vm->_useItem = _vm->_invBox->_tempListIdx[boxX]; else _vm->_useItem = -1; free(inv); return 0; } void InventoryManager::savedFields() { Screen &screen = *_vm->_screen; Room &room = *_vm->_room; _fields._vWindowHeight = screen._vWindowHeight; _fields._vWindowLinesTall = screen._vWindowLinesTall; _fields._vWindowWidth = screen._vWindowWidth; _fields._vWindowBytesWide = screen._vWindowBytesWide; _fields._playFieldHeight = room._playFieldHeight; _fields._playFieldWidth = room._playFieldWidth; _fields._windowXAdd = screen._windowXAdd; _fields._windowYAdd = screen._windowYAdd; _fields._screenYOff = screen._screenYOff; _fields._scrollX = _vm->_scrollX; _fields._scrollY = _vm->_scrollY; _fields._clipWidth = screen._clipWidth; _fields._clipHeight = screen._clipHeight; _fields._bufferStart = screen._bufferStart; _fields._scrollCol = _vm->_scrollCol; _fields._scrollRow = _vm->_scrollRow; } void InventoryManager::restoreFields() { Screen &screen = *_vm->_screen; Room &room = *_vm->_room; screen._vWindowHeight = _fields._vWindowHeight; screen._vWindowLinesTall = _fields._vWindowLinesTall; screen._vWindowWidth = _fields._vWindowWidth; screen._vWindowBytesWide = _fields._vWindowBytesWide; room._playFieldHeight = _fields._playFieldHeight; room._playFieldWidth = _fields._playFieldWidth; screen._windowXAdd = _fields._windowXAdd; screen._windowYAdd = _fields._windowYAdd; screen._screenYOff = _fields._screenYOff; _vm->_scrollX = _fields._scrollX; _vm->_scrollY = _fields._scrollY; screen._clipWidth = _fields._clipWidth; screen._clipHeight = _fields._clipHeight; screen._bufferStart = _fields._bufferStart; _vm->_scrollCol = _fields._scrollCol; _vm->_scrollRow = _fields._scrollRow; } void InventoryManager::initFields() { Screen &screen = *_vm->_screen; Room &room = *_vm->_room; screen._vWindowHeight = screen.h; room._playFieldHeight = screen.h; screen._vWindowLinesTall = screen.h; screen._clipHeight = screen.h; room._playFieldWidth = screen.w; screen._vWindowWidth = screen.w; screen._vWindowBytesWide = screen.w; screen._clipWidth = screen.w; screen._windowXAdd = 0; screen._windowYAdd = 0; screen._screenYOff = 0; screen._bufferStart.x = 0; screen._bufferStart.y = 0; _vm->_scrollX = _vm->_scrollY = 0; _vm->_buffer1.clearBuffer(); _vm->_buffer2.clearBuffer(); // The original was doing at this point a check on vesa mode // We don't need it as we don't do inventory pre-rendering if (!_invRefreshFlag) screen.clearBuffer(); screen.savePalette(); } void InventoryManager::getList() { _items.clear(); _tempLOff.clear(); for (uint i = 0; i < _inv.size(); ++i) { if (_inv[i]._value == ITEM_IN_INVENTORY) { _items.push_back(i); _tempLOff.push_back(_inv[i]._name); } } } void InventoryManager::showAllItems() { _iconDisplayFlag = true; for (uint i = 0; i < _items.size(); ++i) putInvIcon(i, _items[i]); } void InventoryManager::putInvIcon(int itemIndex, int itemId) { SpriteResource *spr = _vm->_objectsTable[99]; assert(spr); Common::Point pt((itemIndex % 6) * 46 + 23, (itemIndex / 6) * 35 + 15); _vm->_buffer2.plotImage(spr, itemId, pt); if (_iconDisplayFlag) { _vm->_screen->copyBlock(&_vm->_buffer2, Common::Rect(pt.x, pt.y, pt.x + 46, pt.y + 35)); } } void InventoryManager::chooseItem() { EventsManager &events = *_vm->_events; _vm->_useItem = -1; while (!_vm->shouldQuit()) { // Check for events events.pollEventsAndWait(); int selIndex; // Poll events and wait for a click on a known area if (!events._leftButton || ((selIndex = coordIndexOf()) == -1)) continue; if (selIndex > 23) { if (selIndex == 25) _vm->_useItem = -1; break; } else if (selIndex < (int)_items.size() && _items[selIndex] != -1) { _boxNum = selIndex; _vm->copyBF2Vid(); combineItems(); _vm->copyBF2Vid(); outlineIcon(_boxNum); _vm->_useItem = _items[_boxNum]; } } } void InventoryManager::freeInvCells() { delete _vm->_objectsTable[99]; _vm->_objectsTable[99] = nullptr; } int InventoryManager::coordIndexOf() { const Common::Point pt = _vm->_events->_mousePos; for (int i = 0; i < (int)_invCoords.size(); ++i) { if (_invCoords[i].contains(pt)) return i; } return -1; } void InventoryManager::saveScreens() { _vm->_buffer1.copyTo(&_savedBuffer1); _vm->_screen->copyTo(&_savedScreen); _vm->_newRects.push_back(Common::Rect(0, 0, _savedScreen.w, _savedScreen.h)); } void InventoryManager::restoreScreens() { _vm->_buffer1.w = _vm->_buffer1.pitch; _savedBuffer1.copyTo(&_vm->_buffer1); _savedScreen.copyTo(_vm->_screen); _savedBuffer1.free(); _savedScreen.free(); } void InventoryManager::outlineIcon(int itemIndex) { Screen &screen = *_vm->_screen; screen.frameRect(_invCoords[itemIndex], 7); Common::String s = _tempLOff[itemIndex]; Font &font = _vm->_fonts._font2; int strWidth = font.stringWidth(s); font._fontColors[0] = 0; font._fontColors[1] = 10; font._fontColors[2] = 11; font._fontColors[3] = 12; font.drawString(&screen, s, Common::Point((screen.w - strWidth) / 2, 184)); } void InventoryManager::combineItems() { Screen &screen = *_vm->_screen; EventsManager &events = *_vm->_events; screen._leftSkip = screen._rightSkip = 0; screen._topSkip = screen._bottomSkip = 0; screen._screenYOff = 0; Common::Point tempMouse = events._mousePos; Common::Point lastMouse = events._mousePos; Common::Rect &inv = _invCoords[_boxNum]; Common::Rect r(inv.left, inv.top, inv.left + 46, inv.top + 35); Common::Point tempBox(inv.left, inv.top); Common::Point lastBox(inv.left, inv.top); _vm->_buffer2.copyBlock(&_vm->_buffer1, r); SpriteResource *sprites = _vm->_objectsTable[99]; int invItem = _items[_boxNum]; events.pollEvents(); // Item drag handling loop if left button is held down while (!_vm->shouldQuit() && events._leftButton) { // Poll for events events.pollEventsAndWait(); // Check positioning if (lastMouse == events._mousePos) continue; lastMouse = events._mousePos; Common::Rect lastRect(lastBox.x, lastBox.y, lastBox.x + 46, lastBox.y + 35); screen.copyBlock(&_vm->_buffer2, lastRect); Common::Point newPt; newPt.x = MAX(events._mousePos.x - tempMouse.x + tempBox.x, 0); newPt.y = MAX(events._mousePos.y - tempMouse.y + tempBox.y, 0); screen.plotImage(sprites, invItem, newPt); lastBox = newPt; } int destBox = events.checkMouseBox1(_invCoords); if (destBox >= 0 && destBox != _boxNum && destBox < (int)_items.size() && _items[destBox] != -1) { int itemA = invItem; int itemB = _items[destBox]; // Check whether the items can be combined int combinedItem = _inv[itemA].checkItem(itemB); if (combinedItem != -1) { _inv[combinedItem]._value = 1; _inv[itemA]._value = 2; _inv[itemB]._value = 2; _items[_boxNum] = -1; _items[destBox] = combinedItem; _tempLOff[destBox] = _inv[combinedItem]._name; events.hideCursor(); // Shrink down the first item on top of the second item zoomIcon(itemA, itemB, destBox, true); // Shrink down the second item Common::Rect destRect(_invCoords[destBox].left, _invCoords[destBox].top, _invCoords[destBox].left + 46, _invCoords[destBox].top + 35); _vm->_buffer2.copyBlock(&_vm->_buffer1, destRect); screen._screenYOff = 0; zoomIcon(itemB, -1, destBox, true); // Exand up the new combined item from nothing to full size zoomIcon(combinedItem, -1, destBox, false); _boxNum = destBox; events.showCursor(); return; } } _iconDisplayFlag = true; putInvIcon(_boxNum, invItem); } void InventoryManager::zoomIcon(int zoomItem, int backItem, int zoomBox, bool shrink) { Screen &screen = *_vm->_screen; screen._screenYOff = 0; SpriteResource *sprites = _vm->_objectsTable[99]; int oldScale = _vm->_scale; int zoomScale = shrink ? 255 : 1; int zoomInc = shrink ? -1 : 1; Common::Rect boxRect(_invCoords[zoomBox].left, _invCoords[zoomBox].top, _invCoords[zoomBox].left + 46, _invCoords[zoomBox].top + 35); while (!_vm->shouldQuit() && zoomScale != 0 && zoomScale != 256) { _vm->_events->pollEventsAndWait(); _vm->_buffer2.copyBlock(&_vm->_buffer1, boxRect); if (backItem != -1) { _iconDisplayFlag = false; putInvIcon(zoomBox, backItem); } _vm->_scale = zoomScale; screen.setScaleTable(zoomScale); int xv = screen._scaleTable1[boxRect.width() + 1]; if (xv) { int yv = screen._scaleTable1[boxRect.height() + 1]; if (yv) { // The zoomed size is positive in both directions, so show zoomed item Common::Rect scaledBox(xv, yv); scaledBox.moveTo(boxRect.left + (boxRect.width() - xv + 1) / 2, boxRect.top + (boxRect.height() - yv + 1) / 2); _vm->_buffer2.sPlotF(sprites->getFrame(zoomItem), scaledBox); } } screen.copyBlock(&_vm->_buffer2, boxRect); zoomScale += zoomInc; } if (!shrink) { // Handle the final full-size version _vm->_buffer2.copyBlock(&_vm->_buffer1, boxRect); _vm->_buffer2.plotImage(sprites, zoomItem, Common::Point(boxRect.left, boxRect.top)); screen.copyBlock(&_vm->_buffer2, boxRect); } _vm->_scale = oldScale; screen.setScaleTable(oldScale); } void InventoryManager::synchronize(Common::Serializer &s) { int count = _inv.size(); s.syncAsUint16LE(count); if (!s.isSaving()) _inv.resize(count); for (int i = 0; i < count; ++i) s.syncAsUint16LE(_inv[i]._value); } } // End of namespace Access