/* 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 3 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, see . * */ #include "mm/xeen/dialogs/dialogs_items.h" #include "mm/xeen/dialogs/dialogs_query.h" #include "mm/xeen/dialogs/dialogs_quests.h" #include "mm/xeen/resources.h" #include "mm/xeen/xeen.h" namespace MM { namespace Xeen { enum ItemsButtonEnchantTTSTextIndex { kItemsEnchantWeapons = 0, kItemsEnchantArmor = 1, kItemsEnchantAccessories = 2, kItemsEnchantMisc = 3, kItemsEnchantEnchant = 4, kItemsEnchantExit = 5, kItemsEnchantUse = 6, kItemsEnchantItem1 = 7, kItemsEnchantItem2 = 8, kItemsEnchantItem3 = 9, kItemsEnchantItem4 = 10, kItemsEnchantItem5 = 11, kItemsEnchantItem6 = 12, kItemsEnchantItem7 = 13, kItemsEnchantItem8 = 14, kItemsEnchantItem9 = 15 }; enum kItemsButtonTTSTextIndex { kItemsWeapons = 0, kItemsArmor = 1, kItemsAccessories = 2, kItemsMisc = 3, kItemsBuyOrEquip = 4, kItemsSellOrRemove = 5, kItemsIdentifyOrDiscard = 6, kItemsFixOrQuest = 7, kItemsExit = 8, kItemsItem1 = 9, kItemsItem2 = 10, kItemsItem3 = 11, kItemsItem4 = 12, kItemsItem5 = 13, kItemsItem6 = 14, kItemsItem7 = 15, kItemsItem8 = 16, kItemsItem9 = 17 }; enum ItemSelectionButtonTTSTextIndex { kItemSelectionItem1 = 0, kItemSelectionItem2 = 1, kItemSelectionItem3 = 2, kItemSelectionItem4 = 3, kItemSelectionItem5 = 4, kItemSelectionItem6 = 5, kItemSelectionItem7 = 6, kItemSelectionItem8 = 7, kItemSelectionItem9 = 8 }; Character *ItemsDialog::show(XeenEngine *vm, Character *c, ItemsMode mode) { ItemsDialog *dlg = new ItemsDialog(vm); Character *result = dlg->execute(c, mode); delete dlg; return result; } Character *ItemsDialog::execute(Character *c, ItemsMode mode) { Combat &combat = *_vm->_combat; EventsManager &events = *_vm->_events; Interface &intf = *_vm->_interface; Party &party = *_vm->_party; Windows &windows = *_vm->_windows; ItemsMode priorMode = ITEMMODE_INVALID; Character *startingChar = c; ItemCategory category = mode == ITEMMODE_RECHARGE || mode == ITEMMODE_COMBAT ? CATEGORY_MISC : CATEGORY_WEAPON; int varA = mode == ITEMMODE_COMBAT ? 1 : 0; if (varA != 0) mode = ITEMMODE_CHAR_INFO; bool updateStock = mode == ITEMMODE_BUY; int itemIndex = -1; Common::StringArray lines; uint arr[40]; int actionIndex = -1; if (mode == ITEMMODE_BUY) { _oldCharacter = c; c = &_itemsCharacter; party._blacksmithWares.blackData2CharData(_itemsCharacter); _itemsCharacter._class = _oldCharacter->_class; setEquipmentIcons(); } else if (mode == ITEMMODE_ENCHANT) { _oldCharacter = c; } events.setCursor(0); windows[29].open(); windows[30].open(); Common::String buttonsText; #ifdef USE_TTS uint buttonTextCount = 0; #endif enum { REDRAW_NONE, REDRAW_TEXT, REDRAW_FULL } redrawFlag = REDRAW_FULL; for (;;) { if (redrawFlag == REDRAW_FULL) { // Write text for the dialog Common::String msg; if (mode != ITEMMODE_CHAR_INFO && mode != ITEMMODE_8 && mode != ITEMMODE_ENCHANT && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) { msg = Common::String::format(Res.ITEMS_DIALOG_TEXT1, Res.BTN_BUY, Res.BTN_SELL, Res.BTN_IDENTIFY, Res.BTN_FIX); } else if (mode != ITEMMODE_ENCHANT && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) { msg = Common::String::format(Res.ITEMS_DIALOG_TEXT1, category == 3 ? Res.BTN_USE : Res.BTN_EQUIP, Res.BTN_REMOVE, Res.BTN_DISCARD, Res.BTN_QUEST); } else if (mode == ITEMMODE_ENCHANT) { msg = Common::String::format(Res.ITEMS_DIALOG_TEXT2, Res.BTN_ENCHANT); } else if (mode == ITEMMODE_RECHARGE) { msg = Common::String::format(Res.ITEMS_DIALOG_TEXT2, Res.BTN_RECHARGE); } else { msg = Common::String::format(Res.ITEMS_DIALOG_TEXT2, Res.BTN_GOLD); } buttonsText.clear(); windows[29].writeString(msg, false, &buttonsText); Common::fill(&arr[0], &arr[40], 0); itemIndex = -1; priorMode = ITEMMODE_INVALID; } if (mode != priorMode) { // Set up the buttons for the dialog loadButtons(mode, c, category); #ifdef USE_TTS setButtonTexts(buttonsText); buttonTextCount = _buttonTexts.size(); #endif priorMode = mode; drawButtons(&windows[0]); } if (redrawFlag == REDRAW_TEXT || redrawFlag == REDRAW_FULL) { lines.clear(); for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) { DrawStruct &ds = _itemsDrawList[idx]; XeenItem &i = c->_items[category][idx]; ds._sprites = nullptr; ds._x = 8; ds._y = 18 + idx * 9; switch (category) { case CATEGORY_WEAPON: case CATEGORY_ARMOR: case CATEGORY_ACCESSORY: if (i._id) { if ((mode == ITEMMODE_CHAR_INFO && !g_vm->_extOptions._showItemCosts) || mode == ITEMMODE_8 || mode == ITEMMODE_ENCHANT || mode == ITEMMODE_RECHARGE) { lines.push_back(Common::String::format(Res.ITEMS_DIALOG_LINE1, arr[idx], idx + 1, c->_items[category].getFullDescription(idx, arr[idx]).c_str())); } else { lines.push_back(Common::String::format(Res.ITEMS_DIALOG_LINE2, arr[idx], idx + 1, c->_items[category].getFullDescription(idx, arr[idx]).c_str(), calcItemCost(c, idx, (mode == ITEMMODE_CHAR_INFO) ? ITEMMODE_BUY : mode, mode == ITEMMODE_TO_GOLD ? 1 : startingChar->_skills[MERCHANT], category) )); } ds._sprites = &_equipSprites; if (c->_items[category].passRestrictions(i._id, true)) ds._frame = i._frame; else ds._frame = 14; } else if (ds._sprites == nullptr && idx == 0) { lines.push_back(Res.NO_ITEMS_AVAILABLE); } break; case CATEGORY_MISC: if (i._material == 0) { // No item if (idx == 0) { lines.push_back(Res.NO_ITEMS_AVAILABLE); } } else { ItemsMode tempMode = mode; int skill = startingChar->_skills[MERCHANT]; if (mode == ITEMMODE_CHAR_INFO || mode == ITEMMODE_8 || mode == ITEMMODE_ENCHANT || mode == ITEMMODE_RECHARGE) { tempMode = ITEMMODE_ENCHANT; } else if (mode == ITEMMODE_TO_GOLD) { skill = 1; } lines.push_back(Common::String::format(Res.ITEMS_DIALOG_LINE2, arr[idx], idx + 1, c->_items[category].getFullDescription(idx, arr[idx]).c_str(), calcItemCost(c, idx, tempMode, skill, category) )); } break; default: break; } } #ifdef USE_TTS uint8 lineCount = lines.size(); // Make space for spells _buttonTexts.resize(lines.size() + buttonTextCount - 1); #endif while (lines.size() < INV_ITEMS_TOTAL) lines.push_back(""); Common::String ttsMessage; #ifdef USE_TTS uint8 headerCount = 0; #endif // Draw out overall text and the list of items switch (mode) { case ITEMMODE_CHAR_INFO: case ITEMMODE_8: windows[30].writeString(Common::String::format(Res.X_FOR_THE_Y, category == CATEGORY_MISC ? "\x3l" : "\x3""c", Res.CATEGORY_NAMES[category], c->_name.c_str(), Res.CLASS_NAMES[c->_class], category == CATEGORY_MISC ? Res.FMT_CHARGES : " ", lines[0].c_str(), lines[1].c_str(), lines[2].c_str(), lines[3].c_str(), lines[4].c_str(), lines[5].c_str(), lines[6].c_str(), lines[7].c_str(), lines[8].c_str() ), false, &ttsMessage); #ifdef USE_TTS // Misc category lists charges as well headerCount = category == CATEGORY_MISC ? 2 : 1; #endif break; case ITEMMODE_BUY: windows[30].writeString(Common::String::format(Res.AVAILABLE_GOLD_COST, Res.CATEGORY_NAMES[category], party._gold, lines[0].c_str(), lines[1].c_str(), lines[2].c_str(), lines[3].c_str(), lines[4].c_str(), lines[5].c_str(), lines[6].c_str(), lines[7].c_str(), lines[8].c_str() ), false, &ttsMessage); #ifdef USE_TTS headerCount = 3; #endif break; case ITEMMODE_SELL: case ITEMMODE_RECHARGE: case ITEMMODE_ENCHANT: case ITEMMODE_REPAIR: case ITEMMODE_IDENTIFY: case ITEMMODE_TO_GOLD: windows[30].writeString(Common::String::format(Res.X_FOR_Y, Res.CATEGORY_NAMES[category], startingChar->_name.c_str(), (mode == ITEMMODE_RECHARGE || mode == ITEMMODE_ENCHANT) ? Res.CHARGES : Res.COST, lines[0].c_str(), lines[1].c_str(), lines[2].c_str(), lines[3].c_str(), lines[4].c_str(), lines[5].c_str(), lines[6].c_str(), lines[7].c_str(), lines[8].c_str() ), false, &ttsMessage); #ifdef USE_TTS headerCount = 2; #endif break; case ITEMMODE_3: case ITEMMODE_5: windows[30].writeString(Common::String::format(Res.X_FOR_Y_GOLD, Res.CATEGORY_NAMES[category], c->_name.c_str(), party._gold, Res.CHARGES, lines[0].c_str(), lines[1].c_str(), lines[2].c_str(), lines[3].c_str(), lines[4].c_str(), lines[5].c_str(), lines[6].c_str(), lines[7].c_str(), lines[8].c_str() ), false, &ttsMessage); #ifdef USE_TTS headerCount = 2; #endif break; default: break; } #ifdef USE_TTS if (lines[0] == Res.NO_ITEMS_AVAILABLE) { // Speak headers and the "no items" message uint index = 0; _vm->sayText(getNextTextSection(ttsMessage, index, headerCount + 1)); } else { speakText(ttsMessage, headerCount, lineCount); } #endif // Draw the glyphs for the items windows[0].drawList(_itemsDrawList, INV_ITEMS_TOTAL); windows[0].update(); } redrawFlag = REDRAW_NONE; if (itemIndex != -1) { switch (mode) { case ITEMMODE_BUY: actionIndex = 0; break; case ITEMMODE_SELL: actionIndex = 1; break; case ITEMMODE_REPAIR: actionIndex = 3; break; case ITEMMODE_IDENTIFY: actionIndex = 2; break; default: break; } } // If it's time to do an item action, take care of it if (actionIndex >= 0) { int result = doItemOptions(*c, actionIndex, itemIndex, category, mode); if (result == 1) { // Finish dialog with no selected character c = nullptr; break; } else if (result == 2) { // Close dialogs and finish dialog with original starting character windows[30].close(); windows[29].close(); c = startingChar; break; } // Otherwise, result and continue showing dialog actionIndex = -1; redrawFlag = REDRAW_FULL; continue; } #ifdef USE_TTS _vm->sayText(buttonsText); #endif // Wait for a selection _buttonValue = 0; while (!_vm->shouldExit() && !_buttonValue) { events.pollEventsAndWait(); checkEvents(_vm); } if (_vm->shouldExit()) return nullptr; // Handle escaping out of dialog if (_buttonValue == Common::KEYCODE_ESCAPE) { if (mode == ITEMMODE_8) continue; c = startingChar; break; } // Handle other selections if (Common::KEYCODE_F1 == _buttonValue || Common::KEYCODE_F2 == _buttonValue || Common::KEYCODE_F3 == _buttonValue || Common::KEYCODE_F4 == _buttonValue || Common::KEYCODE_F5 == _buttonValue || Common::KEYCODE_F6 == _buttonValue) { if (!varA && mode != ITEMMODE_3 && mode != ITEMMODE_ENCHANT && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD && party._mazeId != 0) { _buttonValue -= Common::KEYCODE_F1; if (_buttonValue < (int)(_vm->_mode == MODE_COMBAT ? combat._combatParty.size() : party._activeParty.size())) { // Character number is valid #ifdef USE_TTS _vm->stopTextToSpeech(); #endif redrawFlag = REDRAW_FULL; Character *newChar = _vm->_mode == MODE_COMBAT ? combat._combatParty[_buttonValue] : &party._activeParty[_buttonValue]; if (mode == ITEMMODE_BUY) { _oldCharacter = newChar; startingChar = newChar; c = &_itemsCharacter; _itemsCharacter._class = _oldCharacter->_class; } else if (mode == ITEMMODE_SELL || mode == ITEMMODE_REPAIR || mode == ITEMMODE_IDENTIFY) { _oldCharacter = newChar; startingChar = newChar; c = newChar; } else if (itemIndex != -1) { // Switching item to another character InventoryItems &destItems = newChar->_items[category]; InventoryItems &srcItems = c->_items[category]; XeenItem &srcItem = srcItems[itemIndex]; if (srcItem._state._cursed) ErrorScroll::show(_vm, Res.CANNOT_REMOVE_CURSED_ITEM); else if (destItems.isFull()) ErrorScroll::show(_vm, Common::String::format( Res.CATEGORY_BACKPACK_IS_FULL[category], newChar->_name.c_str())); else { XeenItem &destItem = destItems[INV_ITEMS_TOTAL - 1]; destItem = srcItem; srcItem.clear(); destItem._frame = 0; srcItems.sort(); destItems.sort(); continue; } } else { c = newChar; startingChar = newChar; } intf.highlightChar(_buttonValue); continue; } } } else if (Common::KEYCODE_1 == _buttonValue || Common::KEYCODE_2 == _buttonValue || Common::KEYCODE_3 == _buttonValue || Common::KEYCODE_4 == _buttonValue || Common::KEYCODE_5 == _buttonValue || Common::KEYCODE_6 == _buttonValue || Common::KEYCODE_7 == _buttonValue || Common::KEYCODE_8 == _buttonValue || Common::KEYCODE_9 == _buttonValue) { // Select an item if (mode != ITEMMODE_3) { _buttonValue -= Common::KEYCODE_1; if (_buttonValue != itemIndex) { // Check whether the new selection has an associated item if (!c->_items[category][_buttonValue].empty()) { itemIndex = _buttonValue; Common::fill(&arr[0], &arr[40], 0); arr[itemIndex] = 15; } } else { Common::fill(&arr[0], &arr[40], 0); itemIndex = -1; } redrawFlag = REDRAW_TEXT; } } else if (Res.KeyConstants.DialogsItems.KEY_WEAPONS == _buttonValue) { // Weapons category category = CATEGORY_WEAPON; redrawFlag = REDRAW_FULL; } else if (Res.KeyConstants.DialogsItems.KEY_ARMOR == _buttonValue) { // Armor category category = CATEGORY_ARMOR; redrawFlag = REDRAW_FULL; } else if (Res.KeyConstants.DialogsItems.KEY_ACCESSORY == _buttonValue) { // Accessories category category = CATEGORY_ACCESSORY; redrawFlag = REDRAW_FULL; } else if (Res.KeyConstants.DialogsItems.KEY_MISC == _buttonValue) { // Misc category = CATEGORY_MISC; redrawFlag = REDRAW_FULL; } else if (mode == ITEMMODE_ENCHANT || mode == ITEMMODE_RECHARGE || mode == ITEMMODE_TO_GOLD) { if (Res.KeyConstants.DialogsItems.KEY_USE == _buttonValue) { } else if (mode == ITEMMODE_ENCHANT && Res.KeyConstants.DialogsItems.KEY_ENCHANT == _buttonValue) { actionIndex = 4; } else if (mode == ITEMMODE_RECHARGE && Res.KeyConstants.DialogsItems.KEY_RECHRG == _buttonValue) { actionIndex = 5; } else if (mode == ITEMMODE_TO_GOLD && Res.KeyConstants.DialogsItems.KEY_GOLD == _buttonValue) { actionIndex = 6; } else if (mode == ITEMMODE_TO_GOLD && Res.KeyConstants.DialogsItems.KEY_EQUIP == _buttonValue) { if (category != CATEGORY_MISC) actionIndex = 0; } } else if (ITEMMODE_BUY == mode || ITEMMODE_SELL == mode || ITEMMODE_IDENTIFY == mode || ITEMMODE_REPAIR == mode) { if (Res.KeyConstants.DialogsItems.KEY_BUY == _buttonValue) { mode = ITEMMODE_BUY; c = &_itemsCharacter; redrawFlag = REDRAW_FULL; } else if (Res.KeyConstants.DialogsItems.KEY_SELL == _buttonValue) { mode = ITEMMODE_SELL; c = startingChar; redrawFlag = REDRAW_TEXT; } else if (Res.KeyConstants.DialogsItems.KEY_IDENTIFY == _buttonValue) { mode = ITEMMODE_IDENTIFY; c = startingChar; redrawFlag = REDRAW_TEXT; } else if (Res.KeyConstants.DialogsItems.KEY_FIX == _buttonValue) { mode = ITEMMODE_REPAIR; c = startingChar; redrawFlag = REDRAW_TEXT; } } else if (ITEMMODE_CHAR_INFO == mode) { if (Res.KeyConstants.DialogsItems.KEY_EQUIP == _buttonValue) { if (category != CATEGORY_MISC) actionIndex = 0; } else if (Res.KeyConstants.DialogsItems.KEY_USE == _buttonValue) { if (category == CATEGORY_MISC) actionIndex = 2; } else if (Res.KeyConstants.DialogsItems.KEY_REM == _buttonValue) { actionIndex = 1; } else if (Res.KeyConstants.DialogsItems.KEY_DISC == _buttonValue) { actionIndex = 3; } else if (Res.KeyConstants.DialogsItems.KEY_QUEST == _buttonValue) { // Quests Quests::show(_vm); redrawFlag = REDRAW_FULL; } } } windows[30].close(); windows[29].close(); intf.drawParty(true); if (updateStock) party._blacksmithWares.charData2BlackData(_itemsCharacter); return c; } void ItemsDialog::loadButtons(ItemsMode mode, Character *&c, ItemCategory category) { if (_iconSprites.empty()) _iconSprites.load(Common::Path(Common::String::format("%s.icn", (mode == ITEMMODE_CHAR_INFO) ? "items" : "buy"))); if (_equipSprites.empty()) _equipSprites.load("equip.icn"); clearButtons(); if (mode == ITEMMODE_ENCHANT || mode == ITEMMODE_RECHARGE || mode == ITEMMODE_TO_GOLD) { // Enchant button list addButton(Common::Rect(12, 109, 36, 129), Res.KeyConstants.DialogsItems.KEY_WEAPONS, &_iconSprites, kItemsEnchantWeapons); addButton(Common::Rect(46, 109, 70, 129), Res.KeyConstants.DialogsItems.KEY_ARMOR, &_iconSprites, kItemsEnchantArmor); addButton(Common::Rect(80, 109, 104, 129), Res.KeyConstants.DialogsItems.KEY_ACCESSORY, &_iconSprites, kItemsEnchantAccessories); addButton(Common::Rect(114, 109, 138, 129), Res.KeyConstants.DialogsItems.KEY_MISC, &_iconSprites, kItemsEnchantMisc); addButton(Common::Rect(148, 109, 172, 129), Res.KeyConstants.DialogsItems.KEY_ENCHANT, &_iconSprites, kItemsEnchantEnchant); addButton(Common::Rect(284, 109, 308, 129), Common::KEYCODE_ESCAPE, &_iconSprites, kItemsEnchantExit); addButton(Common::Rect(148, 109, 172, 129), Res.KeyConstants.DialogsItems.KEY_USE, &_iconSprites, kItemsEnchantUse); addButton(Common::Rect(8, 20, 263, 28), Common::KEYCODE_1, nullptr, kItemsEnchantItem1); addButton(Common::Rect(8, 29, 263, 37), Common::KEYCODE_2, nullptr, kItemsEnchantItem2); addButton(Common::Rect(8, 38, 263, 46), Common::KEYCODE_3, nullptr, kItemsEnchantItem3); addButton(Common::Rect(8, 47, 263, 55), Common::KEYCODE_4, nullptr, kItemsEnchantItem4); addButton(Common::Rect(8, 56, 263, 64), Common::KEYCODE_5, nullptr, kItemsEnchantItem5); addButton(Common::Rect(8, 65, 263, 73), Common::KEYCODE_6, nullptr, kItemsEnchantItem6); addButton(Common::Rect(8, 74, 263, 82), Common::KEYCODE_7, nullptr, kItemsEnchantItem7); addButton(Common::Rect(8, 83, 263, 91), Common::KEYCODE_8, nullptr, kItemsEnchantItem8); addButton(Common::Rect(8, 92, 263, 100), Common::KEYCODE_9, nullptr, kItemsEnchantItem9); } else { bool flag = mode == ITEMMODE_BUY || mode == ITEMMODE_SELL || mode == ITEMMODE_IDENTIFY || mode == ITEMMODE_REPAIR; addButton(Common::Rect(12, 109, 36, 129), Res.KeyConstants.DialogsItems.KEY_WEAPONS, &_iconSprites, kItemsWeapons); addButton(Common::Rect(46, 109, 70, 129), Res.KeyConstants.DialogsItems.KEY_ARMOR, &_iconSprites, kItemsArmor); addButton(Common::Rect(80, 109, 104, 129), Res.KeyConstants.DialogsItems.KEY_ACCESSORY, &_iconSprites, kItemsAccessories); addButton(Common::Rect(114, 109, 138, 129), Res.KeyConstants.DialogsItems.KEY_MISC, &_iconSprites, kItemsMisc); addButton(Common::Rect(148, 109, 172, 129), flag ? Res.KeyConstants.DialogsItems.KEY_BUY : Res.KeyConstants.DialogsItems.KEY_EQUIP, &_iconSprites, kItemsBuyOrEquip); addButton(Common::Rect(182, 109, 206, 129), flag ? Res.KeyConstants.DialogsItems.KEY_SELL : Res.KeyConstants.DialogsItems.KEY_REM, &_iconSprites, kItemsSellOrRemove); addButton(Common::Rect(216, 109, 240, 129), flag ? Res.KeyConstants.DialogsItems.KEY_IDENTIFY : Res.KeyConstants.DialogsItems.KEY_DISC, &_iconSprites, kItemsIdentifyOrDiscard); addButton(Common::Rect(250, 109, 274, 129), flag ? Res.KeyConstants.DialogsItems.KEY_FIX : Res.KeyConstants.DialogsItems.KEY_QUEST, &_iconSprites, kItemsFixOrQuest); addButton(Common::Rect(284, 109, 308, 129), Common::KEYCODE_ESCAPE, &_iconSprites, kItemsExit); addButton(Common::Rect(8, 20, 263, 28), Common::KEYCODE_1, nullptr, kItemsItem1); addButton(Common::Rect(8, 29, 263, 37), Common::KEYCODE_2, nullptr, kItemsItem2); addButton(Common::Rect(8, 38, 263, 46), Common::KEYCODE_3, nullptr, kItemsItem3); addButton(Common::Rect(8, 47, 263, 55), Common::KEYCODE_4, nullptr, kItemsItem4); addButton(Common::Rect(8, 56, 263, 64), Common::KEYCODE_5, nullptr, kItemsItem5); addButton(Common::Rect(8, 65, 263, 73), Common::KEYCODE_6, nullptr, kItemsItem6); addButton(Common::Rect(8, 74, 263, 82), Common::KEYCODE_7, nullptr, kItemsItem7); addButton(Common::Rect(8, 83, 263, 91), Common::KEYCODE_8, nullptr, kItemsItem8); addButton(Common::Rect(8, 92, 263, 100), Common::KEYCODE_9, nullptr, kItemsItem9); addPartyButtons(_vm); } if (mode == ITEMMODE_CHAR_INFO && category == CATEGORY_MISC) { _buttons[4].setFrame(18); _buttons[4]._value = Res.KeyConstants.DialogsItems.KEY_USE; } if (mode != ITEMMODE_ENCHANT && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) { _buttons[5].setFrame(10); _buttons[6].setFrame(12); _buttons[7].setFrame(14); } // Set button as depressed depending on which mode the dialog is currently in switch (mode) { case ITEMMODE_BUY: _buttons[4].setFrame(9); break; case ITEMMODE_SELL: _buttons[5].setFrame(11); break; case ITEMMODE_IDENTIFY: _buttons[6].setFrame(13); break; case ITEMMODE_REPAIR: _buttons[7].setFrame(15); break; default: break; } if ((mode != ITEMMODE_CHAR_INFO || category != CATEGORY_MISC) && mode != ITEMMODE_ENCHANT && mode != ITEMMODE_RECHARGE && mode != ITEMMODE_TO_GOLD) { _buttons[4]._bounds.moveTo(148, _buttons[4]._bounds.top); _buttons[9]._draw = false; } else if (mode == ITEMMODE_RECHARGE) { _buttons[4]._value = Res.KeyConstants.DialogsItems.KEY_RECHRG; } else if (mode == ITEMMODE_ENCHANT) { _buttons[4]._value = Res.KeyConstants.DialogsItems.KEY_ENCHANT; } else if (mode == ITEMMODE_TO_GOLD) { _buttons[4]._value = Res.KeyConstants.DialogsItems.KEY_GOLD; } } void ItemsDialog::setEquipmentIcons() { for (int typeIndex = 0; typeIndex < 4; ++typeIndex) { for (int idx = 0; idx < INV_ITEMS_TOTAL; ++idx) { switch (typeIndex) { case CATEGORY_WEAPON: { XeenItem &i = _itemsCharacter._weapons[idx]; if (i._id <= 17) i._frame = 1; else if (i._id <= 29 || i._id > 33) i._frame = 13; else i._frame = 4; break; } case CATEGORY_ARMOR: { XeenItem &i = _itemsCharacter._armor[idx]; if (i._id <= 7) i._frame = 3; else if (i._id == 8) i._frame = 2; else if (i._id == 9) i._frame = 5; else if (i._id == 10) i._frame = 9; else if (i._id <= 12) i._frame = 10; else i._frame = 6; break; } case CATEGORY_ACCESSORY: { XeenItem &i = _itemsCharacter._accessories[idx]; if (i._id == 1) i._id = 8; else if (i._id == 2) i._frame = 12; else if (i._id <= 7) i._frame = 7; else i._frame = 11; break; } default: break; } } } } int ItemsDialog::calcItemCost(Character *c, int itemIndex, ItemsMode mode, int skillLevel, ItemCategory category) { int amount1 = 0, amount2 = 0, amount3 = 0, amount4 = 0; int result = 0; int level = skillLevel & 0x7f; XeenItem &i = c->_items[category][itemIndex]; const int *BASE_COSTS[4] = { Res.WEAPON_BASE_COSTS, Res.ARMOR_BASE_COSTS, Res.ACCESSORY_BASE_COSTS, Res.MISC_BASE_COSTS }; switch (mode) { case ITEMMODE_BUY: level = 0; break; case ITEMMODE_SELL: case ITEMMODE_TO_GOLD: level = level == 0 ? 1 : 0; break; case ITEMMODE_IDENTIFY: level = 2; break; case ITEMMODE_REPAIR: level = 3; break; default: break; } switch (category) { case CATEGORY_WEAPON: case CATEGORY_ARMOR: case CATEGORY_ACCESSORY: amount1 = (BASE_COSTS[category])[i._id]; if (i._material > 36 && i._material < 59) { switch (i._material) { case 37: amount1 /= 10; break; case 38: amount1 /= 4; break; case 39: amount1 /= 2; break; case 40: amount1 /= 4; break; default: amount1 *= Res.METAL_BASE_MULTIPLIERS[i._material - 37]; break; } } if (i._material < 37) amount2 = Res.ELEMENTAL_DAMAGE[i._material] * 100; else if (i._material > 58) amount3 = Res.ELEMENTAL_DAMAGE[i._material - 59 + 7] * 100; switch (mode) { case ITEMMODE_BUY: case ITEMMODE_SELL: case ITEMMODE_REPAIR: case ITEMMODE_IDENTIFY: case ITEMMODE_TO_GOLD: result = (amount1 + amount2 + amount3 + amount4) / Res.ITEM_SKILL_DIVISORS[level]; if (!result) result = 1; break; default: break; } break; case CATEGORY_MISC: // Misc amount1 = Res.MISC_MATERIAL_COSTS[i._material]; amount4 = Res.MISC_BASE_COSTS[i._id]; switch (mode) { case ITEMMODE_BUY: case ITEMMODE_SELL: case ITEMMODE_REPAIR: case ITEMMODE_IDENTIFY: case ITEMMODE_TO_GOLD: result = (amount1 + amount2 + amount3 + amount4) / Res.ITEM_SKILL_DIVISORS[level]; if (!result) result = 1; break; case ITEMMODE_3: case ITEMMODE_RECHARGE: case ITEMMODE_5: case ITEMMODE_ENCHANT: // Show number of charges result = i._state._counter; break; default: break; } break; default: break; } return (mode == ITEMMODE_CHAR_INFO) ? 0 : result; } const char *ItemsDialog::getGoldPlurals(int cost) { if (Common::RU_RUS == g_vm->getLanguage()) return Res.GOLDS[cost % 10 == 1 ? 0 : 1]; return Res.GOLDS[0]; } int ItemsDialog::doItemOptions(Character &c, int actionIndex, int itemIndex, ItemCategory category, ItemsMode mode) { Combat &combat = *_vm->_combat; EventsManager &events = *_vm->_events; Interface &intf = *_vm->_interface; Party &party = *_vm->_party; Sound &sound = *_vm->_sound; Spells &spells = *_vm->_spells; Windows &windows = *_vm->_windows; int ccNum = _vm->_files->_ccNum; InventoryItems &items = c._items[category]; if (items[0].empty()) // Inventory is empty return category == CATEGORY_MISC ? 0 : 2; if (itemIndex < 0 || itemIndex > 8) { // Populate item button texts. Text for items starts at keycode 1 and ends at keycode 9 Common::StringArray ttsItemButtonTexts; #ifdef USE_TTS for (uint i = 0; i < _buttons.size(); ++i) { if (_buttons[i]._value == Common::KeyCode::KEYCODE_1) { if (_buttons[i]._ttsIndex >= _buttonTexts.size()) { break; } ttsItemButtonTexts.assign(_buttonTexts.begin() + _buttons[i]._ttsIndex, _buttonTexts.end()); break; } } #endif itemIndex = ItemSelectionDialog::show(actionIndex, items, ttsItemButtonTexts); } if (itemIndex != -1) { XeenItem &item = items[itemIndex]; switch (mode) { case ITEMMODE_CHAR_INFO: case ITEMMODE_8: switch (actionIndex) { case 0: c._items[category].equipItem(itemIndex); break; case 1: c._items[category].removeItem(itemIndex); break; case 2: if (!party._mazeId) { ErrorScroll::show(_vm, Res.WHATS_YOUR_HURRY); } else { XeenItem &i = c._misc[itemIndex]; Condition condition = c.worstCondition(); switch (condition) { case ASLEEP: case PARALYZED: case UNCONSCIOUS: case DEAD: case STONED: case ERADICATED: ErrorScroll::show(_vm, Common::String::format(Res.IN_NO_CONDITION, c._name.c_str())); break; default: if (combat._itemFlag) { ErrorScroll::show(_vm, Res.USE_ITEM_IN_COMBAT); } else if (i._id && !i.isBad() && i._state._counter > 0) { --i._state._counter; _oldCharacter = &c; combat._oldCharacter = _oldCharacter; windows[30].close(); windows[29].close(); windows[24].close(); spells.castItemSpell(i._id); if (!i._state._counter) { // Ran out of charges, so make item disappear c._items[category][itemIndex].clear(); c._items[category].sort(); } intf._charsShooting = false; combat.moveMonsters(); combat._whosTurn = -1; return 1; } else { ErrorScroll::show(_vm, Common::String::format(Res.NO_SPECIAL_ABILITIES, c._items[category].getFullDescription(itemIndex).c_str() )); } } } break; case 3: if (c._items[category].discardItem(itemIndex) && mode == ITEMMODE_8) return 2; break; default: break; } break; case ITEMMODE_BUY: { InventoryItems &invItems = _oldCharacter->_items[category]; if (invItems.isFull()) { // Character's inventory for that category is already full ErrorScroll::show(_vm, Common::String::format(Res.BACKPACK_IS_FULL, _oldCharacter->_name.c_str())); } else { int cost = calcItemCost(&c, itemIndex, mode, 0, category); Common::String desc = c._items[category].getFullDescription(itemIndex); if (Confirm::show(_vm, Common::String::format(Res.BUY_X_FOR_Y_GOLD, desc.c_str(), cost, getGoldPlurals(cost)))) { if (party.subtract(CONS_GOLD, cost, WHERE_PARTY, WT_FREEZE_WAIT)) { if (ccNum) { sound.stopSound(); sound.playSound("choice2.voc"); } // Add entry to the end of the list XeenItem &srcItem = c._items[category][itemIndex]; XeenItem &destItem = _oldCharacter->_items[category][INV_ITEMS_TOTAL - 1]; destItem = srcItem; destItem._frame = 0; srcItem.clear(); c._items[category].sort(); _oldCharacter->_items[category].sort(); } } } break; } case ITEMMODE_SELL: { bool noNeed; switch (category) { case CATEGORY_WEAPON: noNeed = (item._state._cursed) || item._id >= XEEN_SLAYER_SWORD; break; default: noNeed = item._state._cursed; break; } if (noNeed) { ErrorScroll::show(_vm, Common::String::format(Res.NO_NEED_OF_THIS, c._items[category].getFullDescription(itemIndex).c_str())); } else { int cost = calcItemCost(&c, itemIndex, mode, c._skills[MERCHANT], category); Common::String desc = c._items[category].getFullDescription(itemIndex); Common::String msg = Common::String::format(Res.SELL_X_FOR_Y_GOLD, desc.c_str(), cost, getGoldPlurals(cost)); if (Confirm::show(_vm, msg)) { // Remove the sold item and add gold to the party's total item.clear(); c._items[category].sort(); party._gold += cost; } } break; } case ITEMMODE_RECHARGE: if (category != CATEGORY_MISC || item.empty() || item._material > 9 || item._id == 53) { sound.playFX(21); ErrorScroll::show(_vm, Common::String::format(Res.NOT_RECHARGABLE, Res.SPELL_FAILED)); } else { item._state._counter = MIN(63, _vm->getRandomNumber(1, 6) + item._state._counter); sound.playFX(20); } return 2; case ITEMMODE_ENCHANT: { int amount = _vm->getRandomNumber(1, _oldCharacter->getCurrentLevel() / 5 + 1); amount = MIN(amount, 5); _oldCharacter->_items[category].enchantItem(itemIndex, amount); return 2; } case ITEMMODE_REPAIR: if (!item._state._broken) { ErrorScroll::show(_vm, Res.ITEM_NOT_BROKEN); } else { int cost = calcItemCost(&c, itemIndex, mode, actionIndex, category); Common::String msg = Common::String::format(Res.FIX_IDENTIFY_GOLD, Res.FIX_IDENTIFY[0], c._items[category].getFullDescription(itemIndex).c_str(), cost, getGoldPlurals(cost)); if (Confirm::show(_vm, msg) && party.subtract(CONS_GOLD, cost, WHERE_PARTY)) { item._state._broken = false; } } break; case ITEMMODE_IDENTIFY: { int cost = calcItemCost(&c, itemIndex, mode, actionIndex, category); Common::String msg = Common::String::format(Res.FIX_IDENTIFY_GOLD, Res.FIX_IDENTIFY[1], c._items[category].getFullDescription(itemIndex).c_str(), cost, getGoldPlurals(cost)); if (Confirm::show(_vm, msg) && party.subtract(CONS_GOLD, cost, WHERE_PARTY)) { Common::String details = c._items[category].getIdentifiedDetails(itemIndex); Common::String desc = c._items[category].getFullDescription(itemIndex); Common::String str = Common::String::format(Res.IDENTIFY_ITEM_MSG, desc.c_str(), details.c_str()); Window &win = windows[14]; win.open(); win.writeString(str); win.update(); saveButtons(); clearButtons(); while (!_vm->shouldExit() && !events.isKeyMousePressed()) events.pollEventsAndWait(); events.clearEvents(); restoreButtons(); win.close(); } break; } case ITEMMODE_TO_GOLD: // Convert item in inventory to gold itemToGold(c, itemIndex, category, mode); return 2; default: break; } } return 0; } void ItemsDialog::itemToGold(Character &c, int itemIndex, ItemCategory category, ItemsMode mode) { XeenItem &item = c._items[category][itemIndex]; Party &party = *_vm->_party; Sound &sound = *_vm->_sound; if (category == CATEGORY_WEAPON && item._id >= XEEN_SLAYER_SWORD) { sound.playFX(21); ErrorScroll::show(_vm, Common::String::format("\v012\t000\x03""c%s", Res.SPELL_FAILED)); } else if (!item.empty()) { // There is a valid item present // Calculate cost of item and add it to the party's total int cost = calcItemCost(&c, itemIndex, mode, 1, category); party._gold += cost; // Remove the item from the inventory item.clear(); c._items[category].sort(); } } #ifdef USE_TTS void ItemsDialog::speakText(const Common::String &text, uint8 headerCount, uint8 lineCount) { uint index = 0; _vm->sayText(getNextTextSection(text, index, headerCount)); uint startingIndex = 0; for (uint i = 0; i < _buttonTexts.size(); ++i) { if (_buttonTexts[i].empty()) { startingIndex = i; break; } } // In some cases, each item has 3 fields: number, name, and cost. In others, it only has the number and name // This generally corresponds to the number of fields in the header (i.e. "Weapons for Character" and "Cost" is 2 headers // and 2 fields, versus just "Weapons for Character" that's 1 header with 1 field) uint fieldsPerSection = (headerCount >= 2 || g_vm->_extOptions._showItemCosts) ? 3 : 2; for (uint i = 0; i < lineCount; ++i) { Common::String itemInfo = getNextTextSection(text, index, fieldsPerSection, ", "); _vm->sayText(itemInfo); if (startingIndex != 0 && i + startingIndex < _buttonTexts.size()) { _buttonTexts[i + startingIndex] = itemInfo; } } } #endif /*------------------------------------------------------------------------*/ int ItemSelectionDialog::show(int actionIndex, InventoryItems &items, const Common::StringArray &ttsItemButtonTexts) { ItemSelectionDialog *dlg = new ItemSelectionDialog(g_vm, actionIndex, items, ttsItemButtonTexts); int result = dlg->execute(); delete dlg; return result; } void ItemSelectionDialog::loadButtons() { _icons.load("esc.icn"); addButton(Common::Rect(235, 111, 259, 131), Common::KEYCODE_ESCAPE, &_icons); addButton(Common::Rect(8, 20, 263, 28), Common::KEYCODE_1, nullptr, kItemSelectionItem1); addButton(Common::Rect(8, 29, 263, 37), Common::KEYCODE_2, nullptr, kItemSelectionItem2); addButton(Common::Rect(8, 38, 263, 46), Common::KEYCODE_3, nullptr, kItemSelectionItem3); addButton(Common::Rect(8, 47, 263, 55), Common::KEYCODE_4, nullptr, kItemSelectionItem4); addButton(Common::Rect(8, 56, 263, 64), Common::KEYCODE_5, nullptr, kItemSelectionItem5); addButton(Common::Rect(8, 65, 263, 73), Common::KEYCODE_6, nullptr, kItemSelectionItem6); addButton(Common::Rect(8, 74, 263, 82), Common::KEYCODE_7, nullptr, kItemSelectionItem7); addButton(Common::Rect(8, 83, 263, 91), Common::KEYCODE_8, nullptr, kItemSelectionItem8); addButton(Common::Rect(8, 92, 263, 100), Common::KEYCODE_9, nullptr, kItemSelectionItem9); } int ItemSelectionDialog::execute() { EventsManager &events = *g_vm->_events; Windows &windows = *g_vm->_windows; Window &w = windows[13]; w.open(); w.writeString(Common::String::format(Res.WHICH_ITEM, Res.ITEM_ACTIONS[_actionIndex])); _icons.draw(0, 0, Common::Point(235, 111)); w.update(); #ifdef USE_TTS for (uint i = 0; i < _buttonTexts.size(); ++i) { _vm->sayText(_buttonTexts[i]); } #endif int itemIndex = -1; while (!_vm->shouldExit()) { _buttonValue = 0; while (!_buttonValue) { events.pollEventsAndWait(); checkEvents(_vm); if (_vm->shouldExit()) return false; } if (_buttonValue == Common::KEYCODE_ESCAPE) { itemIndex = -1; break; } else if (_buttonValue >= Common::KEYCODE_1 && _buttonValue <= Common::KEYCODE_9) { // Check whether there's an item at the selected index int selectedIndex = _buttonValue - Common::KEYCODE_1; if (!_items[selectedIndex]._id) continue; itemIndex = selectedIndex; break; } } w.close(); return itemIndex; } } // End of namespace Xeen } // End of namespace MM